From 93e85345df0e47a480e943588f8a3323a91027bb Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Tue, 16 Sep 2025 13:23:28 +0200 Subject: [PATCH 1/7] Backmerge: master into develop (#105) From 0b216811776b2ab31e9bcfdea9e718d1cb5e7ca1 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 16 Oct 2025 22:33:19 +0200 Subject: [PATCH 2/7] Refactor core objects, improve validation, and update API (#106) * Simplifies branch processing and installs badgery * Adds flag for unit test coverage in .badgery.yaml * Updates dependencies and easydiffraction shortcuts * Adds dashboard build trigger post-tutorial tests * Updates tutorial command names in workflow script * Simplifies installation by using all extras * Adds dashboard build trigger after PyPI tests * Refactors sample model creation architecture * Updates sample model API in tests * Updates sample model API in tutorials * Refactors argument validation and simplifies CIF reading * Update SampleModel API and adjust linked phases check * Increase McCabe complexity limit and add dependency * Fixes dev-install command in pixi.toml * Adds centralized logging utility * Adds function to parse CIF-style numeric strings to ufloat * Refactors core object model for improved structure * Refactors ID attributes to use entry_name consistently * Refactors alias and constraint classes * Refactors core objects to enhance category handling * Refactors core components and structures for enhanced clarity * Adds CIF alias utility functions to enhance data handling * Add test suite for SampleModels components and fix imports * Updates package versions in dependency lock file * Introduce allowed attributes and update collection initialization * Adds allowed attributes to mixins for parameter handling * Refactors CIF conversion by removing method calls * Enhances attribute validation in CryspyCalculator * Allow editing of uncertainty and free attributes * Refines CIF data parsing and handling logic * Modifies model initialization and linking * Refines instrument and peak attribute handling * Fixes bug in parameter setting logic * Refactors parameter attributes and access methods * Enhances background and model flexibility * Rename error handling methods and adjust datablock management * Refactors entry name handling in core classes * Standardizes 'entry_name' to 'category_entry_name' * Enhances sample model and plotting configuration * Enhances item management in CategoryCollection * Adds allowed attributes to SingleCrystalExperiment * Replaces human-readable ID generation with property access * Comments out unit test step from pre-push hook * Disables physical parameter bounds * Refines physical constraints and simplifies code * Refactors method calls to use add_from_args for clarity * Refactors method calls and updates numeric constants * Updates parameter bound handling and method naming * Refactors method calls to improve clarity * Refines attribute access and updates test methods * Refactors item addition in CategoryCollection * Refactors unit tests to use full_cif_names * Refactors parameter access and method calls * Refines attribute naming for clarity in background model * Adjusts default renderer for PyCharm environment * Updates attribute checks, adds data files, renames scripts * Adds extra test configuration and refines unit tests * Refactors attribute access and control logic * Enhances logging and error handling mechanism * Simplifies error handling in logging with 'raise from' * Refactors core objects for modular design * Improves logging behavior during pytest * Simplifies py-format-fix command * Refactors parameter descriptor hierarchy * Enhances logging and documents parameter abstraction * Refactors test cases to align with new API and data structures * Cleans up test imports and simplifies assertions * Refactors singleton tests for consistency * Refines descriptor/parameter handling logic * First attempt to add CifMixin to parameter classes * Updates CifMixin.cif_uid to use full_name * Refactors parameter abstraction layer * Enhances Logger configuration with environment variables * Refactors lazy loading in __init__.py for efficiency * Consolidates attribute guard mixins and refactors logic * Move mixins to guarded base class * Refactors core component architecture * Replace enforce_type with typeguard for runtime type checking * Remove legacy signature from add method and update tests * Enhances category item renaming logic (ugly temporary workaround) * Refactors attribute handling in parameter classes * Enhances Project class with type-checking and encapsulation * Refines __str__ method and fixes super call * Enhances collection handling by implementing AbstractCollection * Enhances type safety with generics for collections * Add SPDX license headers to source files * Initialize base class in CollectionBase constructor * Refactors parameter access for consistency * Removes unnecessary Generic typing * Work in progress * Enhances identity resolution and string representation * Improves parameter retrieval in collections * Adds `full_name` property to multiple classes * Simplifies attribute handling in GuardedBase * Renames full_name property to unique_name * Refines validation logging messages * Adds configurable error reaction in Logger * Enhances type error diagnostics in checktype decorator * Giant refactoring * Refactors parameter handling in FitResults * Refactors descriptor properties and enhances validation * Removes unused TODO comments and corrects assertions * Adds SPDX license headers to source files * Enhances space group validation logic * Refactors validation logic for atom site properties * Adds missing newline in atom_sites.py * Refactors CIF conversion and updates test format * Reformats code for consistency and clarity * Refines parameter handling and improves validation * Refactors tests to improve readability and maintainability * Enhances atom site configuration in tutorials * Enhances x-axis data selection for plotting * Refactors atom site addition for clarity and consistency * Fixes parameter limit handling * Fixes attribute assignment and model access * Refactors data extraction with list comprehensions * Refactors constraint handling and data access methods * Enhances peak profile type handling in experiments * Removes unused public attributes method * Improves logging settings and Python path resolution * Refines validators with enhanced logging * Adds parameter validation and logging enhancements * Replaces diagnostics and validators with an enhanced implementation * Adds Diagnostics for Type Override and Enhances Collections * Enhances validation and logging mechanisms * Refactors validator and identity handling * Replaces RegexValidator with AttributeSpec for validation * Maintain parent linkage for nested objects * Corrects attribute access in identity resolution * Refactors validation types to use DataTypes enum * Refines type validation logic in diagnostics * Replace UUID with secure random UID generation * Refines atom site validations and logging * Enhances validation logic and diagnostics handling * Relocates CifHandler to new IO module for clarity * Refactors experiment enums structure * Updates peak mixin imports for better organization * Refactors instrument setup for improved modularity * Refactors background module implementation * Refactors CIF serialization methods * Refactors CIF serialization to centralized functions * Removes obsolete CIF conversion methods * Restructures experiment type classes for modular design * Refactors instrument handling and restructures sample models * Reorganizes import paths to category_items * Refactors component naming for better clarity * Refactors code to categorize collections * Reorganizes import paths to use category_collections modules * Refactors background type handling for clarity * Refactors background handling and reorganizes enums * Refactors background and instrument modules organization * Refactors instrument and peak base class imports * Refactors argument validation in ExperimentFactory * Refactors import paths for consistency * Refactors datastore architecture for segregation of types * Fixes property getter for uncertainty * Add and update SPDX license headers across files * Adds package structure documentation generator * Renames datastore type modules for clarity * Refactors package structure for clarity * Renames and restructures package directories for clarity * Renames core modules for consistency * New unit tests * Refactors factory method usage in test suite * Add unit tests for various components in easydiffraction * Adds unit test for crystallographic and experimental data * Enables macOS h5py dependency for validation * Replaces scaffold tests with concrete test implementations * Replaces test scaffolds with concrete tests * Adds comprehensive test coverage for core modules * Format tests * Enhances SPDX header update for tests directory * Cleans up tests and refines pytest configuration * Refines config and reorganizes files for tmp exclusion * Adds comprehensive docstrings to Analysis classes and descriptors * Enhances experiment class documentation * Updates .gitignore for test and linter caches * Removes Python cache before packaging artifacts * Updates python_socketio and mkdocs-jupyter dependencies * Adds multiple Python cache removal steps * Adds dev-install step in GitHub workflow * Removes redundant dev-install command from workflow * Reorganizes dependency configuration * Refactors Python cache removal in workflow * Refactors to use Factory classes for model and experiment creation * Enhances enums with descriptive methods * Enhances documentation with docstrings * Add missing documentation for core modules * Adds docstrings for improved code documentation * Updates pytest configuration for test management * Refactors CryspyCalculator and updates SPDX compliance * Remove commented code and add documentation placeholders * Adds environment variable for CI branch --- .badgery.yaml | 1 + .github/workflows/dashboard.yaml | 23 +- .github/workflows/pypi-test.yaml | 34 +- .github/workflows/test.yaml | 5 + .github/workflows/tutorial-tests.yaml | 27 + .gitignore | 9 + .pre-commit-config.yaml | 12 +- .prettierignore | 4 + docs/architecture/package-structure-full.md | 245 + docs/architecture/package-structure-short.md | 133 + pixi.lock | 11331 ++++++++-------- pixi.toml | 30 +- pyproject.toml | 29 +- pytest.ini | 7 + src/easydiffraction/__init__.py | 152 +- src/easydiffraction/analysis/analysis.py | 209 +- src/easydiffraction/analysis/calculation.py | 12 +- .../{calculator_base.py => base.py} | 24 +- .../{calculator_crysfml.py => crysfml.py} | 16 +- .../{calculator_cryspy.py => cryspy.py} | 111 +- .../{calculator_factory.py => factory.py} | 38 +- .../{calculator_pdffit.py => pdffit.py} | 17 +- .../{collections => categories}/__init__.py | 0 .../analysis/categories/aliases.py | 106 + .../analysis/categories/constraints.py | 102 + .../categories/joint_fit_experiments.py | 104 + .../analysis/collections/aliases.py | 50 - .../analysis/collections/constraints.py | 50 - .../collections/joint_fit_experiments.py | 54 - .../{fitting => fit_helpers}/__init__.py | 0 .../{fitting => fit_helpers}/metrics.py | 4 +- .../results.py => fit_helpers/reporting.py} | 64 +- .../tracking.py} | 40 +- .../analysis/{minimization.py => fitting.py} | 28 +- .../minimizers/{minimizer_base.py => base.py} | 62 +- .../{minimizer_dfols.py => dfols.py} | 6 +- .../{minimizer_factory.py => factory.py} | 6 +- .../{minimizer_lmfit.py => lmfit.py} | 12 +- src/easydiffraction/core/category.py | 82 + src/easydiffraction/core/collection.py | 102 + src/easydiffraction/core/datablock.py | 90 + src/easydiffraction/core/diagnostic.py | 217 + src/easydiffraction/core/factory.py | 36 + src/easydiffraction/core/guard.py | 144 + src/easydiffraction/core/identity.py | 77 + src/easydiffraction/core/objects.py | 538 - src/easydiffraction/core/parameters.py | 365 + src/easydiffraction/core/singletons.py | 36 +- src/easydiffraction/core/validation.py | 345 + .../crystallography/crystallography.py | 28 +- .../crystallography/space_groups.py | 10 + .../{collections => categories}/__init__.py | 0 .../background}/__init__.py | 0 .../experiments/categories/background/base.py | 35 + .../categories/background/chebyshev.py | 115 + .../categories/background/enums.py | 27 + .../categories/background/factory.py | 67 + .../categories/background/line_segment.py | 118 + .../categories/excluded_regions.py | 125 + .../experiments/categories/experiment_type.py | 156 + .../categories/instrument}/__init__.py | 0 .../experiments/categories/instrument/base.py | 24 + .../experiments/categories/instrument/cwl.py | 72 + .../categories/instrument/factory.py | 74 + .../experiments/categories/instrument/tof.py | 153 + .../experiments/categories/linked_phases.py | 86 + .../categories/peak}/__init__.py | 0 .../experiments/categories/peak/base.py | 13 + .../experiments/categories/peak/cwl.py | 45 + .../experiments/categories/peak/cwl_mixins.py | 331 + .../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/collections/background.py | 241 - .../collections/excluded_regions.py | 98 - .../experiments/collections/linked_phases.py | 59 - .../experiments/components/experiment_type.py | 119 - .../experiments/components/instrument.py | 141 - .../experiments/components/peak.py | 466 - src/easydiffraction/experiments/datastore.py | 254 - .../experiments/datastore/__init__.py | 2 + .../experiments/datastore/base.py | 79 + .../experiments/datastore/factory.py | 70 + .../experiments/datastore/pd.py | 63 + .../experiments/datastore/sc.py | 46 + src/easydiffraction/experiments/experiment.py | 572 - .../experiments/experiment/__init__.py | 16 + .../experiments/experiment/base.py | 217 + .../experiments/experiment/bragg_pd.py | 135 + .../experiments/experiment/bragg_sc.py | 28 + .../experiments/experiment/enums.py | 118 + .../experiments/experiment/factory.py | 150 + .../experiment/instrument_mixin.py | 46 + .../experiments/experiment/total_pd.py | 59 + .../experiments/experiments.py | 121 +- src/easydiffraction/io/cif/handler.py | 33 + src/easydiffraction/io/cif/serialize.py | 268 + .../plotting/plotters/plotter_ascii.py | 13 + .../plotting/plotters/plotter_base.py | 21 +- .../plotting/plotters/plotter_plotly.py | 21 +- src/easydiffraction/plotting/plotting.py | 23 + src/easydiffraction/project/__init__.py | 2 + src/easydiffraction/{ => project}/project.py | 273 +- src/easydiffraction/project/project_info.py | 104 + .../sample_models/categories/__init__.py | 2 + .../sample_models/categories/atom_sites.py | 295 + .../sample_models/categories/cell.py | 157 + .../sample_models/categories/space_group.py | 107 + .../sample_models/collections/atom_sites.py | 113 - .../sample_models/components/cell.py | 75 - .../sample_models/components/space_group.py | 43 - .../sample_models/sample_model/__init__.py | 2 + .../{sample_model.py => sample_model/base.py} | 168 +- .../sample_models/sample_model/factory.py | 170 + .../sample_models/sample_models.py | 142 +- src/easydiffraction/summary/__init__.py | 2 + src/easydiffraction/{ => summary}/summary.py | 40 +- src/easydiffraction/utils/decorators.py | 59 - src/easydiffraction/utils/logging.py | 310 + src/easydiffraction/utils/utils.py | 52 +- .../test_pair-distribution-function.py | 64 +- ..._powder-diffraction_constant-wavelength.py | 219 +- .../test_powder-diffraction_joint-fit.py | 143 +- .../test_powder-diffraction_multiphase.py | 73 +- .../test_powder-diffraction_time-of-flight.py | 93 +- .../calculators/test_calculator_base.py | 83 - .../calculators/test_calculator_cryspy.py | 103 - .../calculators/test_calculator_factory.py | 65 - .../collections/test_joint_fit_experiment.py | 22 - .../test_fitting_progress_tracker.py | 98 - .../minimizers/test_minimizer_base.py | 116 - .../minimizers/test_minimizer_dfols.py | 79 - .../minimizers/test_minimizer_factory.py | 66 - .../minimizers/test_minimizer_lmfit.py | 98 - tests/unit/analysis/test_analysis.py | 157 - tests/unit/analysis/test_minimization.py | 163 - .../unit/analysis/test_reliability_factors.py | 110 - tests/unit/core/test_objects.py | 128 - tests/unit/core/test_singletons.py | 87 - .../analysis/calculators/test_base.py | 60 + .../analysis/calculators/test_crysfml.py | 29 + .../analysis/calculators/test_cryspy.py | 25 + .../analysis/calculators/test_factory.py | 41 + .../analysis/calculators/test_pdffit.py | 98 + .../analysis/categories/test_aliases.py | 15 + .../analysis/categories/test_constraints.py | 14 + .../categories/test_joint_fit_experiments.py | 15 + .../analysis/fit_helpers/test_metrics.py | 56 + .../analysis/fit_helpers/test_reporting.py | 60 + .../analysis/fit_helpers/test_tracking.py | 45 + .../analysis/minimizers/test_base.py | 107 + .../analysis/minimizers/test_dfols.py | 50 + .../analysis/minimizers/test_factory.py | 51 + .../analysis/minimizers/test_lmfit.py | 73 + .../easydiffraction/analysis/test_analysis.py | 84 + .../analysis/test_analysis_access_params.py | 50 + .../analysis/test_analysis_show_empty.py | 39 + .../analysis/test_calculation.py | 39 + .../easydiffraction/analysis/test_fitting.py | 36 + .../easydiffraction/core/test_category.py | 67 + .../easydiffraction/core/test_collection.py | 31 + .../easydiffraction/core/test_datablock.py | 73 + .../easydiffraction/core/test_diagnostic.py | 33 + .../unit/easydiffraction/core/test_factory.py | 29 + tests/unit/easydiffraction/core/test_guard.py | 53 + .../easydiffraction/core/test_identity.py | 30 + .../easydiffraction/core/test_parameters.py | 100 + .../easydiffraction/core/test_singletons.py | 45 + .../easydiffraction/core/test_validation.py | 65 + .../crystallography/test_crystallography.py | 9 + .../crystallography/test_space_groups.py | 9 + .../categories/background/test_base.py | 62 + .../categories/background/test_chebyshev.py | 26 + .../categories/background/test_enums.py | 11 + .../categories/background/test_factory.py | 24 + .../background/test_line_segment.py | 33 + .../categories/instrument/test_base.py | 12 + .../categories/instrument/test_cwl.py | 12 + .../categories/instrument/test_factory.py | 37 + .../categories/instrument/test_tof.py | 44 + .../experiments/categories/peak/test_base.py | 13 + .../experiments/categories/peak/test_cwl.py | 34 + .../categories/peak/test_cwl_mixins.py | 30 + .../categories/peak/test_factory.py | 58 + .../experiments/categories/peak/test_tof.py | 25 + .../categories/peak/test_tof_mixins.py | 38 + .../experiments/categories/peak/test_total.py | 36 + .../categories/peak/test_total_mixins.py | 12 + .../categories/test_excluded_regions.py | 41 + .../categories/test_experiment_type.py | 36 + .../categories/test_linked_phases.py | 16 + .../experiments/datastore/test_base.py | 9 + .../experiments/datastore/test_factory.py | 24 + .../experiments/datastore/test_pd.py | 9 + .../experiments/datastore/test_sc.py | 9 + .../experiments/experiment/test_base.py | 40 + .../experiments/experiment/test_bragg_pd.py | 72 + .../experiments/experiment/test_bragg_sc.py | 35 + .../experiments/experiment/test_enums.py | 20 + .../experiments/experiment/test_factory.py | 35 + .../experiment/test_instrument_mixin.py | 11 + .../experiments/experiment/test_total_pd.py | 51 + .../experiments/test_experiments.py | 40 + .../easydiffraction/io/cif/test_handler.py | 16 + .../easydiffraction/io/cif/test_serialize.py | 83 + .../io/cif/test_serialize_more.py | 181 + .../plotting/plotters/test_plotter_ascii.py | 23 + .../plotting/plotters/test_plotter_base.py | 36 + .../plotting/plotters/test_plotter_plotly.py | 87 + .../easydiffraction/plotting/test_plotting.py | 138 + .../easydiffraction/project/test_project.py | 9 + .../project/test_project_d_spacing.py | 102 + .../project/test_project_info.py | 9 + .../test_project_load_and_summary_wrap.py | 39 + .../project/test_project_save.py | 39 + .../categories/test_atom_sites.py | 29 + .../sample_models/categories/test_cell.py | 43 + .../categories/test_space_group.py | 15 + .../sample_models/sample_model/test_base.py | 12 + .../sample_model/test_factory.py | 16 + .../sample_models/test_sample_models.py | 12 + .../easydiffraction/summary/test_summary.py | 60 + .../summary/test_summary_details.py | 135 + tests/unit/easydiffraction/test___init__.py | 51 + tests/unit/easydiffraction/test___main__.py | 54 + .../easydiffraction/utils/test_formatting.py | 39 + .../easydiffraction/utils/test_logging.py | 27 + .../unit/easydiffraction/utils/test_utils.py | 198 + .../collections/test_background.py | 106 - .../experiments/collections/test_datastore.py | 181 - .../collections/test_linked_phases.py | 46 - .../components/test_experiment_type.py | 54 - .../experiments/components/test_instrument.py | 85 - .../unit/experiments/components/test_peak.py | 145 - tests/unit/experiments/test_experiment.py | 154 - tests/unit/experiments/test_experiments.py | 86 - .../collections/test_atom_sites.py | 86 - .../sample_models/components/test_cell.py | 37 - .../components/test_space_group.py | 31 - .../unit/sample_models/test_sample_models.py | 80 - tests/unit/test_project.py | 212 - tests/unit/test_symmetry_lookup_table.py | 129 - tmp/Untitled.ipynb | 492 + tmp/Untitled1.ipynb | 149 + ...truct_pd-neut-tof_multiphase-BSFTO-HRPT.py | 42 +- .../data/DREAM_mantle_bc240_nist_cif.xye | 0 .../data/DREAM_mantle_bc240_nist_cif_2.xye | 0 .../data/DREAM_mantle_bc240_nist_nc.xye | 0 .../data/DREAM_mantle_bc240_nist_nc_2.xye | 0 .../data/Si_mp-149_symmetrized_mcstas.cif | 0 tmp/data/hrpt_lbco.xye | 3099 +++++ tmp/data/lbco.cif | 36 + tmp/generate_overview_mermaid.py | 309 + ...0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye | 0 tmp/short.py | 94 + tmp/short2.py | 228 + tmp/short3.py | 138 + tmp/short5.py | 316 + tmp/short6.py | 85 + tmp/short7.py | 129 + ...test_single-fit_pd-neut-tof_Si-DREAM_nc.py | 83 +- tools/gen_tests_scaffold.py | 107 + tools/generate_package_docs.py | 172 + tools/naming_check.py | 117 + tools/test_scripts.py | 7 +- tools/update_spdx.py | 89 +- ...vanced_joint-fit_pd-neut-xray-cwl_PbSO4.py | 68 +- .../basic_single-fit_pd-neut-cwl_LBCO-HRPT.py | 35 +- .../cryst-struct_pd-neut-cwl_CoSiO4-D20.py | 98 +- tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py | 79 +- tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py | 23 +- ...-struct_pd-neut-tof_multidata_NCAF-WISH.py | 86 +- ...t_pd-neut-tof_multiphase-LBCO-Si_McStas.py | 98 +- tutorials/data/lbco.cif | 36 + ...school-2025_analysis-powder-diffraction.py | 62 +- tutorials/pdf_pd-neut-cwl_Ni.py | 6 +- tutorials/pdf_pd-neut-tof_Si-NOMAD.py | 6 +- tutorials/pdf_pd-xray_NaCl.py | 8 +- .../quick_single-fit_pd-neut-cwl_LBCO-HRPT.py | 54 +- 281 files changed, 23700 insertions(+), 12917 deletions(-) create mode 100644 docs/architecture/package-structure-full.md create mode 100644 docs/architecture/package-structure-short.md rename src/easydiffraction/analysis/calculators/{calculator_base.py => base.py} (85%) rename src/easydiffraction/analysis/calculators/{calculator_crysfml.py => crysfml.py} (94%) rename src/easydiffraction/analysis/calculators/{calculator_cryspy.py => cryspy.py} (81%) rename src/easydiffraction/analysis/calculators/{calculator_factory.py => factory.py} (64%) rename src/easydiffraction/analysis/calculators/{calculator_pdffit.py => pdffit.py} (87%) rename src/easydiffraction/analysis/{collections => categories}/__init__.py (100%) create mode 100644 src/easydiffraction/analysis/categories/aliases.py create mode 100644 src/easydiffraction/analysis/categories/constraints.py create mode 100644 src/easydiffraction/analysis/categories/joint_fit_experiments.py delete mode 100644 src/easydiffraction/analysis/collections/aliases.py delete mode 100644 src/easydiffraction/analysis/collections/constraints.py delete mode 100644 src/easydiffraction/analysis/collections/joint_fit_experiments.py rename src/easydiffraction/analysis/{fitting => fit_helpers}/__init__.py (100%) rename src/easydiffraction/analysis/{fitting => fit_helpers}/metrics.py (97%) rename src/easydiffraction/analysis/{fitting/results.py => fit_helpers/reporting.py} (63%) rename src/easydiffraction/analysis/{fitting/progress_tracker.py => fit_helpers/tracking.py} (82%) rename src/easydiffraction/analysis/{minimization.py => fitting.py} (87%) rename src/easydiffraction/analysis/minimizers/{minimizer_base.py => base.py} (66%) rename src/easydiffraction/analysis/minimizers/{minimizer_dfols.py => dfols.py} (91%) rename src/easydiffraction/analysis/minimizers/{minimizer_factory.py => factory.py} (94%) rename src/easydiffraction/analysis/minimizers/{minimizer_lmfit.py => lmfit.py} (91%) create mode 100644 src/easydiffraction/core/category.py create mode 100644 src/easydiffraction/core/collection.py create mode 100644 src/easydiffraction/core/datablock.py create mode 100644 src/easydiffraction/core/diagnostic.py create mode 100644 src/easydiffraction/core/factory.py create mode 100644 src/easydiffraction/core/guard.py create mode 100644 src/easydiffraction/core/identity.py delete mode 100644 src/easydiffraction/core/objects.py create mode 100644 src/easydiffraction/core/parameters.py create mode 100644 src/easydiffraction/core/validation.py rename src/easydiffraction/experiments/{collections => categories}/__init__.py (100%) rename src/easydiffraction/experiments/{components => categories/background}/__init__.py (100%) create mode 100644 src/easydiffraction/experiments/categories/background/base.py create mode 100644 src/easydiffraction/experiments/categories/background/chebyshev.py create mode 100644 src/easydiffraction/experiments/categories/background/enums.py create mode 100644 src/easydiffraction/experiments/categories/background/factory.py create mode 100644 src/easydiffraction/experiments/categories/background/line_segment.py create mode 100644 src/easydiffraction/experiments/categories/excluded_regions.py create mode 100644 src/easydiffraction/experiments/categories/experiment_type.py rename src/easydiffraction/{sample_models/collections => experiments/categories/instrument}/__init__.py (100%) create mode 100644 src/easydiffraction/experiments/categories/instrument/base.py create mode 100644 src/easydiffraction/experiments/categories/instrument/cwl.py create mode 100644 src/easydiffraction/experiments/categories/instrument/factory.py create mode 100644 src/easydiffraction/experiments/categories/instrument/tof.py create mode 100644 src/easydiffraction/experiments/categories/linked_phases.py rename src/easydiffraction/{sample_models/components => experiments/categories/peak}/__init__.py (100%) create mode 100644 src/easydiffraction/experiments/categories/peak/base.py create mode 100644 src/easydiffraction/experiments/categories/peak/cwl.py create mode 100644 src/easydiffraction/experiments/categories/peak/cwl_mixins.py create mode 100644 src/easydiffraction/experiments/categories/peak/factory.py create mode 100644 src/easydiffraction/experiments/categories/peak/tof.py create mode 100644 src/easydiffraction/experiments/categories/peak/tof_mixins.py create mode 100644 src/easydiffraction/experiments/categories/peak/total.py create mode 100644 src/easydiffraction/experiments/categories/peak/total_mixins.py delete mode 100644 src/easydiffraction/experiments/collections/background.py delete mode 100644 src/easydiffraction/experiments/collections/excluded_regions.py delete mode 100644 src/easydiffraction/experiments/collections/linked_phases.py delete mode 100644 src/easydiffraction/experiments/components/experiment_type.py delete mode 100644 src/easydiffraction/experiments/components/instrument.py delete mode 100644 src/easydiffraction/experiments/components/peak.py delete mode 100644 src/easydiffraction/experiments/datastore.py create mode 100644 src/easydiffraction/experiments/datastore/__init__.py create mode 100644 src/easydiffraction/experiments/datastore/base.py create mode 100644 src/easydiffraction/experiments/datastore/factory.py create mode 100644 src/easydiffraction/experiments/datastore/pd.py create mode 100644 src/easydiffraction/experiments/datastore/sc.py delete mode 100644 src/easydiffraction/experiments/experiment.py create mode 100644 src/easydiffraction/experiments/experiment/__init__.py create mode 100644 src/easydiffraction/experiments/experiment/base.py create mode 100644 src/easydiffraction/experiments/experiment/bragg_pd.py create mode 100644 src/easydiffraction/experiments/experiment/bragg_sc.py create mode 100644 src/easydiffraction/experiments/experiment/enums.py create mode 100644 src/easydiffraction/experiments/experiment/factory.py create mode 100644 src/easydiffraction/experiments/experiment/instrument_mixin.py create mode 100644 src/easydiffraction/experiments/experiment/total_pd.py create mode 100644 src/easydiffraction/io/cif/handler.py create mode 100644 src/easydiffraction/io/cif/serialize.py create mode 100644 src/easydiffraction/project/__init__.py rename src/easydiffraction/{ => project}/project.py (59%) create mode 100644 src/easydiffraction/project/project_info.py create mode 100644 src/easydiffraction/sample_models/categories/__init__.py create mode 100644 src/easydiffraction/sample_models/categories/atom_sites.py create mode 100644 src/easydiffraction/sample_models/categories/cell.py create mode 100644 src/easydiffraction/sample_models/categories/space_group.py delete mode 100644 src/easydiffraction/sample_models/collections/atom_sites.py delete mode 100644 src/easydiffraction/sample_models/components/cell.py delete mode 100644 src/easydiffraction/sample_models/components/space_group.py create mode 100644 src/easydiffraction/sample_models/sample_model/__init__.py rename src/easydiffraction/sample_models/{sample_model.py => sample_model/base.py} (57%) create mode 100644 src/easydiffraction/sample_models/sample_model/factory.py create mode 100644 src/easydiffraction/summary/__init__.py rename src/easydiffraction/{ => summary}/summary.py (86%) delete mode 100644 src/easydiffraction/utils/decorators.py create mode 100644 src/easydiffraction/utils/logging.py delete mode 100644 tests/unit/analysis/calculators/test_calculator_base.py delete mode 100644 tests/unit/analysis/calculators/test_calculator_cryspy.py delete mode 100644 tests/unit/analysis/calculators/test_calculator_factory.py delete mode 100644 tests/unit/analysis/collections/test_joint_fit_experiment.py delete mode 100644 tests/unit/analysis/minimizers/test_fitting_progress_tracker.py delete mode 100644 tests/unit/analysis/minimizers/test_minimizer_base.py delete mode 100644 tests/unit/analysis/minimizers/test_minimizer_dfols.py delete mode 100644 tests/unit/analysis/minimizers/test_minimizer_factory.py delete mode 100644 tests/unit/analysis/minimizers/test_minimizer_lmfit.py delete mode 100644 tests/unit/analysis/test_analysis.py delete mode 100644 tests/unit/analysis/test_minimization.py delete mode 100644 tests/unit/analysis/test_reliability_factors.py delete mode 100644 tests/unit/core/test_objects.py delete mode 100644 tests/unit/core/test_singletons.py create mode 100644 tests/unit/easydiffraction/analysis/calculators/test_base.py create mode 100644 tests/unit/easydiffraction/analysis/calculators/test_crysfml.py create mode 100644 tests/unit/easydiffraction/analysis/calculators/test_cryspy.py create mode 100644 tests/unit/easydiffraction/analysis/calculators/test_factory.py create mode 100644 tests/unit/easydiffraction/analysis/calculators/test_pdffit.py create mode 100644 tests/unit/easydiffraction/analysis/categories/test_aliases.py create mode 100644 tests/unit/easydiffraction/analysis/categories/test_constraints.py create mode 100644 tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py create mode 100644 tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py create mode 100644 tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py create mode 100644 tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py create mode 100644 tests/unit/easydiffraction/analysis/minimizers/test_base.py create mode 100644 tests/unit/easydiffraction/analysis/minimizers/test_dfols.py create mode 100644 tests/unit/easydiffraction/analysis/minimizers/test_factory.py create mode 100644 tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py create mode 100644 tests/unit/easydiffraction/analysis/test_analysis.py create mode 100644 tests/unit/easydiffraction/analysis/test_analysis_access_params.py create mode 100644 tests/unit/easydiffraction/analysis/test_analysis_show_empty.py create mode 100644 tests/unit/easydiffraction/analysis/test_calculation.py create mode 100644 tests/unit/easydiffraction/analysis/test_fitting.py create mode 100644 tests/unit/easydiffraction/core/test_category.py create mode 100644 tests/unit/easydiffraction/core/test_collection.py create mode 100644 tests/unit/easydiffraction/core/test_datablock.py create mode 100644 tests/unit/easydiffraction/core/test_diagnostic.py create mode 100644 tests/unit/easydiffraction/core/test_factory.py create mode 100644 tests/unit/easydiffraction/core/test_guard.py create mode 100644 tests/unit/easydiffraction/core/test_identity.py create mode 100644 tests/unit/easydiffraction/core/test_parameters.py create mode 100644 tests/unit/easydiffraction/core/test_singletons.py create mode 100644 tests/unit/easydiffraction/core/test_validation.py create mode 100644 tests/unit/easydiffraction/crystallography/test_crystallography.py create mode 100644 tests/unit/easydiffraction/crystallography/test_space_groups.py create mode 100644 tests/unit/easydiffraction/experiments/categories/background/test_base.py create mode 100644 tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py create mode 100644 tests/unit/easydiffraction/experiments/categories/background/test_enums.py create mode 100644 tests/unit/easydiffraction/experiments/categories/background/test_factory.py create mode 100644 tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py create mode 100644 tests/unit/easydiffraction/experiments/categories/instrument/test_base.py create mode 100644 tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py create mode 100644 tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py create mode 100644 tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py create mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_base.py create mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py create mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py create mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_factory.py create mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_tof.py create mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py create mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_total.py create mode 100644 tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py create mode 100644 tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py create mode 100644 tests/unit/easydiffraction/experiments/categories/test_experiment_type.py create mode 100644 tests/unit/easydiffraction/experiments/categories/test_linked_phases.py create mode 100644 tests/unit/easydiffraction/experiments/datastore/test_base.py create mode 100644 tests/unit/easydiffraction/experiments/datastore/test_factory.py create mode 100644 tests/unit/easydiffraction/experiments/datastore/test_pd.py create mode 100644 tests/unit/easydiffraction/experiments/datastore/test_sc.py create mode 100644 tests/unit/easydiffraction/experiments/experiment/test_base.py create mode 100644 tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py create mode 100644 tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py create mode 100644 tests/unit/easydiffraction/experiments/experiment/test_enums.py create mode 100644 tests/unit/easydiffraction/experiments/experiment/test_factory.py create mode 100644 tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py create mode 100644 tests/unit/easydiffraction/experiments/experiment/test_total_pd.py create mode 100644 tests/unit/easydiffraction/experiments/test_experiments.py create mode 100644 tests/unit/easydiffraction/io/cif/test_handler.py create mode 100644 tests/unit/easydiffraction/io/cif/test_serialize.py create mode 100644 tests/unit/easydiffraction/io/cif/test_serialize_more.py create mode 100644 tests/unit/easydiffraction/plotting/plotters/test_plotter_ascii.py create mode 100644 tests/unit/easydiffraction/plotting/plotters/test_plotter_base.py create mode 100644 tests/unit/easydiffraction/plotting/plotters/test_plotter_plotly.py create mode 100644 tests/unit/easydiffraction/plotting/test_plotting.py create mode 100644 tests/unit/easydiffraction/project/test_project.py create mode 100644 tests/unit/easydiffraction/project/test_project_d_spacing.py create mode 100644 tests/unit/easydiffraction/project/test_project_info.py create mode 100644 tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py create mode 100644 tests/unit/easydiffraction/project/test_project_save.py create mode 100644 tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py create mode 100644 tests/unit/easydiffraction/sample_models/categories/test_cell.py create mode 100644 tests/unit/easydiffraction/sample_models/categories/test_space_group.py create mode 100644 tests/unit/easydiffraction/sample_models/sample_model/test_base.py create mode 100644 tests/unit/easydiffraction/sample_models/sample_model/test_factory.py create mode 100644 tests/unit/easydiffraction/sample_models/test_sample_models.py create mode 100644 tests/unit/easydiffraction/summary/test_summary.py create mode 100644 tests/unit/easydiffraction/summary/test_summary_details.py create mode 100644 tests/unit/easydiffraction/test___init__.py create mode 100644 tests/unit/easydiffraction/test___main__.py create mode 100644 tests/unit/easydiffraction/utils/test_formatting.py create mode 100644 tests/unit/easydiffraction/utils/test_logging.py create mode 100644 tests/unit/easydiffraction/utils/test_utils.py delete mode 100644 tests/unit/experiments/collections/test_background.py delete mode 100644 tests/unit/experiments/collections/test_datastore.py delete mode 100644 tests/unit/experiments/collections/test_linked_phases.py delete mode 100644 tests/unit/experiments/components/test_experiment_type.py delete mode 100644 tests/unit/experiments/components/test_instrument.py delete mode 100644 tests/unit/experiments/components/test_peak.py delete mode 100644 tests/unit/experiments/test_experiment.py delete mode 100644 tests/unit/experiments/test_experiments.py delete mode 100644 tests/unit/sample_models/collections/test_atom_sites.py delete mode 100644 tests/unit/sample_models/components/test_cell.py delete mode 100644 tests/unit/sample_models/components/test_space_group.py delete mode 100644 tests/unit/sample_models/test_sample_models.py delete mode 100644 tests/unit/test_project.py delete mode 100644 tests/unit/test_symmetry_lookup_table.py create mode 100644 tmp/Untitled.ipynb create mode 100644 tmp/Untitled1.ipynb rename {tutorials-drafts => tmp}/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py (71%) rename {tutorials-drafts => tmp}/data/DREAM_mantle_bc240_nist_cif.xye (100%) rename {tutorials-drafts => tmp}/data/DREAM_mantle_bc240_nist_cif_2.xye (100%) rename {tutorials-drafts => tmp}/data/DREAM_mantle_bc240_nist_nc.xye (100%) rename {tutorials-drafts => tmp}/data/DREAM_mantle_bc240_nist_nc_2.xye (100%) rename {tutorials-drafts => tmp}/data/Si_mp-149_symmetrized_mcstas.cif (100%) create mode 100644 tmp/data/hrpt_lbco.xye create mode 100644 tmp/data/lbco.cif create mode 100644 tmp/generate_overview_mermaid.py rename {tutorials-drafts => tmp}/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye (100%) create mode 100644 tmp/short.py create mode 100644 tmp/short2.py create mode 100644 tmp/short3.py create mode 100644 tmp/short5.py create mode 100644 tmp/short6.py create mode 100644 tmp/short7.py rename {tutorials-drafts => tmp}/test_single-fit_pd-neut-tof_Si-DREAM_nc.py (59%) create mode 100644 tools/gen_tests_scaffold.py create mode 100644 tools/generate_package_docs.py create mode 100644 tools/naming_check.py create mode 100644 tutorials/data/lbco.cif diff --git a/.badgery.yaml b/.badgery.yaml index f0fc4e74..f22752fa 100644 --- a/.badgery.yaml +++ b/.badgery.yaml @@ -52,6 +52,7 @@ cards: - group: Coverage type: codecov title: Unit test coverage (Codecov) + flag: unittests enabled: true - group: Coverage diff --git a/.github/workflows/dashboard.yaml b/.github/workflows/dashboard.yaml index 733fac98..1076f349 100644 --- a/.github/workflows/dashboard.yaml +++ b/.github/workflows/dashboard.yaml @@ -41,33 +41,34 @@ jobs: - name: Install and setup development dependencies shell: bash - run: pixi run dev + run: | + pixi run dev + pixi add --pypi --git https://github.com/enhantica/badgery badgery - name: Run docstring coverage and code complexity/maintainability checks run: | for BRANCH in ${{ env.DEFAULT_BRANCH }} ${{ env.DEVELOP_BRANCH }} ${{ env.CI_BRANCH }}; do echo "=== Processing branch $BRANCH ===" - if [ -d "../worktree-$BRANCH" ]; then + if [ -d "../$BRANCH" ]; then echo "Branch $BRANCH already processed, skipping" continue fi - git worktree add ../worktree-$BRANCH origin/$BRANCH + git worktree add ../$BRANCH origin/$BRANCH mkdir -p reports/$BRANCH echo "Docstring coverage for branch $BRANCH" - pixi run interrogate -c pyproject.toml --fail-under=0 ../worktree-$BRANCH/src > reports/$BRANCH/coverage-docstring.txt + pixi run interrogate -c pyproject.toml --fail-under=0 ../$BRANCH/src > reports/$BRANCH/coverage-docstring.txt echo "Cyclomatic complexity for branch $BRANCH" - pixi run radon cc -s -j ../worktree-$BRANCH/src --exclude ../worktree-$BRANCH/src/easydiffraction/crystallography/space_group_lookup_table.py > reports/$BRANCH/cyclomatic-complexity.json + pixi run radon cc -s -j ../$BRANCH/src > reports/$BRANCH/cyclomatic-complexity.json echo "Maintainability index for branch $BRANCH" - pixi run radon mi -j ../worktree-$BRANCH/src --exclude ../worktree-$BRANCH/src/easydiffraction/crystallography/space_group_lookup_table.py > reports/$BRANCH/maintainability-index.json + pixi run radon mi -j ../$BRANCH/src > reports/$BRANCH/maintainability-index.json echo "Raw metrics for branch $BRANCH" - pixi run radon raw -s -j ../worktree-$BRANCH/src --exclude ../worktree-$BRANCH/src/easydiffraction/crystallography/space_group_lookup_table.py > reports/$BRANCH/raw-metrics.json + pixi run radon raw -s -j ../$BRANCH/src > reports/$BRANCH/raw-metrics.json done - name: Generate dashboard HTML - run: | - pixi add --pypi --git https://github.com/enhantica/badgery badgery - pixi run pip show badgery - pixi run python -m badgery --config .badgery.yaml --repo ${{ github.repository }} --branch ${{ env.CI_BRANCH }} --output index.html + run: > + pixi run python -m badgery --config .badgery.yaml --repo ${{ + github.repository }} --branch ${{ env.CI_BRANCH }} --output index.html - name: Prepare publish directory run: | diff --git a/.github/workflows/pypi-test.yaml b/.github/workflows/pypi-test.yaml index f7f25213..8cb5323c 100644 --- a/.github/workflows/pypi-test.yaml +++ b/.github/workflows/pypi-test.yaml @@ -20,6 +20,7 @@ env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} jobs: + # Job 1: Test installation from PyPI on multiple OS pypi-package-tests: strategy: matrix: @@ -54,10 +55,8 @@ jobs: - name: Create the environment and install dependencies run: pixi install - - name: Install package from PyPI with dev and visualization extras - run: | - pixi add --pypi "easydiffraction[dev,visualization]" - pixi run easydiffraction --version + - name: Install package from PyPI with all extras + run: pixi add --pypi "easydiffraction[all]" - name: Run unit tests to verify the installation run: pixi run unit-tests @@ -70,8 +69,31 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - pixi run tutorials-list - pixi run tutorials-fetch + pixi run easydiffraction --version + pixi run easydiffraction list-tutorials + pixi run easydiffraction fetch-tutorials - name: Test tutorials as notebooks run: pixi run notebook-tests + + # Job 2: Trigger dashboard build + dashboard-build-trigger: + needs: pypi-package-tests + + runs-on: ubuntu-latest + + steps: + - name: Check-out repository + uses: actions/checkout@v5 + + - name: Trigger dashboard build + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: "dashboard.yaml", + ref: "${{ env.CI_BRANCH }}" + }); diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7868d738..ec185ee8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -147,6 +147,10 @@ jobs: shell: bash run: pixi remove --pypi easydiffraction + - name: Remove Python cache files before uploading + shell: bash + run: pixi run clean-pycache + # More than one file/dir need to be specified in 'path', to preserve the # structure of the dist/ directory, not only its contents. - name: Upload Python package for the next job @@ -156,6 +160,7 @@ jobs: path: | dist/ tests/ + pytest.ini pixi.toml pixi.lock if-no-files-found: 'error' diff --git a/.github/workflows/tutorial-tests.yaml b/.github/workflows/tutorial-tests.yaml index f5d4af8d..c8fc6448 100644 --- a/.github/workflows/tutorial-tests.yaml +++ b/.github/workflows/tutorial-tests.yaml @@ -22,7 +22,12 @@ concurrency: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + jobs: + # Job 1: Test tutorials as scripts and notebooks on multiple OS tutorial-tests: strategy: fail-fast: false @@ -60,3 +65,25 @@ jobs: - name: Test tutorials as notebooks shell: bash run: pixi run notebook-tests + + # Job 2: Trigger dashboard build + dashboard-build-trigger: + needs: tutorial-tests + + runs-on: ubuntu-latest + + steps: + - name: Check-out repository + uses: actions/checkout@v5 + + - name: Trigger dashboard build + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: "dashboard.yaml", + ref: "${{ env.CI_BRANCH }}" + }); diff --git a/.gitignore b/.gitignore index 02bb32e1..2c43d2b8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,18 @@ __pycache__ .coverage .pyc +# PyTest +.pytest_cache + +# MyPy +.mypy_cache + # Pixi .pixi +# Ruff +.ruff_cache + # Jupyter Notebooks .ipynb_checkpoints diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3d471cd..87117c70 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,9 +49,9 @@ repos: pass_filenames: false stages: [pre-push] - - id: pixi-unit-tests - name: pixi run unit-tests - entry: pixi run unit-tests - language: system - pass_filenames: false - stages: [pre-push] + #- id: pixi-unit-tests + # name: pixi run unit-tests + # entry: pixi run unit-tests + # language: system + # pass_filenames: false + # stages: [pre-push] diff --git a/.prettierignore b/.prettierignore index f599168f..4bd61611 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,6 @@ # Pixi +.pixi pixi.lock + +# MyPy +.mypy_cache diff --git a/docs/architecture/package-structure-full.md b/docs/architecture/package-structure-full.md new file mode 100644 index 00000000..312b6c18 --- /dev/null +++ b/docs/architecture/package-structure-full.md @@ -0,0 +1,245 @@ +# Package Structure (full) + +``` +📦 easydiffraction +├── 📁 analysis +│ ├── 📁 calculators +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class CalculatorBase +│ │ ├── 📄 crysfml.py +│ │ │ └── 🏷️ class CrysfmlCalculator +│ │ ├── 📄 cryspy.py +│ │ │ └── 🏷️ class CryspyCalculator +│ │ ├── 📄 factory.py +│ │ │ └── 🏷️ class CalculatorFactory +│ │ └── 📄 pdffit.py +│ │ └── 🏷️ class PdffitCalculator +│ ├── 📁 categories +│ │ ├── 📄 __init__.py +│ │ ├── 📄 aliases.py +│ │ │ ├── 🏷️ class Alias +│ │ │ └── 🏷️ class Aliases +│ │ ├── 📄 constraints.py +│ │ │ ├── 🏷️ class Constraint +│ │ │ └── 🏷️ class Constraints +│ │ └── 📄 joint_fit_experiments.py +│ │ ├── 🏷️ class JointFitExperiment +│ │ └── 🏷️ class JointFitExperiments +│ ├── 📁 fit_helpers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 metrics.py +│ │ ├── 📄 reporting.py +│ │ │ └── 🏷️ class FitResults +│ │ └── 📄 tracking.py +│ │ └── 🏷️ class FitProgressTracker +│ ├── 📁 minimizers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class MinimizerBase +│ │ ├── 📄 dfols.py +│ │ │ └── 🏷️ class DfolsMinimizer +│ │ ├── 📄 factory.py +│ │ │ └── 🏷️ class MinimizerFactory +│ │ └── 📄 lmfit.py +│ │ └── 🏷️ class LmfitMinimizer +│ ├── 📄 __init__.py +│ ├── 📄 analysis.py +│ │ └── 🏷️ class Analysis +│ ├── 📄 calculation.py +│ │ └── 🏷️ class Calculator +│ └── 📄 fitting.py +│ └── 🏷️ class Fitter +├── 📁 core +│ ├── 📄 __init__.py +│ ├── 📄 category.py +│ │ ├── 🏷️ class CategoryItem +│ │ └── 🏷️ class CategoryCollection +│ ├── 📄 collection.py +│ │ └── 🏷️ class CollectionBase +│ ├── 📄 datablock.py +│ │ ├── 🏷️ class DatablockItem +│ │ └── 🏷️ class DatablockCollection +│ ├── 📄 diagnostic.py +│ │ └── 🏷️ class Diagnostics +│ ├── 📄 factory.py +│ │ └── 🏷️ class FactoryBase +│ ├── 📄 guard.py +│ │ └── 🏷️ class GuardedBase +│ ├── 📄 identity.py +│ │ └── 🏷️ class Identity +│ ├── 📄 parameters.py +│ │ ├── 🏷️ class GenericDescriptorBase +│ │ ├── 🏷️ class GenericStringDescriptor +│ │ ├── 🏷️ class GenericNumericDescriptor +│ │ ├── 🏷️ class GenericParameter +│ │ ├── 🏷️ class StringDescriptor +│ │ ├── 🏷️ class NumericDescriptor +│ │ └── 🏷️ class Parameter +│ ├── 📄 singletons.py +│ │ ├── 🏷️ class SingletonBase +│ │ ├── 🏷️ class UidMapHandler +│ │ └── 🏷️ class ConstraintsHandler +│ └── 📄 validation.py +│ ├── 🏷️ class DataTypes +│ ├── 🏷️ class ValidationStage +│ ├── 🏷️ class ValidatorBase +│ ├── 🏷️ class TypeValidator +│ ├── 🏷️ class RangeValidator +│ ├── 🏷️ class MembershipValidator +│ ├── 🏷️ class RegexValidator +│ └── 🏷️ class AttributeSpec +├── 📁 crystallography +│ ├── 📄 __init__.py +│ ├── 📄 crystallography.py +│ └── 📄 space_groups.py +├── 📁 experiments +│ ├── 📁 categories +│ │ ├── 📁 background +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ └── 🏷️ class BackgroundBase +│ │ │ ├── 📄 chebyshev.py +│ │ │ │ ├── 🏷️ class PolynomialTerm +│ │ │ │ └── 🏷️ class ChebyshevPolynomialBackground +│ │ │ ├── 📄 enums.py +│ │ │ │ └── 🏷️ class BackgroundTypeEnum +│ │ │ ├── 📄 factory.py +│ │ │ │ └── 🏷️ class BackgroundFactory +│ │ │ └── 📄 line_segment.py +│ │ │ ├── 🏷️ class LineSegment +│ │ │ └── 🏷️ class LineSegmentBackground +│ │ ├── 📁 instrument +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ └── 🏷️ class InstrumentBase +│ │ │ ├── 📄 cwl.py +│ │ │ │ └── 🏷️ class CwlInstrument +│ │ │ ├── 📄 factory.py +│ │ │ │ └── 🏷️ class InstrumentFactory +│ │ │ └── 📄 tof.py +│ │ │ └── 🏷️ class TofInstrument +│ │ ├── 📁 peak +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ │ └── 🏷️ class PeakBase +│ │ │ ├── 📄 cwl.py +│ │ │ │ ├── 🏷️ class CwlPseudoVoigt +│ │ │ │ ├── 🏷️ class CwlSplitPseudoVoigt +│ │ │ │ └── 🏷️ class CwlThompsonCoxHastings +│ │ │ ├── 📄 cwl_mixins.py +│ │ │ │ ├── 🏷️ class CwlBroadeningMixin +│ │ │ │ ├── 🏷️ class EmpiricalAsymmetryMixin +│ │ │ │ └── 🏷️ class FcjAsymmetryMixin +│ │ │ ├── 📄 factory.py +│ │ │ │ └── 🏷️ class PeakFactory +│ │ │ ├── 📄 tof.py +│ │ │ │ ├── 🏷️ class TofPseudoVoigt +│ │ │ │ ├── 🏷️ class TofPseudoVoigtIkedaCarpenter +│ │ │ │ └── 🏷️ class TofPseudoVoigtBackToBack +│ │ │ ├── 📄 tof_mixins.py +│ │ │ │ ├── 🏷️ class TofBroadeningMixin +│ │ │ │ └── 🏷️ class IkedaCarpenterAsymmetryMixin +│ │ │ ├── 📄 total.py +│ │ │ │ └── 🏷️ class TotalGaussianDampedSinc +│ │ │ └── 📄 total_mixins.py +│ │ │ └── 🏷️ class TotalBroadeningMixin +│ │ ├── 📄 __init__.py +│ │ ├── 📄 excluded_regions.py +│ │ │ ├── 🏷️ class ExcludedRegion +│ │ │ └── 🏷️ class ExcludedRegions +│ │ ├── 📄 experiment_type.py +│ │ │ └── 🏷️ class ExperimentType +│ │ └── 📄 linked_phases.py +│ │ ├── 🏷️ class LinkedPhase +│ │ └── 🏷️ class LinkedPhases +│ ├── 📁 datastore +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class DatastoreBase +│ │ ├── 📄 factory.py +│ │ │ └── 🏷️ class DatastoreFactory +│ │ ├── 📄 pd.py +│ │ │ └── 🏷️ class PdDatastore +│ │ └── 📄 sc.py +│ │ └── 🏷️ class ScDatastore +│ ├── 📁 experiment +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ ├── 🏷️ class ExperimentBase +│ │ │ └── 🏷️ class PdExperimentBase +│ │ ├── 📄 bragg_pd.py +│ │ │ └── 🏷️ class BraggPdExperiment +│ │ ├── 📄 bragg_sc.py +│ │ │ └── 🏷️ class BraggScExperiment +│ │ ├── 📄 enums.py +│ │ │ ├── 🏷️ class SampleFormEnum +│ │ │ ├── 🏷️ class ScatteringTypeEnum +│ │ │ ├── 🏷️ class RadiationProbeEnum +│ │ │ ├── 🏷️ class BeamModeEnum +│ │ │ └── 🏷️ class PeakProfileTypeEnum +│ │ ├── 📄 factory.py +│ │ │ └── 🏷️ class ExperimentFactory +│ │ ├── 📄 instrument_mixin.py +│ │ │ └── 🏷️ class InstrumentMixin +│ │ └── 📄 total_pd.py +│ │ └── 🏷️ class TotalPdExperiment +│ ├── 📄 __init__.py +│ └── 📄 experiments.py +│ └── 🏷️ class Experiments +├── 📁 io +│ └── 📁 cif +│ ├── 📄 handler.py +│ │ └── 🏷️ class CifHandler +│ └── 📄 serialize.py +├── 📁 plotting +│ ├── 📁 plotters +│ │ ├── 📄 __init__.py +│ │ ├── 📄 plotter_ascii.py +│ │ │ └── 🏷️ class AsciiPlotter +│ │ ├── 📄 plotter_base.py +│ │ │ └── 🏷️ class PlotterBase +│ │ └── 📄 plotter_plotly.py +│ │ └── 🏷️ class PlotlyPlotter +│ ├── 📄 __init__.py +│ └── 📄 plotting.py +│ ├── 🏷️ class Plotter +│ └── 🏷️ class PlotterFactory +├── 📁 project +│ ├── 📄 __init__.py +│ ├── 📄 project.py +│ │ └── 🏷️ class Project +│ └── 📄 project_info.py +│ └── 🏷️ class ProjectInfo +├── 📁 sample_models +│ ├── 📁 categories +│ │ ├── 📄 __init__.py +│ │ ├── 📄 atom_sites.py +│ │ │ ├── 🏷️ class AtomSite +│ │ │ └── 🏷️ class AtomSites +│ │ ├── 📄 cell.py +│ │ │ └── 🏷️ class Cell +│ │ └── 📄 space_group.py +│ │ └── 🏷️ class SpaceGroup +│ ├── 📁 sample_model +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class SampleModelBase +│ │ └── 📄 factory.py +│ │ └── 🏷️ class SampleModelFactory +│ ├── 📄 __init__.py +│ └── 📄 sample_models.py +│ └── 🏷️ class SampleModels +├── 📁 summary +│ ├── 📄 __init__.py +│ └── 📄 summary.py +│ └── 🏷️ class Summary +├── 📁 utils +│ ├── 📄 __init__.py +│ ├── 📄 formatting.py +│ ├── 📄 logging.py +│ │ └── 🏷️ class Logger +│ └── 📄 utils.py +├── 📄 __init__.py +└── 📄 __main__.py +``` diff --git a/docs/architecture/package-structure-short.md b/docs/architecture/package-structure-short.md new file mode 100644 index 00000000..d664e1b4 --- /dev/null +++ b/docs/architecture/package-structure-short.md @@ -0,0 +1,133 @@ +# Package Structure (short) + +``` +📦 easydiffraction +├── 📁 analysis +│ ├── 📁 calculators +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 crysfml.py +│ │ ├── 📄 cryspy.py +│ │ ├── 📄 factory.py +│ │ └── 📄 pdffit.py +│ ├── 📁 categories +│ │ ├── 📄 __init__.py +│ │ ├── 📄 aliases.py +│ │ ├── 📄 constraints.py +│ │ └── 📄 joint_fit_experiments.py +│ ├── 📁 fit_helpers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 metrics.py +│ │ ├── 📄 reporting.py +│ │ └── 📄 tracking.py +│ ├── 📁 minimizers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 dfols.py +│ │ ├── 📄 factory.py +│ │ └── 📄 lmfit.py +│ ├── 📄 __init__.py +│ ├── 📄 analysis.py +│ ├── 📄 calculation.py +│ └── 📄 fitting.py +├── 📁 core +│ ├── 📄 __init__.py +│ ├── 📄 category.py +│ ├── 📄 collection.py +│ ├── 📄 datablock.py +│ ├── 📄 diagnostic.py +│ ├── 📄 factory.py +│ ├── 📄 guard.py +│ ├── 📄 identity.py +│ ├── 📄 parameters.py +│ ├── 📄 singletons.py +│ └── 📄 validation.py +├── 📁 crystallography +│ ├── 📄 __init__.py +│ ├── 📄 crystallography.py +│ └── 📄 space_groups.py +├── 📁 experiments +│ ├── 📁 categories +│ │ ├── 📁 background +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ ├── 📄 chebyshev.py +│ │ │ ├── 📄 enums.py +│ │ │ ├── 📄 factory.py +│ │ │ └── 📄 line_segment.py +│ │ ├── 📁 instrument +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ ├── 📄 cwl.py +│ │ │ ├── 📄 factory.py +│ │ │ └── 📄 tof.py +│ │ ├── 📁 peak +│ │ │ ├── 📄 __init__.py +│ │ │ ├── 📄 base.py +│ │ │ ├── 📄 cwl.py +│ │ │ ├── 📄 cwl_mixins.py +│ │ │ ├── 📄 factory.py +│ │ │ ├── 📄 tof.py +│ │ │ ├── 📄 tof_mixins.py +│ │ │ ├── 📄 total.py +│ │ │ └── 📄 total_mixins.py +│ │ ├── 📄 __init__.py +│ │ ├── 📄 excluded_regions.py +│ │ ├── 📄 experiment_type.py +│ │ └── 📄 linked_phases.py +│ ├── 📁 datastore +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 factory.py +│ │ ├── 📄 pd.py +│ │ └── 📄 sc.py +│ ├── 📁 experiment +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 bragg_pd.py +│ │ ├── 📄 bragg_sc.py +│ │ ├── 📄 enums.py +│ │ ├── 📄 factory.py +│ │ ├── 📄 instrument_mixin.py +│ │ └── 📄 total_pd.py +│ ├── 📄 __init__.py +│ └── 📄 experiments.py +├── 📁 io +│ └── 📁 cif +│ ├── 📄 handler.py +│ └── 📄 serialize.py +├── 📁 plotting +│ ├── 📁 plotters +│ │ ├── 📄 __init__.py +│ │ ├── 📄 plotter_ascii.py +│ │ ├── 📄 plotter_base.py +│ │ └── 📄 plotter_plotly.py +│ ├── 📄 __init__.py +│ └── 📄 plotting.py +├── 📁 project +│ ├── 📄 __init__.py +│ ├── 📄 project.py +│ └── 📄 project_info.py +├── 📁 sample_models +│ ├── 📁 categories +│ │ ├── 📄 __init__.py +│ │ ├── 📄 atom_sites.py +│ │ ├── 📄 cell.py +│ │ └── 📄 space_group.py +│ ├── 📁 sample_model +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ └── 📄 factory.py +│ ├── 📄 __init__.py +│ └── 📄 sample_models.py +├── 📁 summary +│ ├── 📄 __init__.py +│ └── 📄 summary.py +├── 📁 utils +│ ├── 📄 __init__.py +│ ├── 📄 formatting.py +│ ├── 📄 logging.py +│ └── 📄 utils.py +├── 📄 __init__.py +└── 📄 __main__.py +``` diff --git a/pixi.lock b/pixi.lock index cdbb77db..0b02e764 100644 --- a/pixi.lock +++ b/pixi.lock @@ -9,699 +9,730 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py313h7033f15_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py313hf01b4d8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py313h5d5ffb9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.1-he9a06e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py313h8060acc_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.4.1-heeeca48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py313h07c4f96_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.7-h2b335a9_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py313h843e2db_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/47/807e225343ee50f71132bc6185d62230c8f6950e3e6a6e161ae11eed9495/diffpy.pdffit2-1.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/9e/024450978a674b2f021aa1f46b6fa73823713c7fe8b5d713fbd6defdb157/gemmi-0.7.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl osx-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py313h585f44e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py313h253db18_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py313he20ea1e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py313h03db916_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py313habf4b1d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.1-h3d58e20_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py313h717bdf5_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.4.1-h2e7699b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py313h585f44e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py313h6971d95_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py313h55ae1d7_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.7-h5eba815_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312hb7d603e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py313h66e1e84_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py313h585f44e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py313hcb05632_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/a1/1aec6b9c471074a184c93ffe95fa16593ebd96a017b1a08a4aebed5cd6c6/diffpy.pdffit2-1.5.1-cp313-cp313-macosx_13_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/be/fc5fe58dd76af7127b769b68071dbc32d4b95adc8b58d1d28d42d93c90f2/fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/93/e2/c45cd48ec7cc0f49e182d8a736355a61edf0fc2ac060b90fc822c4fca067/gemmi-0.7.3-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/0d/6af0bb9a45c771ffccd5c4c035c57ac9005e711b1191ddad1dd954187cfe/pycifrw-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313hcdf3177_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py313h755b2b2_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py313hab38a8b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.1-hf598326_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py313ha9b7d5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/d9/e96af817e50310f9891dd67399e8e9fd651b21a7c8be13ed21d04ceaeb61/diffpy.pdffit2-1.5.1-cp313-cp313-macosx_13_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/7b/d0d3b9431642947b5805201fbbbe938a47b70c76685ef1f0cb5f5d7140d6/fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/60/06/6e3a083a02d2a1b7da69dce5538d51b4a83dc308e3ea9e21edcf324e10de/gemmi-0.7.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl win-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py313hfe59770_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py313h927ade5_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh3521513_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyh6be1c34_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py313hfa70ccb_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -710,155 +741,242 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.20-hc70643c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-20.1.8-hfa2b4ca_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py313hb4c8b1a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.4.1-he453025_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.7-hdf00ec1_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312hbb5da91_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py313hfbe8231_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh5737063_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h18a62a1_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h5bddc39_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py313h5fd188c_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/93/8239b7cd3bb4c134295c1c07318c6fac06fe920b0f615787bb67738e867a/diffpy.pdffit2-1.5.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/a9/be7219fc64a6026cc0aded17fa3720f9277001c185434230bd351bf678e6/fonttools-4.59.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/28/38/c5c1b52c16ef70b9e7e0392f6298b93dd17783c3c34a92512825b1617841/gemmi-0.7.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl py311-dev: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -868,691 +986,732 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py311h1ddb823_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py311h5b438cf_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py311hc665b79_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py311h38be061_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.1-he9a06e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.4.1-heeeca48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py311h49ec1c0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.13-h9e4cc4f_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py311h2dc5d0c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h2315fbb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.14-hfe2f287_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py311h902ca64_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py311h49ec1c0_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py311haee01d2_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/8a/3308fe30660a0a585b7bc4b68b216ce6dc63d69fe9cbff9bd97ca411a8f9/diffpy.pdffit2-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/69/95/864726eaa8f9d4e053d0c462e64d5830ec7c599cbdf1db9e40f25ca3972e/fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e5/4e/55e3410500c274a15b44997a14c16cc0f11b4793fbd90c7fc8b009f83a9f/gemmi-0.7.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/21/d4/d461649cafd5137088fb7f8e78fdc6621bb0c4ff2c090a389f68e8edc136/h5py-3.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/29/4a8650a3dcae97fa4f375d46efcb25920d67b512186f8a6788b896062a81/matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8b/ef/0e2ffb30b1f7fbc9a588bd01e3c14a0d96854d09a887e15e30cc19961227/pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/75/35/a44ce3d7c3f52a2a443cae261a05c2affc52fde7f1643974adbef105785f/pycifrw-5.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/03/b5/cacf432e6f1fc9d156eca0560ac61d4355d2181e751ba8c0cd9cb232c8c1/sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/2d/fdb9246d9d32518bda5d90f4b65030b9bf403a935cfe4c36a474846517cb/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl osx-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py311h13e5629_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py311h7b20566_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py311he66fa18_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py311hc651eee_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py311h6eed73b_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.1-h3d58e20_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py311ha3cf9ac_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.4.1-h2e7699b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py311h13e5629_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py311h2f44256_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py311hbc8e8a3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.13-h9ccd52b_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py311ha3cf9ac_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py311h0ab6910_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.14-h3999593_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py311hd3d88a1_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py311h13e5629_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py311h62e9434_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ae/f9/2d6d93fd57ab4726e18a7cdab083772eda8302d682620fbf2aef48322351/aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/c6/5cfa31bb0ac6fd0a1370bdced0640eb8821fe6b10ffd5f00dbe77e4721aa/diffpy.pdffit2-1.5.1-cp311-cp311-macosx_13_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/2a/976f5f9fa3b4dd911dc58d07358467bec20e813d933bc5d3db1a955dd456/fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7c/d1/283c9d103b8b605cc4cdbb8e398d314b01b4bac309be03e19f7cecc5a4d9/gemmi-0.7.3-cp311-cp311-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/61/1b/ad24a8ce846cf0519695c10491e99969d9d203b9632c4fcd5004b1641c2e/h5py-3.14.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/80/d6/5d3665aa44c49005aaacaa68ddea6fcb27345961cd538a98bb0177934ede/matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/7a/59/f3e010879f118c2d400902d2d871c2226cef29b08c09fb8dc41111730400/pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/b6/84364503e0726da4a263e1736d0e1754526d1b1729d0087c680d96345570/pycifrw-5.0.1-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/77/fa7189fe44114658002566c6fe443d3ed0ec1fa782feb72af6ef7fbe98e7/sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/81/15d7c161c9ddf0900b076b55345872ed04ff1ed6a0666e5e94ab44b0163c/sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h3696347_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py311hf719da1_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py311h146a0b8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py311ha59bd64_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py311h267d04e_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.1-hf598326_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py311h4921393_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py311h3696347_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py311hf0763de_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py311hc5b188e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.13-hc22306f_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py311h4921393_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h13abfa4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.14-hec0b533_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py311h1c3fc1a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py311h3696347_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py311h5bb9006_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/89/a6/e1c061b079fed04ffd6777950c82f2e8246fd08b7b3c4f56fdd47f697e5a/aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/26/47f142b99ad0e5d0e1e94beb7d05e6bed0cfd84669c263df382f5b6ce99b/diffpy.pdffit2-1.5.1-cp311-cp311-macosx_13_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f8/53/742fcd750ae0bdc74de4c0ff923111199cc2f90a4ee87aaddad505b6f477/fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl - - pypi: https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/05/78/64628f519ff553a0d8101dd3852b87441caa69c6617250d48b3c6bad9422/gemmi-0.7.3-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/5b/a066e459ca48b47cc73a5c668e9924d9619da9e3c500d9fb9c29c03858ec/h5py-3.14.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8c/af/30ddefe19ca67eebd70047dabf50f899eaff6f3c5e6a1a7edaecaf63f794/matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/38/18/48f10f1cc5c397af59571d638d211f494dba481f449c19adbd282aa8f4ca/pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/5c/b999ea3e64981018d52846b9b69193fa581a70cd255912cb6962a33a666a/pycifrw-5.0.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/99/ea/92ac27f2fbc2e6c1766bb807084ca455265707e041ba027c09c17d697867/sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/d5/4abd13b245c7d91bdf131d4916fd9e96a584dac74215f8b5bc945206a974/sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl win-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py311h3e6a449_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py311h3485c13_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.13-py311hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py311h5dfdfe8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh3521513_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyh6be1c34_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py311h1ea47a8_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -1560,155 +1719,243 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.1-default_h64bd3f2_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.20-hc70643c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-20.1.8-hfa2b4ca_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py311h5082efb_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.4.1-he453025_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py311h3485c13_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.13-h3f84c4b_0_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py311h5082efb_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hb77b9c8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py311hf51aa87_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh5737063_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.14-h30ce641_1_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h18a62a1_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py311h3485c13_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h5bddc39_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py311hf893f09_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3e/bd/485d98b372a2cd6998484a93ddd401ec6b6031657661c36846a10e2a1f6e/aiohttp-3.13.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/59/b7/5464cb1ffcf0cfaf34b8fb1604c23982855d453c987ccb18cb273b547e9f/diffpy.pdffit2-1.5.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/de/35d839aa69db737a3f9f3a45000ca24721834d40118652a5775d5eca8ebb/fonttools-4.59.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/f4/6d50077a2bf4449fab360e85790db4031be1545de77cce239a215866d34d/gemmi-0.7.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/db/0c/6c3f879a0f8e891625817637fad902da6e764e36919ed091dc77529004ac/h5py-3.14.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fc/8e/0a18d6d7d2d0a2e66585032a760d13662e5250c784d53ad50434e9560991/matplotlib-3.10.6-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a7/e7/ae86261695b6c8a36d6a4c8d5f9b9ede8248510d689a2f379a18354b37d7/pandas-2.3.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/58/e60915c59f4adcbd97af30047694978127d63139ae05a0cf987c6f2e90f9/pycifrw-5.0.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/13/744a32ebe3b4a7a9c7ea4e57babae7aa22070d47acf330d8e5a1359607f1/sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/23/907193c2f4d680aedbfbdf7bf24c13925e3c7c292e813326c1b84a0b878e/sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl py313-dev: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -1718,699 +1965,730 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py313h7033f15_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py313hf01b4d8_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py313h5d5ffb9_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.1-he9a06e4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py313h8060acc_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.4.1-heeeca48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py313h07c4f96_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.7-h2b335a9_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py313h843e2db_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/47/807e225343ee50f71132bc6185d62230c8f6950e3e6a6e161ae11eed9495/diffpy.pdffit2-1.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/9e/024450978a674b2f021aa1f46b6fa73823713c7fe8b5d713fbd6defdb157/gemmi-0.7.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl osx-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py313h585f44e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py313h253db18_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py313he20ea1e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py313h03db916_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py313habf4b1d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.1-h3d58e20_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libmpdec-4.0.0-h6e16a3a_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.50.4-h39a8b3b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.51.0-h58003a5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py313h717bdf5_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.4.1-h2e7699b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py313h585f44e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py313h6971d95_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py313h55ae1d7_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.7-h5eba815_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312hb7d603e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py313h66e1e84_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py313h585f44e_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py313hcb05632_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ed/a1/1aec6b9c471074a184c93ffe95fa16593ebd96a017b1a08a4aebed5cd6c6/diffpy.pdffit2-1.5.1-cp313-cp313-macosx_13_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/be/fc5fe58dd76af7127b769b68071dbc32d4b95adc8b58d1d28d42d93c90f2/fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/93/e2/c45cd48ec7cc0f49e182d8a736355a61edf0fc2ac060b90fc822c4fca067/gemmi-0.7.3-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/87/0d/6af0bb9a45c771ffccd5c4c035c57ac9005e711b1191ddad1dd954187cfe/pycifrw-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313hcdf3177_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py313h755b2b2_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py313hab38a8b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.1-hf598326_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h5505292_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.50.4-h4237e3c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py313ha9b7d5b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/d9/e96af817e50310f9891dd67399e8e9fd651b21a7c8be13ed21d04ceaeb61/diffpy.pdffit2-1.5.1-cp313-cp313-macosx_13_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/7b/d0d3b9431642947b5805201fbbbe938a47b70c76685ef1f0cb5f5d7140d6/fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/60/06/6e3a083a02d2a1b7da69dce5538d51b4a83dc308e3ea9e21edcf324e10de/gemmi-0.7.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl win-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py313hfe59770_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py313h927ade5_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh3521513_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyh6be1c34_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py313hfa70ccb_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -2419,155 +2697,242 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.1-h2466b09_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.20-hc70643c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.50.4-hf5d6505_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-20.1.8-hfa2b4ca_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py313hb4c8b1a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.4.1-he453025_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.7-hdf00ec1_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312hbb5da91_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py313hfbe8231_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh5737063_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h18a62a1_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py313h5ea7bf4_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h5bddc39_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py313h5fd188c_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/93/8239b7cd3bb4c134295c1c07318c6fac06fe920b0f615787bb67738e867a/diffpy.pdffit2-1.5.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/52/39914bf42bb01901c91781def35c1aeffa431a59299e9748c0cfae3e5493/diffpy_structure-3.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c8/f0f4ab7fd08950d5358579364a0a9b9198bf03e179a4fcceae4b353be32e/diffpy_utils-3.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ea/a9/be7219fc64a6026cc0aded17fa3720f9277001c185434230bd351bf678e6/fonttools-4.59.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/28/38/c5c1b52c16ef70b9e7e0392f6298b93dd17783c3c34a92512825b1617841/gemmi-0.7.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/b7/339b9ed180c28418f3c5c425f341759ce3722b61cc54f8c20918a034a1d5/mpld3-0.5.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ab/2e/bdaf2039f1bac918d013f560f3dc3ac59974912178b24c8bf94d059fba2e/pixi_kernel-0.6.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 @@ -2590,26 +2955,15 @@ packages: purls: [] size: 23621 timestamp: 1650670423406 -- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 - md5: aaa2a381ccc56eac91d63b6c1240312f - depends: - - cpython - - python-gil - license: MIT - license_family: MIT - purls: [] - size: 8191 - timestamp: 1744137672556 - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl name: aiohappyeyeballs version: 2.6.1 sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl name: aiohttp - version: 3.12.15 - sha256: edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d + version: 3.13.0 + sha256: 059978d2fddc462e9211362cbc8446747ecd930537fa559d3d25c256f032ff54 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2622,11 +2976,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp - version: 3.12.15 - sha256: 1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84 + version: 3.13.0 + sha256: d169c47e40c911f728439da853b6fd06da83761012e6e76f11cb62cddae7282b requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2639,11 +2994,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/3e/bd/485d98b372a2cd6998484a93ddd401ec6b6031657661c36846a10e2a1f6e/aiohttp-3.13.0-cp311-cp311-win_amd64.whl name: aiohttp - version: 3.12.15 - sha256: 3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4 + version: 3.13.0 + sha256: 0f19f7798996d4458c669bd770504f710014926e9970f4729cf55853ae200469 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2656,11 +3012,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp - version: 3.12.15 - sha256: 3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9 + version: 3.13.0 + sha256: cc6d5fc5edbfb8041d9607f6a417997fa4d02de78284d386bea7ab767b5ea4f3 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2673,11 +3030,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/89/a6/e1c061b079fed04ffd6777950c82f2e8246fd08b7b3c4f56fdd47f697e5a/aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl name: aiohttp - version: 3.12.15 - sha256: 010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe + version: 3.13.0 + sha256: 3e6a38366f7f0d0f6ed7a1198055150c52fda552b107dad4785c0852ad7685d1 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2690,11 +3048,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ae/f9/2d6d93fd57ab4726e18a7cdab083772eda8302d682620fbf2aef48322351/aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl name: aiohttp - version: 3.12.15 - sha256: 5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d + version: 3.13.0 + sha256: 4696665b2713021c6eba3e2b882a86013763b442577fe5d2056a42111e732eca requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2707,11 +3066,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl name: aiohttp - version: 3.12.15 - sha256: 2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd + version: 3.13.0 + sha256: 7897298b3eedc790257fef8a6ec582ca04e9dbe568ba4a9a890913b925b8ea21 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2724,11 +3084,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl name: aiohttp - version: 3.12.15 - sha256: b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b + version: 3.13.0 + sha256: 564b36512a7da3b386143c611867e3f7cfb249300a1bf60889bd9985da67ab77 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2741,6 +3102,7 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl name: aiosignal @@ -2750,184 +3112,81 @@ packages: - frozenlist>=1.1.0 - typing-extensions>=4.2 ; python_full_version < '3.13' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.10.0-pyhe01879c_0.conda - sha256: d1b50686672ebe7041e44811eda563e45b94a8354db67eca659040392ac74d63 - md5: cc2613bfa71dec0eb2113ee21ac9ccbf - depends: - - exceptiongroup >=1.0.2 - - idna >=2.8 - - python >=3.9 - - sniffio >=1.1 - - typing_extensions >=4.5 - - python - constrains: - - trio >=0.26.1 - - uvloop >=0.21 - license: MIT - license_family: MIT - purls: - - pkg:pypi/anyio?source=compressed-mapping - size: 134857 - timestamp: 1754315087747 -- conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - sha256: 8f032b140ea4159806e4969a68b4a3c0a7cab1ad936eb958a2b5ffe5335e19bf - md5: 54898d0f524c9dee622d44bbb081a8ab - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/appnope?source=hash-mapping - size: 10076 - timestamp: 1733332433806 -- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad - md5: 8ac12aff0860280ee0cff7fa2cf63f3b - depends: +- pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl + name: anyio + version: 4.11.0 + sha256: 0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc + requires_dist: + - exceptiongroup>=1.0.2 ; python_full_version < '3.11' + - idna>=2.8 + - sniffio>=1.1 + - typing-extensions>=4.5 ; python_full_version < '3.13' + - trio>=0.31.0 ; extra == 'trio' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl + name: appnope + version: 0.1.4 + sha256: 502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl + name: argon2-cffi + version: 25.1.0 + sha256: fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741 + requires_dist: - argon2-cffi-bindings - - python >=3.9 - - typing-extensions - constrains: - - argon2_cffi ==999 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi?source=hash-mapping - size: 18715 - timestamp: 1749017288144 -- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_0.conda - sha256: d6d2f38ece253492a3e00800b5d4a5c2cc4b2de73b2c0fcc580c218f1cf58de6 - md5: 112c5e2b7fe99e3678bbd64316d38f0c - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.0.1 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 35657 - timestamp: 1753994819264 -- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_0.conda - sha256: 659d3876bd07ae8548b9be869708507bf56112ac300e43ca05860d5fbe73072e - md5: 99b4a1dea9e1d402c26dbbc0aef4f47c - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.0.1 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 35811 - timestamp: 1753994992173 -- conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py311h13e5629_0.conda - sha256: cab14d6bdcaf64f0911dcd994b51ddb753650d041a74c87a2107041763605e66 - md5: 8051f5bb22c95da482362f7a8c35fd68 - depends: - - __osx >=10.13 - - cffi >=1.0.1 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 33400 - timestamp: 1753995045262 -- conda: https://conda.anaconda.org/conda-forge/osx-64/argon2-cffi-bindings-25.1.0-py313h585f44e_0.conda - sha256: 1ff3a2d7f533aa8b3c3ab1ea812aee4a21584b77c0308a3df5eaf3e7a7f21369 - md5: aa855240032c28272b48199eda601486 - depends: - - __osx >=10.13 - - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 33475 - timestamp: 1753994999531 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h3696347_0.conda - sha256: f5b4102716a568877e212a9d4c677027b887f215d4735acfe4532efb2da59de1 - md5: 3b4ba20f581ec2268df5a76c64232ae5 - depends: - - __osx >=11.0 - - cffi >=1.0.1 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 34325 - timestamp: 1753995031680 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313hcdf3177_0.conda - sha256: 4318f43b5a524caf824f65a1ca7428d0a4a659d7c0a27f70adabfc660280fdc6 - md5: 337c72a8f36fb82bbd21bad12d502909 - depends: - - __osx >=11.0 - - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 34145 - timestamp: 1753995019248 -- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_0.conda - sha256: 4bde4487abbca4c8834a582928a80692a32ebba67e906ce676e931035a13d004 - md5: fdb37c9bd914e2a2c20f204f9cb15e6b - depends: - - cffi >=1.0.1 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 38615 - timestamp: 1753995128176 -- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_0.conda - sha256: b40e78275538abbf138a1a0233dbdca876bb4c03295a06ea0c475ee846d741c0 - md5: f68ecfe2b2dcd299454f3e3ee0968e2f - depends: - - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 38542 - timestamp: 1753995252162 -- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.3.0-pyhd8ed1ab_1.conda - sha256: c4b0bdb3d5dee50b60db92f99da3e4c524d5240aafc0a5fcc15e45ae2d1a3cd1 - md5: 46b53236fdd990271b03c3978d4218a9 - depends: - - python >=3.9 - - python-dateutil >=2.7.0 - - types-python-dateutil >=2.8.10 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/arrow?source=hash-mapping - size: 99951 - timestamp: 1733584345583 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: 2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: 7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl + name: argon2-cffi-bindings + version: 25.1.0 + sha256: a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98 + requires_dist: + - cffi>=1.0.1 ; python_full_version < '3.14' + - cffi>=2.0.0b1 ; python_full_version >= '3.14' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + name: arrow + version: 1.3.0 + sha256: c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80 + requires_dist: + - python-dateutil>=2.7.0 + - types-python-dateutil>=2.8.10 + - doc8 ; extra == 'doc' + - sphinx>=7.0.0 ; extra == 'doc' + - sphinx-autobuild ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + - sphinx-rtd-theme>=1.3.0 ; extra == 'doc' + - dateparser==1.* ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytz==2021.1 ; extra == 'test' + - simplejson==3.* ; extra == 'test' + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl name: asciichartpy version: 1.5.25 @@ -2948,251 +3207,129 @@ packages: - coverage ; extra == 'test' - asteval[dev,doc,test] ; extra == 'all' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.0-pyhd8ed1ab_1.conda - sha256: 93b14414b3b3ed91e286e1cbe4e7a60c4e1b1c730b0814d1e452a8ac4b9af593 - md5: 8f587de4bcf981e26228f268df374a9b - depends: - - python >=3.9 - constrains: - - astroid >=2,<4 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/asttokens?source=hash-mapping - size: 28206 - timestamp: 1733250564754 -- conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.0.5-pyh29332c3_0.conda - sha256: 3b7233041e462d9eeb93ea1dfe7b18aca9c358832517072054bb8761df0c324b - md5: d9d0f99095a9bb7e3641bca8c6ad2ac7 - depends: - - python >=3.9 - - typing_extensions >=4.0.0 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/async-lru?source=hash-mapping - size: 17335 - timestamp: 1742153708859 -- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - sha256: 99c53ffbcb5dc58084faf18587b215f9ac8ced36bbfb55fa807c00967e419019 - md5: a10d11958cadc13fdb43df75f8b1903f - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/attrs?source=hash-mapping - size: 57181 - timestamp: 1741918625732 -- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda - sha256: 1c656a35800b7f57f7371605bc6507c8d3ad60fbaaec65876fce7f73df1fc8ac - md5: 0a01c169f0ab0f91b26e77a3301fbfe4 - depends: - - python >=3.9 - - pytz >=2015.7 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/babel?source=hash-mapping - size: 6938256 - timestamp: 1738490268466 -- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.13.5-pyha770c72_0.conda - sha256: d2124c0ea13527c7f54582269b3ae19541141a3740d6d779e7aa95aa82eaf561 - md5: de0fd9702fd4c1186e930b8c35af6b6b - depends: - - python >=3.10 - - soupsieve >=1.2 - - typing-extensions - license: MIT - license_family: MIT - purls: - - pkg:pypi/beautifulsoup4?source=compressed-mapping - size: 88278 - timestamp: 1756094375546 +- pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl + name: asttokens + version: 3.0.0 + sha256: e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2 + requires_dist: + - astroid>=2,<4 ; extra == 'astroid' + - astroid>=2,<4 ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-xdist ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl + name: async-lru + version: 2.0.5 + sha256: ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943 + requires_dist: + - typing-extensions>=4.0.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + name: attrs + version: 25.4.0 + sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + name: autopep8 + version: 2.3.2 + sha256: ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128 + requires_dist: + - pycodestyle>=2.12.0 + - tomli ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl + name: babel + version: 2.17.0 + sha256: 4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2 + requires_dist: + - pytz>=2015.7 ; python_full_version < '3.9' + - tzdata ; sys_platform == 'win32' and extra == 'dev' + - backports-zoneinfo ; python_full_version < '3.9' and extra == 'dev' + - freezegun~=1.0 ; extra == 'dev' + - jinja2>=3.0 ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest>=6.0 ; extra == 'dev' + - pytz ; extra == 'dev' + - setuptools ; extra == 'dev' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl + name: backrefs + version: '5.9' + sha256: cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b + requires_dist: + - regex ; extra == 'extras' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl + name: backrefs + version: '5.9' + sha256: 6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf + requires_dist: + - regex ; extra == 'extras' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl + name: beautifulsoup4 + version: 4.14.2 + sha256: 5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515 + requires_dist: + - soupsieve>1.2 + - typing-extensions>=4.0.0 + - cchardet ; extra == 'cchardet' + - chardet ; extra == 'chardet' + - charset-normalizer ; extra == 'charset-normalizer' + - html5lib ; extra == 'html5lib' + - lxml ; extra == 'lxml' + requires_python: '>=3.7.0' - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl name: bidict version: 0.23.1 sha256: 5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5 requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.2.0-pyh29332c3_4.conda - sha256: a05971bb80cca50ce9977aad3f7fc053e54ea7d5321523efc7b9a6e12901d3cd - md5: f0b4c8e370446ef89797608d60a564b3 - depends: - - python >=3.9 +- pypi: https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl + name: bleach + version: 6.2.0 + sha256: 117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e + requires_dist: - webencodings - - python - constrains: - - tinycss >=1.1.0,<1.5 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/bleach?source=hash-mapping - size: 141405 - timestamp: 1737382993425 -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.2.0-h82add2a_4.conda - sha256: 0aba699344275b3972bd751f9403316edea2ceb942db12f9f493b63c74774a46 - md5: a30e9406c873940383555af4c873220d - depends: - - bleach ==6.2.0 pyh29332c3_4 - - tinycss2 - license: Apache-2.0 AND MIT - purls: [] - size: 4213 - timestamp: 1737382993425 + - tinycss2>=1.1.0,<1.5 ; extra == 'css' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl name: blinker version: 1.9.0 sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py311h1ddb823_4.conda - sha256: 318d4985acbf46457d254fbd6f0df80cc069890b5fc0013b3546d88eee1b1a1f - md5: 7138a06a7b0d11a23cfae323e6010a08 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - libbrotlicommon 1.1.0 hb03c661_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 354304 - timestamp: 1756599521587 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py313h7033f15_4.conda - sha256: b1941426e564d326097ded7af8b525540be219be7a88ca961d58a8d4fc116db2 - md5: bc8624c405856b1d047dd0a81829b08c - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - libbrotlicommon 1.1.0 hb03c661_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 353639 - timestamp: 1756599425945 -- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py311h7b20566_4.conda - sha256: 10afc3a0df8e7447c56b0753848336eeeeea04be9bf1817569c45755392de14b - md5: 13de3b969fd0ba12c4f6f9513f486f16 - depends: - - __osx >=10.13 - - libcxx >=19 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - libbrotlicommon 1.1.0 h1c43f85_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 368751 - timestamp: 1756600247737 -- conda: https://conda.anaconda.org/conda-forge/osx-64/brotli-python-1.1.0-py313h253db18_4.conda - sha256: fc4db6916598d1c634de85337db6d351d6f1cb8a93679715e0ee572777a5007e - md5: 8643345f12d0db3096a8aa0abd74f6e9 - depends: - - __osx >=10.13 - - libcxx >=19 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - libbrotlicommon 1.1.0 h1c43f85_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 369082 - timestamp: 1756600456664 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py311hf719da1_4.conda - sha256: 64645da991de052f0e522486cf97f8457fb37ed5c30d67655d3a32d2b9f56167 - md5: 4cd43bb7ba1a3cb4cd7a2e335f6d3c32 - depends: - - __osx >=11.0 - - libcxx >=19 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - constrains: - - libbrotlicommon 1.1.0 h6caf38d_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 340889 - timestamp: 1756599941690 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.1.0-py313hb4b7877_4.conda - sha256: a6402a7186ace5c3eb21ed4ce50eda3592c44ce38ab4e9a7ddd57d72b1e61fb3 - md5: 9518cd948fc334d66119c16a2106a959 - depends: - - __osx >=11.0 - - libcxx >=19 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - constrains: - - libbrotlicommon 1.1.0 h6caf38d_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 341104 - timestamp: 1756600117644 -- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py311h3e6a449_4.conda - sha256: d524edc172239fec70ad946e3b2fa1b9d7eea145ad80e9e66da25a4d815770ea - md5: 21d3a7fa95d27938158009cd08e461f2 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - libbrotlicommon 1.1.0 hfd05255_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 323289 - timestamp: 1756600106141 -- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.1.0-py313hfe59770_4.conda - sha256: 0e98ebafd586c4da7d848f9de94770cb27653ba9232a2badb28f8a01f6e48fb5 - md5: 477bf04a8a3030368068ccd39b8c5532 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - libbrotlicommon 1.1.0 hfd05255_4 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=compressed-mapping - size: 323459 - timestamp: 1756600051044 -- pypi: https://files.pythonhosted.org/packages/91/3e/f7329f3beb975edf740f6112f72b5e32933bffb7011c96f68cc3c92934c3/bumps-1.0.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl + name: build + version: 1.3.0 + sha256: 7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4 + requires_dist: + - packaging>=19.1 + - pyproject-hooks + - colorama ; os_name == 'nt' + - importlib-metadata>=4.6 ; python_full_version < '3.10.2' + - tomli>=1.1.0 ; python_full_version < '3.11' + - uv>=0.1.18 ; extra == 'uv' + - virtualenv>=20.11 ; python_full_version < '3.10' and extra == 'virtualenv' + - virtualenv>=20.17 ; python_full_version >= '3.10' and python_full_version < '3.14' and extra == 'virtualenv' + - virtualenv>=20.31 ; python_full_version >= '3.14' and extra == 'virtualenv' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl name: bumps - version: 1.0.2 - sha256: 5bdd117c0afc85caea0444daa3571a24f84eb68ce4f1a5f5574b9cdb03cd8fce + version: 1.0.3 + sha256: 4f503c814b9ddd2cda760b2e35aaa6285651434fc2e64ccac55b1a666bca17f6 requires_dist: - numpy - scipy - h5py - dill + - cloudpickle - matplotlib - blinker - aiohttp - python-socketio - plotly - mpld3 + - msgpack - graphlib-backport ; python_full_version < '3.9' - build ; extra == 'dev' - pre-commit ; extra == 'dev' @@ -3203,7 +3340,7 @@ packages: - setuptools ; extra == 'dev' - sphinx ; extra == 'dev' - versioningit ; extra == 'dev' - requires_python: '>=3.8' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 md5: 51a19bba1b8ebfb60df25cde030b7ebc @@ -3247,228 +3384,144 @@ packages: purls: [] size: 55977 timestamp: 1757437738856 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-h4c7d964_0.conda - sha256: 3b82f62baad3fd33827b01b0426e8203a2786c8f452f633740868296bcbe8485 - md5: c9e0c0f82f6e63323827db462b40ede8 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-h4c7d964_0.conda + sha256: bfb7f9f242f441fdcd80f1199edd2ecf09acea0f2bcef6f07d7cbb1a8131a345 + md5: e54200a1cd1fe33d61c9df8d3b00b743 depends: - __win license: ISC purls: [] - size: 154489 - timestamp: 1754210967212 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.8.3-hbd8a1cb_0.conda - sha256: 837b795a2bb39b75694ba910c13c15fa4998d4bb2a622c214a6a5174b2ae53d1 - md5: 74784ee3d225fc3dca89edb635b4e5cc + size: 156354 + timestamp: 1759649104842 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda + sha256: 3b5ad78b8bb61b6cdc0978a6a99f8dfb2cc789a451378d054698441005ecbdb6 + md5: f9e5fbc24009179e8b0409624691758a depends: - __unix license: ISC purls: [] - size: 154402 - timestamp: 1754210968730 -- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 - noarch: python - sha256: 561e6660f26c35d137ee150187d89767c988413c978e1b712d53f27ddf70ea17 - md5: 9b347a7ec10940d3f7941ff6c460b551 - depends: - - cached_property >=1.5.2,<1.5.3.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 4134 - timestamp: 1615209571450 -- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - sha256: 6dbf7a5070cc43d90a1e4c2ec0c541c69d8e30a0e25f50ce9f6e4a432e42c5d7 - md5: 576d629e47797577ab0f1b351297ef4a - depends: - - python >=3.6 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/cached-property?source=hash-mapping - size: 11065 - timestamp: 1615209567874 -- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda - sha256: a1ad5b0a2a242f439608f22a538d2175cac4444b7b3f4e2b8c090ac337aaea40 - md5: 11f59985f49df4620890f3e746ed7102 - depends: - - python >=3.9 - license: ISC - purls: - - pkg:pypi/certifi?source=compressed-mapping - size: 158692 - timestamp: 1754231530168 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py311h5b438cf_1.conda - sha256: bbd04c8729e6400fa358536b1007c1376cc396d569b71de10f1df7669d44170e - md5: 82e0123a459d095ac99c76d150ccdacf - depends: - - __glibc >=2.17,<3.0.a0 - - libffi >=3.4.6,<3.5.0a0 - - libgcc >=14 - - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=compressed-mapping - size: 303055 - timestamp: 1756808613387 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py313hf01b4d8_1.conda - sha256: 2c2d68ef3480c22e0d5837b9314579b4a8484ccfed264b8b7d5da70f695afdd9 - md5: c4a0f01c46bc155d205694bec57bd709 - depends: - - __glibc >=2.17,<3.0.a0 - - libffi >=3.4.6,<3.5.0a0 - - libgcc >=14 - - pycparser - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 297231 - timestamp: 1756808418076 -- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py311he66fa18_1.conda - sha256: f5c6c73e0a389d2c89e10b36883e18cd3abd14756d9d01d53856aaae3131f219 - md5: 70cd671f73c5c08899d5c43366d37787 - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 294021 - timestamp: 1756808523082 -- conda: https://conda.anaconda.org/conda-forge/osx-64/cffi-1.17.1-py313he20ea1e_1.conda - sha256: be88bd2cbb3f1f4e16326affc22b2c26f926dd18e03defc24df1fe6c80e7ce18 - md5: fc55afa9103145b51e5227a4ef0b8bad - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - pycparser - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 289490 - timestamp: 1756808563 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py311h146a0b8_1.conda - sha256: 97635f50d473eae17e11b954570efdc66b615dfa6321dd069742d6df4c14a8ba - md5: 1c72ccc307e7681c34e1c06c1711ee33 - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pycparser - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=compressed-mapping - size: 293204 - timestamp: 1756808628759 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-1.17.1-py313h755b2b2_1.conda - sha256: 6dd0ba5c68f59eb4039399d0c51d060b89f2028acd5c2f7f6879476ab108d797 - md5: a9024b1e15f59ce83654d542f83c23be - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pycparser - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 289263 - timestamp: 1756808593662 -- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py311h3485c13_1.conda - sha256: 46baee342b50ce7fbf4c52267f73327cb0512b970332037c8911afee1e54f063 - md5: 553a1836df919ca232b80ce1324fa5bb - depends: - - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 296743 - timestamp: 1756808544874 -- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-1.17.1-py313h5ea7bf4_1.conda - sha256: 8e2cd5b827a717d4a9f14594404a7d3693a5a7b7a9a181409ca1bd24a995d78c - md5: 69a537fed13191160f1a139b6d42f6c1 - depends: - - pycparser - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 290632 - timestamp: 1756808584791 -- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.3-pyhd8ed1ab_0.conda - sha256: 838d5a011f0e7422be6427becba3de743c78f3874ad2743c341accbba9bb2624 - md5: 7e7d5ef1b9ed630e4a1c358d6bc62284 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/charset-normalizer?source=hash-mapping - size: 51033 - timestamp: 1754767444665 -- pypi: https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl + size: 155907 + timestamp: 1759649036195 +- pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl + name: certifi + version: 2025.10.5 + sha256: 0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl + name: cffi + version: 2.0.0 + sha256: b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl + name: cffi + version: 2.0.0 + sha256: 19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl + name: cffi + version: 2.0.0 + sha256: 45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl + name: cffi + version: 2.0.0 + sha256: 00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl + name: cffi + version: 2.0.0 + sha256: 2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: cffi + version: 2.0.0 + sha256: c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl + name: cffi + version: 2.0.0 + sha256: 66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: cffi + version: 2.0.0 + sha256: 8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26 + requires_dist: + - pycparser ; implementation_name != 'PyPy' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + name: cfgv + version: 3.4.0 + sha256: b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl + name: charset-normalizer + version: 3.4.4 + sha256: 5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: charset-normalizer + version: 3.4.4 + sha256: 840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl + name: charset-normalizer + version: 3.4.4 + sha256: e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl + name: charset-normalizer + version: 3.4.4 + sha256: b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl + name: charset-normalizer + version: 3.4.4 + sha256: 6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: charset-normalizer + version: 3.4.4 + sha256: a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl name: click - version: 8.2.1 - sha256: 61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b + version: 8.3.0 + sha256: 9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc requires_dist: - colorama ; sys_platform == 'win32' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl + name: cloudpickle + version: 3.1.1 + sha256: c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl name: colorama version: 0.4.6 sha256: 4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' -- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 - md5: 962b9857ee8e7018c22f2776ffa0b2d7 - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/colorama?source=hash-mapping - size: 27011 - timestamp: 1733218222191 -- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 - md5: 2da13f2b299d8e1995bafbbe9689a2f7 - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/comm?source=hash-mapping - size: 14690 - timestamp: 1753453984907 +- pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl + name: comm + version: 0.2.3 + sha256: c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417 + requires_dist: + - pytest ; extra == 'test' + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl name: contourpy version: 1.3.3 @@ -3669,32 +3722,66 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.13-py311hd8ed1ab_0.conda - noarch: generic - sha256: ab70477f5cfb60961ba27d84a4c933a24705ac4b1736d8f3da14858e95bbfa7a - md5: 4666fd336f6d48d866a58490684704cd - depends: - - python >=3.11,<3.12.0a0 - - python_abi * *_cp311 - license: Python-2.0 - purls: [] - size: 47495 - timestamp: 1749048148121 -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.7-py313hd8ed1ab_100.conda - noarch: generic - sha256: e9ac20662d1c97ef96e44751a78c0057ec308f7cc208ef1fbdc868993c9f5eb3 - md5: c5623ddbd37c5dafa7754a83f97de01e - depends: - - python >=3.13,<3.14.0a0 - - python_abi * *_cp313 - license: Python-2.0 - purls: [] - size: 48174 - timestamp: 1756909387263 -- pypi: https://files.pythonhosted.org/packages/fc/2f/27f1f4b79d968bb6d3a3b61e0679ddfb7c583c7654e14ddfa2ec9e07ddcf/cryspy-0.7.8-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl + name: coverage + version: 7.11.0 + sha256: 865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.11.0 + sha256: aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl + name: coverage + version: 7.11.0 + sha256: 3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl + name: coverage + version: 7.11.0 + sha256: cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.11.0 + sha256: 10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl + name: coverage + version: 7.11.0 + sha256: b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl + name: coverage + version: 7.11.0 + sha256: f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl + name: coverage + version: 7.11.0 + sha256: 2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/91/de/dbade7ef5c9a0ba4b8179f78e3ed5b3aa09fd7fef63b47ba428c2ec9b4ca/cryspy-0.8.0-py3-none-any.whl name: cryspy - version: 0.7.8 - sha256: 5d389d7b932317ca6ab60996883aefacc9df94ac6f74b9cc080600304e298577 + version: 0.8.0 + sha256: be7f08232e583f57d455aaa3de57a3b50411172b77d67d69d58d6fc4efda1a19 requires_dist: - numpy - scipy @@ -3720,154 +3807,31 @@ packages: requires_dist: - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'macos-listener' requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py311hc665b79_1.conda - sha256: 19b0d1d9b0459db1466ad5846f6a30408ca9bbe244dcbbf32708116b564ceb11 - md5: 06e8c743932cc7788624128d08bc8806 - depends: - - python - - libgcc >=14 - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2729957 - timestamp: 1756742061937 -- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.16-py313h5d5ffb9_1.conda - sha256: a5cefff8a7ef8a0ede9af01247bcebbc6222bc95a8e7eeeb0495765e22fc3f1c - md5: 17e1e8f60454cf198f17234ccbb2fa8d - depends: - - python - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2868399 - timestamp: 1756742083538 -- conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py311hc651eee_1.conda - sha256: 7f7300ee62b58658813e99a08f4a6f8daf585420cab6330f083ae697f569e66a - md5: 32c16e5c0e427989aff77ead02bf7f88 - depends: - - python - - __osx >=10.13 - - libcxx >=19 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2665552 - timestamp: 1756742018979 -- conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.16-py313h03db916_1.conda - sha256: e57e4f91b2433d5726430cfe0a1b60cebb9743a18114614e7953ee726ff743d3 - md5: 35b920d8af8948d80dcbc01fe2d88467 - depends: - - python - - __osx >=10.13 - - libcxx >=19 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2769428 - timestamp: 1756742032895 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py311ha59bd64_1.conda - sha256: eea58c83203a96c1967574d495860bab109c52e10b41c5ac12daad7b66b81e70 - md5: 9d7f1641fc7385ed8dfc337d35ad4008 - depends: - - python - - libcxx >=19 - - python 3.11.* *_cpython - - __osx >=11.0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2666633 - timestamp: 1756742041729 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.16-py313hab38a8b_1.conda - sha256: 25062aad4e332f5f083584b13f4e1e06748ad8e53779482fa62c036c761bddbd - md5: ceea9fb6d7a6205ad329692ae053736e - depends: - - python - - libcxx >=19 - - __osx >=11.0 - - python 3.13.* *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2755888 - timestamp: 1756742003334 -- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py311h5dfdfe8_1.conda - sha256: 810fa69eca6adfbf707e2e31e26f24842ab313d2efbfdb8e73c15c164a8010d9 - md5: 5996fd469da1e196fd42c72a7b7a65ca - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 3933261 - timestamp: 1756742011482 -- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.16-py313h927ade5_1.conda - sha256: 89e86812b7163820e7316971e7b4cfcc32db9e043cf4698b7793207c3ef2592a - md5: f8323ed8df3c1a137fd0ef88cdaec20d - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 4000266 - timestamp: 1756742028369 -- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 - md5: 9ce473d1d1be1cc3810856a48b3fab32 - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/decorator?source=hash-mapping - size: 14129 - timestamp: 1740385067843 -- conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be - md5: 961b3a227b437d82ad7054484cfa71b2 - depends: - - python >=3.6 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/defusedxml?source=hash-mapping - size: 24062 - timestamp: 1615232388757 +- pypi: https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl + name: debugpy + version: 1.8.17 + sha256: 6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl + name: debugpy + version: 1.8.17 + sha256: 60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c2/c5/c012c60a2922cc91caa9675d0ddfbb14ba59e1e36228355f41cab6483469/debugpy-1.8.17-cp311-cp311-win_amd64.whl + name: debugpy + version: 1.8.17 + sha256: b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl + name: decorator + version: 5.2.1 + sha256: d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl + name: defusedxml + version: 0.7.1 + sha256: a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' - pypi: https://files.pythonhosted.org/packages/68/49/869b49db0bad6fba7c7471d612c356e745929d14e0de63acbc53e88f2a50/dfo_ls-1.6-py3-none-any.whl name: dfo-ls version: '1.6' @@ -3963,10 +3927,23 @@ packages: - objgraph>=1.7.2 ; extra == 'graph' - gprof2dot>=2022.7.29 ; extra == 'profile' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/50/0f/79f2e671695d2cb2d59537ec769cbd97e88b1c233a5023e7f4b94484ecef/easydiffraction-0.7.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + name: distlib + version: 0.4.0 + sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 +- pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl + name: docformatter + version: 1.7.7 + sha256: 7af49f8a46346a77858f6651f431b882c503c2f4442c8b4524b920c863277834 + requires_dist: + - charset-normalizer>=3.0.0,<4.0.0 + - tomli>=2.0.0,<3.0.0 ; python_full_version < '3.11' and extra == 'tomli' + - untokenize>=0.1.1,<0.2.0 + requires_python: '>=3.9,<4.0' +- pypi: https://files.pythonhosted.org/packages/a2/04/6326293093ad64756f2a1a2d3a002ed4799404e27d7eba00f649620f0755/easydiffraction-0.7.3-py3-none-any.whl name: easydiffraction - version: 0.7.2 - sha256: 0a76e0a22e85bc8c9267293b53c935d3b3d3b240e6c1d3741ac32b5091a5bd5d + version: 0.7.3 + sha256: f43ea86fb9492d182033653ff5ad082740d61cbb0233f676e7876deaf90d0ae9 requires_dist: - asciichartpy - asteval @@ -3985,16 +3962,49 @@ packages: - tabulate - typer - varname + - build ; extra == 'all' + - darkdetect ; extra == 'all' + - docformatter ; extra == 'all' + - interrogate ; extra == 'all' + - jinja2 ; extra == 'all' + - jupyterquiz ; extra == 'all' + - jupytext ; extra == 'all' + - mkdocs ; extra == 'all' + - mkdocs-autorefs<1.3.0 ; extra == 'all' + - mkdocs-jupyter ; extra == 'all' + - mkdocs-markdownextradata-plugin ; extra == 'all' + - mkdocs-material ; extra == 'all' + - mkdocs-plugin-inline-svg ; extra == 'all' + - mkdocstrings-python ; extra == 'all' + - nbmake ; extra == 'all' + - nbqa ; extra == 'all' + - nbstripout ; extra == 'all' + - pandas ; extra == 'all' + - plotly ; extra == 'all' + - pre-commit ; extra == 'all' + - py3dmol ; extra == 'all' + - pytest ; extra == 'all' + - pytest-cov ; extra == 'all' + - pytest-xdist ; extra == 'all' + - pyyaml ; extra == 'all' + - radon ; extra == 'all' + - ruff ; extra == 'all' + - validate-pyproject[all] ; extra == 'all' + - versioningit ; extra == 'all' - build ; extra == 'dev' + - docformatter ; extra == 'dev' + - interrogate ; extra == 'dev' - jinja2 ; extra == 'dev' - jupyterquiz ; extra == 'dev' - jupytext ; extra == 'dev' - nbmake ; extra == 'dev' - nbqa ; extra == 'dev' - nbstripout ; extra == 'dev' + - pre-commit ; extra == 'dev' - pytest ; extra == 'dev' - pytest-cov ; extra == 'dev' - pytest-xdist ; extra == 'dev' + - radon ; extra == 'dev' - ruff ; extra == 'dev' - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' @@ -4011,32 +4021,51 @@ packages: - plotly ; extra == 'visualization' - py3dmol ; extra == 'visualization' requires_python: '>=3.11,<3.14' -- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda - sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca - md5: 72e42d28960d875c7654614f8b50939a - depends: - - python >=3.9 - - typing_extensions >=4.6.0 - license: MIT and PSF-2.0 - purls: - - pkg:pypi/exceptiongroup?source=hash-mapping - size: 21284 - timestamp: 1746947398083 -- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad - md5: ff9efb7f7469aed3c4a8106ffa29593c - depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/executing?source=compressed-mapping - size: 30753 - timestamp: 1756729456476 -- pypi: https://files.pythonhosted.org/packages/13/7b/d0d3b9431642947b5805201fbbbe938a47b70c76685ef1f0cb5f5d7140d6/fonttools-4.59.2-cp313-cp313-macosx_10_13_universal2.whl +- pypi: https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl + name: execnet + version: 2.1.1 + sha256: 26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc + requires_dist: + - hatch ; extra == 'testing' + - pre-commit ; extra == 'testing' + - pytest ; extra == 'testing' + - tox ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl + name: executing + version: 2.2.1 + sha256: 760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017 + requires_dist: + - asttokens>=2.1.0 ; extra == 'tests' + - ipython ; extra == 'tests' + - pytest ; extra == 'tests' + - coverage ; extra == 'tests' + - coverage-enable-subprocess ; extra == 'tests' + - littleutils ; extra == 'tests' + - rich ; python_full_version >= '3.11' and extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl + name: fastjsonschema + version: 2.21.2 + sha256: 1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463 + requires_dist: + - colorama ; extra == 'devel' + - jsonschema ; extra == 'devel' + - json-spec ; extra == 'devel' + - pylint ; extra == 'devel' + - pytest ; extra == 'devel' + - pytest-benchmark ; extra == 'devel' + - pytest-cache ; extra == 'devel' + - validictory ; extra == 'devel' +- pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl + name: filelock + version: 3.20.0 + sha256: 339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl name: fonttools - version: 4.59.2 - sha256: 381bde13216ba09489864467f6bc0c57997bd729abfbb1ce6f807ba42c06cceb + version: 4.60.1 + sha256: b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4067,10 +4096,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/57/2a/976f5f9fa3b4dd911dc58d07358467bec20e813d933bc5d3db1a955dd456/fonttools-4.59.2-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl name: fonttools - version: 4.59.2 - sha256: 8e5e2682cf7be766d84f462ba8828d01e00c8751a8e8e7ce12d7784ccb69a30d + version: 4.60.1 + sha256: 3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4101,10 +4130,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/69/95/864726eaa8f9d4e053d0c462e64d5830ec7c599cbdf1db9e40f25ca3972e/fonttools-4.59.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl name: fonttools - version: 4.59.2 - sha256: c52694eae5d652361d59ecdb5a2246bff7cff13b6367a12da8499e9df56d148d + version: 4.60.1 + sha256: a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4135,10 +4164,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/76/be/fc5fe58dd76af7127b769b68071dbc32d4b95adc8b58d1d28d42d93c90f2/fonttools-4.59.2-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl name: fonttools - version: 4.59.2 - sha256: f33839aa091f7eef4e9078f5b7ab1b8ea4b1d8a50aeaef9fdb3611bba80869ec + version: 4.60.1 + sha256: 6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4169,10 +4198,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d7/de/35d839aa69db737a3f9f3a45000ca24721834d40118652a5775d5eca8ebb/fonttools-4.59.2-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl name: fonttools - version: 4.59.2 - sha256: 9836394e2f4ce5f9c0a7690ee93bd90aa1adc6b054f1a57b562c5d242c903104 + version: 4.60.1 + sha256: d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4203,10 +4232,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ea/a9/be7219fc64a6026cc0aded17fa3720f9277001c185434230bd351bf678e6/fonttools-4.59.2-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools - version: 4.59.2 - sha256: a72155928d7053bbde499d32a9c77d3f0f3d29ae72b5a121752481bcbd71e50f + version: 4.60.1 + sha256: e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4237,10 +4266,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl name: fonttools - version: 4.59.2 - sha256: 6235fc06bcbdb40186f483ba9d5d68f888ea68aa3c8dac347e05a7c54346fbc8 + version: 4.60.1 + sha256: eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4 requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4271,10 +4300,10 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f8/53/742fcd750ae0bdc74de4c0ff923111199cc2f90a4ee87aaddad505b6f477/fonttools-4.59.2-cp311-cp311-macosx_10_9_universal2.whl +- pypi: https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl name: fonttools - version: 4.59.2 - sha256: 511946e8d7ea5c0d6c7a53c4cb3ee48eda9ab9797cd9bf5d95829a398400354f + version: 4.60.1 + sha256: 7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f requires_dist: - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' @@ -4305,57 +4334,52 @@ packages: - skia-pathops>=0.5.0 ; extra == 'all' - uharfbuzz>=0.23.0 ; extra == 'all' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 - md5: d3549fd50d450b6d9e7dddff25dd2110 - depends: - - cached-property >=1.3.0 - - python >=3.9,<4 - license: MPL-2.0 - license_family: MOZILLA - purls: - - pkg:pypi/fqdn?source=hash-mapping - size: 16705 - timestamp: 1733327494780 -- pypi: https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl + name: fqdn + version: 1.5.1 + sha256: 3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014 + requires_dist: + - cached-property>=1.3.0 ; python_full_version < '3.8' + requires_python: '>=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4,<4' +- pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl name: frozenlist - version: 1.7.0 - sha256: 52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba + version: 1.8.0 + sha256: ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl name: frozenlist - version: 1.7.0 - sha256: 34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd + version: 1.8.0 + sha256: f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: frozenlist - version: 1.7.0 - sha256: 61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae + version: 1.8.0 + sha256: 2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl name: frozenlist - version: 1.7.0 - sha256: 387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d + version: 1.8.0 + sha256: 96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl name: frozenlist - version: 1.7.0 - sha256: 8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00 + version: 1.8.0 + sha256: fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: frozenlist - version: 1.7.0 - sha256: 9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750 + version: 1.8.0 + sha256: fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl name: frozenlist - version: 1.7.0 - sha256: d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d + version: 1.8.0 + sha256: 878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl name: frozenlist - version: 1.7.0 - sha256: cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43 + version: 1.8.0 + sha256: 17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9 requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/05/78/64628f519ff553a0d8101dd3852b87441caa69c6617250d48b3c6bad9422/gemmi-0.7.3-cp311-cp311-macosx_11_0_arm64.whl name: gemmi @@ -4397,6 +4421,16 @@ packages: version: 0.7.3 sha256: c0d39acb44c552449a07f1056c7fd370e3781e2b9b0bf55b065df2079935d6ec requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + name: ghp-import + version: 2.1.0 + sha256: 8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 + requires_dist: + - python-dateutil>=2.8.1 + - twine ; extra == 'dev' + - markdown ; extra == 'dev' + - flake8 ; extra == 'dev' + - wheel ; extra == 'dev' - pypi: https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl name: greenlet version: 3.2.4 @@ -4463,39 +4497,18 @@ packages: - psutil ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhd8ed1ab_0.conda - sha256: f64b68148c478c3bfc8f8d519541de7d2616bf59d44485a5271041d40c061887 - md5: 4b69232755285701bc86a5afe4d9933a - depends: - - python >=3.9 - - typing_extensions - license: MIT - license_family: MIT - purls: - - pkg:pypi/h11?source=hash-mapping - size: 37697 - timestamp: 1745526482242 -- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 - md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 - depends: - - python >=3.10 - - hyperframe >=6.1,<7 - - hpack >=4.1,<5 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/h2?source=compressed-mapping - size: 95967 - timestamp: 1756364871835 -- pypi: https://files.pythonhosted.org/packages/21/d4/d461649cafd5137088fb7f8e78fdc6621bb0c4ff2c090a389f68e8edc136/h5py-3.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: h5py - version: 3.14.0 - sha256: 723a40ee6505bd354bfd26385f2dae7bbfa87655f4e61bab175a49d72ebfc06b +- pypi: https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl + name: griffe + version: 1.14.0 + sha256: 0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0 requires_dist: - - numpy>=1.19.3 + - colorama>=0.4 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + name: h11 + version: 0.16.0 + sha256: 63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/36/5b/a066e459ca48b47cc73a5c668e9924d9619da9e3c500d9fb9c29c03858ec/h5py-3.14.0-cp311-cp311-macosx_11_0_arm64.whl name: h5py version: 3.14.0 @@ -4503,13 +4516,6 @@ packages: requires_dist: - numpy>=1.19.3 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl - name: h5py - version: 3.14.0 - sha256: ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3 - requires_dist: - - numpy>=1.19.3 - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl name: h5py version: 3.14.0 @@ -4531,74 +4537,64 @@ packages: requires_dist: - numpy>=1.19.3 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/0c/6c3f879a0f8e891625817637fad902da6e764e36919ed091dc77529004ac/h5py-3.14.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl name: h5py - version: 3.14.0 - sha256: d2744b520440a996f2dae97f901caa8a953afc055db4673a993f2d87d7f38713 + version: 3.15.1 + sha256: 550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878 requires_dist: - - numpy>=1.19.3 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: h5py - version: 3.14.0 - sha256: d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13 + version: 3.15.1 + sha256: 5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: h5py + version: 3.15.1 + sha256: 121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl + name: h5py + version: 3.15.1 + sha256: dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl + name: httpcore + version: 1.0.9 + sha256: 2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 requires_dist: - - numpy>=1.19.3 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba - md5: 0a802cb9888dd14eeefc611f05c40b6e - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/hpack?source=hash-mapping - size: 30731 - timestamp: 1737618390337 -- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b - md5: 4f14640d58e2cc0aa0819d9d8ba125bb - depends: - - python >=3.9 - - h11 >=0.16 - - h2 >=3,<5 - - sniffio 1.* - - anyio >=4.0,<5.0 - certifi - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/httpcore?source=hash-mapping - size: 49483 - timestamp: 1745602916758 -- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 - md5: d6989ead454181f4f9bc987d3dc4e285 - depends: + - h11>=0.16 + - anyio>=4.0,<5.0 ; extra == 'asyncio' + - h2>=3,<5 ; extra == 'http2' + - socksio==1.* ; extra == 'socks' + - trio>=0.22.0,<1.0 ; extra == 'trio' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl + name: httpx + version: 0.28.1 + sha256: d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + requires_dist: - anyio - certifi - - httpcore 1.* + - httpcore==1.* - idna - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/httpx?source=hash-mapping - size: 63082 - timestamp: 1733663449209 -- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 - md5: 8e6923fc12f1fe8f8c4e5c9f343256ac - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/hyperframe?source=hash-mapping - size: 17397 - timestamp: 1737618427549 + - brotli ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'brotli' + - click==8.* ; extra == 'cli' + - pygments==2.* ; extra == 'cli' + - rich>=10,<14 ; extra == 'cli' + - h2>=3,<5 ; extra == 'http2' + - socksio==1.* ; extra == 'socks' + - zstandard>=0.18.0 ; extra == 'zstd' + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e md5: 8b189310083baabfb622af68fd9d3ae3 @@ -4643,551 +4639,598 @@ packages: purls: [] size: 14544252 timestamp: 1720853966338 -- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - sha256: d7a472c9fd479e2e8dcb83fb8d433fce971ea369d704ece380e876f9c3494e87 - md5: 39a4f67be3286c86d696df570b1201b7 - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/idna?source=hash-mapping - size: 49765 - timestamp: 1733211921194 -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - sha256: c18ab120a0613ada4391b15981d86ff777b5690ca461ea7e9e49531e8f374745 - md5: 63ccfdc3a3ce25b027b8767eb722fca8 - depends: - - python >=3.9 - - zipp >=3.20 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/importlib-metadata?source=hash-mapping - size: 34641 - timestamp: 1747934053147 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh3521513_0.conda - sha256: 3dd6fcdde5e40a3088c9ecd72c29c6e5b1429b99e927f41c8cee944a07062046 - md5: 953007d45edeb098522ac860aade4fcf - depends: - - __win - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.0.0 - - jupyter_core >=4.12,!=5.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.9 - - pyzmq >=25 - - tornado >=6.2 - - traitlets >=5.4.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 121976 - timestamp: 1754353094360 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh82676e8_0.conda - sha256: cfc2c4e31dfedbb3d124d0055f55fda4694538fb790d52cd1b37af5312833e36 - md5: b0cc25825ce9212b8bee37829abad4d6 - depends: - - __linux - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.0.0 - - jupyter_core >=4.12,!=5.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.9 - - pyzmq >=25 - - tornado >=6.2 - - traitlets >=5.4.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 121367 - timestamp: 1754352984703 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.30.1-pyh92f572d_0.conda - sha256: ec80ed5f68c96dd46ff1b533b28d2094b6f07e2ec8115c8c60803920fdd6eb13 - md5: f208c1a85786e617a91329fa5201168c - depends: - - __osx - - appnope - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.0.0 - - jupyter_core >=4.12,!=5.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.9 - - pyzmq >=25 - - tornado >=6.2 - - traitlets >=5.4.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 121397 - timestamp: 1754353050327 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyh6be1c34_0.conda - sha256: 658c547dafb10cd0989f2cdf72f8ee9fe8f66240307b64555ee43f6908e9d0ad - md5: aec1868dd4cbe028b2c8cb11377895a6 - depends: - - __win +- pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl + name: identify + version: 2.6.15 + sha256: 1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 + requires_dist: + - ukkonen ; extra == 'license' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + name: idna + version: '3.11' + sha256: 771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea + requires_dist: + - ruff>=0.6.2 ; extra == 'all' + - mypy>=1.11.2 ; extra == 'all' + - pytest>=8.3.2 ; extra == 'all' + - flake8>=7.1.1 ; extra == 'all' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + name: iniconfig + version: 2.1.0 + sha256: 9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + name: interrogate + version: 1.7.0 + sha256: b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12 + requires_dist: + - attrs + - click>=7.1 - colorama + - py + - tabulate + - tomli ; python_full_version < '3.11' + - cairosvg ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-autobuild ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-mock ; extra == 'dev' + - coverage[toml] ; extra == 'dev' + - wheel ; extra == 'dev' + - pre-commit ; extra == 'dev' + - sphinx ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - cairosvg ; extra == 'png' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-mock ; extra == 'tests' + - coverage[toml] ; extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl + name: ipykernel + version: 6.30.1 + sha256: aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4 + requires_dist: + - appnope>=0.1.2 ; sys_platform == 'darwin' + - comm>=0.1.1 + - debugpy>=1.6.5 + - ipython>=7.23.1 + - jupyter-client>=8.0.0 + - jupyter-core>=4.12,!=5.0.* + - matplotlib-inline>=0.1 + - nest-asyncio>=1.4 + - packaging>=22 + - psutil>=5.7 + - pyzmq>=25 + - tornado>=6.2 + - traitlets>=5.4.0 + - coverage[toml] ; extra == 'cov' + - matplotlib ; extra == 'cov' + - pytest-cov ; extra == 'cov' + - trio ; extra == 'cov' + - intersphinx-registry ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - trio ; extra == 'docs' + - pyqt5 ; extra == 'pyqt5' + - pyside6 ; extra == 'pyside6' + - flaky ; extra == 'test' + - ipyparallel ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-asyncio>=0.23.5 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0,<9 ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl + name: ipython + version: 9.6.0 + sha256: 5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196 + requires_dist: + - colorama ; sys_platform == 'win32' - decorator - - exceptiongroup - - ipython_pygments_lexers - - jedi >=0.16 - - matplotlib-inline - - pickleshare - - prompt-toolkit >=3.0.41,<3.1.0 - - pygments >=2.4.0 - - python >=3.11 - - stack_data - - traitlets >=5.13.0 - - typing_extensions >=4.6 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython?source=hash-mapping - size: 630157 - timestamp: 1756474536497 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.5.0-pyhfa0c392_0.conda - sha256: e9ca009d3aab9d8a85f0241d6ada2c7fbc84072008e95f803fa59da3294aa863 - md5: c0916cc4b733577cd41df93884d857b0 - depends: - - __unix - - pexpect >4.3 - - decorator - - exceptiongroup - - ipython_pygments_lexers - - jedi >=0.16 + - ipython-pygments-lexers + - jedi>=0.16 - matplotlib-inline - - pickleshare - - prompt-toolkit >=3.0.41,<3.1.0 - - pygments >=2.4.0 - - python >=3.11 - - stack_data - - traitlets >=5.13.0 - - typing_extensions >=4.6 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython?source=hash-mapping - size: 630826 - timestamp: 1756474504536 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda - sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 - md5: bd80ba060603cc228d9d81c257093119 - depends: + - pexpect>4.3 ; sys_platform != 'emscripten' and sys_platform != 'win32' + - prompt-toolkit>=3.0.41,<3.1.0 + - pygments>=2.4.0 + - stack-data + - traitlets>=5.13.0 + - typing-extensions>=4.6 ; python_full_version < '3.12' + - black ; extra == 'black' + - docrepr ; extra == 'doc' + - exceptiongroup ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - ipykernel ; extra == 'doc' + - ipython[matplotlib,test] ; extra == 'doc' + - setuptools>=61.2 ; extra == 'doc' + - sphinx-toml==0.0.4 ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - sphinx>=1.3 ; extra == 'doc' + - typing-extensions ; extra == 'doc' + - pytest ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - testpath ; extra == 'test' + - packaging ; extra == 'test' + - ipython[test] ; extra == 'test-extra' + - curio ; extra == 'test-extra' + - jupyter-ai ; extra == 'test-extra' + - ipython[matplotlib] ; extra == 'test-extra' + - nbformat ; extra == 'test-extra' + - nbclient ; extra == 'test-extra' + - ipykernel ; extra == 'test-extra' + - numpy>=1.25 ; extra == 'test-extra' + - pandas>2.0 ; extra == 'test-extra' + - trio ; extra == 'test-extra' + - matplotlib>3.7 ; extra == 'matplotlib' + - ipython[doc,matplotlib,test,test-extra] ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl + name: ipython-pygments-lexers + version: 1.1.1 + sha256: a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c + requires_dist: - pygments - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython-pygments-lexers?source=hash-mapping - size: 13993 - timestamp: 1737123723464 -- conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed - md5: 0b0154421989637d424ccf0f104be51a - depends: - - arrow >=0.15.0 - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/isoduration?source=hash-mapping - size: 19832 - timestamp: 1733493720346 -- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - sha256: 92c4d217e2dc68983f724aa983cca5464dcb929c566627b26a2511159667dba8 - md5: a4f4c5dc9b80bc50e0d3dc4e6e8f1bd9 - depends: - - parso >=0.8.3,<0.9.0 - - python >=3.9 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/jedi?source=hash-mapping - size: 843646 - timestamp: 1733300981994 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - sha256: f1ac18b11637ddadc05642e8185a851c7fab5998c6f5470d716812fae943b2af - md5: 446bd6c8cb26050d528881df495ce646 - depends: - - markupsafe >=2.0 - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jinja2?source=hash-mapping - size: 112714 - timestamp: 1741263433881 -- conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.12.1-pyhd8ed1ab_0.conda - sha256: 4e08ccf9fa1103b617a4167a270768de736a36be795c6cd34c2761100d332f74 - md5: 0fc93f473c31a2f85c0bde213e7c63ca - depends: - - python >=3.9 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/json5?source=hash-mapping - size: 34191 - timestamp: 1755034963991 -- conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py311h38be061_2.conda - sha256: 4e744b30e3002b519c48868b3f5671328274d1d78cc8cbc0cda43057b570c508 - md5: 5dd29601defbcc14ac6953d9504a80a7 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18368 - timestamp: 1756754243123 -- conda: https://conda.anaconda.org/conda-forge/linux-64/jsonpointer-3.0.0-py313h78bf25f_2.conda - sha256: 9174f5209f835cc8918acddc279be919674d874179197e025181fe2a71cb0bce - md5: c1375f38e5f3ee38a9ee0e405a601c35 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18143 - timestamp: 1756754243113 -- conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py311h6eed73b_2.conda - sha256: 8e0f5af4d5bd59f52d27926750416638e32a65d58a202593fa0e97f312ad78c3 - md5: 9983b3959da3b695c8da48c93510cbdf - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18407 - timestamp: 1756754411612 -- conda: https://conda.anaconda.org/conda-forge/osx-64/jsonpointer-3.0.0-py313habf4b1d_2.conda - sha256: 444b99db8da2f87910967012bca4767dbc27024604cb11674baf5b33ad05e216 - md5: 6c9ecc26d54c3b9f9c40a7a21f780243 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18208 - timestamp: 1756754393540 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py311h267d04e_2.conda - sha256: 92b998fa9e68b7793b5b15882932f7703cdc1cc6e1348d0a1799567ef6f04428 - md5: 0edc5f25c32d86d5b4327e0f4de539f9 - depends: - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18716 - timestamp: 1756754690458 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/jsonpointer-3.0.0-py313h8f79df9_2.conda - sha256: 6e6097b156b2c69bcf05842f86a6650eb474477bec5e2233b4b8bcd2936cda5a - md5: 51a61cf0de7b177b4c09fa5eb4775c76 - depends: - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 18604 - timestamp: 1756754452012 -- conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py311h1ea47a8_2.conda - sha256: 64bcf78dbbda7ec523672c4b3f085527fd109732518e33907eac6b8049125113 - md5: c8f80d7bee5c66371969936eba774c45 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 43432 - timestamp: 1756754355044 -- conda: https://conda.anaconda.org/conda-forge/win-64/jsonpointer-3.0.0-py313hfa70ccb_2.conda - sha256: dda25a66128a7b883515a659cd53c694e735374ccfbfa87a998160a33679424a - md5: 8da802c2a92986f7054f97c45e0f4bee - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 43276 - timestamp: 1756754377785 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.25.1-pyhe01879c_0.conda - sha256: ac377ef7762e49cb9c4f985f1281eeff471e9adc3402526eea78e6ac6589cf1d - md5: 341fd940c242cf33e832c0402face56f - depends: - - attrs >=22.2.0 - - jsonschema-specifications >=2023.3.6 - - python >=3.9 - - referencing >=0.28.4 - - rpds-py >=0.7.1 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/jsonschema?source=compressed-mapping - size: 81688 - timestamp: 1755595646123 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 - md5: 439cd0f567d697b20a8f45cb70a1005a - depends: - - python >=3.10 - - referencing >=0.31.0 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/jsonschema-specifications?source=compressed-mapping - size: 19236 - timestamp: 1757335715225 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.25.1-he01879c_0.conda - sha256: aef6705fe1335e6472e1b6365fcdb586356b18dceff72d8d6a315fc90e900ccf - md5: 13e31c573c884962318a738405ca3487 - depends: - - jsonschema >=4.25.1,<4.25.2.0a0 - - fqdn - - idna - - isoduration - - jsonpointer >1.13 - - rfc3339-validator - - rfc3986-validator >0.1.0 - - rfc3987-syntax >=1.1.0 - - uri-template - - webcolors >=24.6.0 - license: MIT - license_family: MIT - purls: [] - size: 4744 - timestamp: 1755595646123 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.0-pyhcf101f3_0.conda - sha256: 897ad2e2c2335ef3c2826d7805e16002a1fd0d509b4ae0bc66617f0e0ff07bc2 - md5: 62b7c96c6cd77f8173cc5cada6a9acaa - depends: - - importlib-metadata >=4.8.3 - - jupyter_server >=1.1.2 - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-lsp?source=hash-mapping - size: 60377 - timestamp: 1756388269267 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.3-pyhd8ed1ab_1.conda - sha256: 19d8bd5bb2fde910ec59e081eeb59529491995ce0d653a5209366611023a0b3a - md5: 4ebae00eae9705b0c3d6d1018a81d047 - depends: - - importlib-metadata >=4.8.3 - - jupyter_core >=4.12,!=5.0.* - - python >=3.9 - - python-dateutil >=2.8.2 - - pyzmq >=23.0 - - tornado >=6.2 - - traitlets >=5.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-client?source=hash-mapping - size: 106342 - timestamp: 1733441040958 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh31011fe_0.conda - sha256: 56a7a7e907f15cca8c4f9b0c99488276d4cb10821d2d15df9245662184872e81 - md5: b7d89d860ebcda28a5303526cdee68ab - depends: - - __unix - - platformdirs >=2.5 - - python >=3.8 - - traitlets >=5.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-core?source=hash-mapping - size: 59562 - timestamp: 1748333186063 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.8.1-pyh5737063_0.conda - sha256: 928c2514c2974fda78447903217f01ca89a77eefedd46bf6a2fe97072df57e8d - md5: 324e60a0d3f39f268e899709575ea3cd - depends: - - __win - - cpython - - platformdirs >=2.5 - - python >=3.8 - - pywin32 >=300 - - traitlets >=5.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-core?source=hash-mapping - size: 59972 - timestamp: 1748333368923 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyh29332c3_0.conda - sha256: 37e6ac3ccf7afcc730c3b93cb91a13b9ae827fd306f35dd28f958a74a14878b5 - md5: f56000b36f09ab7533877e695e4e8cb0 - depends: - - jsonschema-with-format-nongpl >=4.18.0 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl + name: isoduration + version: 20.11.0 + sha256: b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042 + requires_dist: + - arrow>=0.15.0 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl + name: jedi + version: 0.19.2 + sha256: a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9 + requires_dist: + - parso>=0.8.4,<0.9.0 + - jinja2==2.11.3 ; extra == 'docs' + - markupsafe==1.1.1 ; extra == 'docs' + - pygments==2.8.1 ; extra == 'docs' + - alabaster==0.7.12 ; extra == 'docs' + - babel==2.9.1 ; extra == 'docs' + - chardet==4.0.0 ; extra == 'docs' + - commonmark==0.8.1 ; extra == 'docs' + - docutils==0.17.1 ; extra == 'docs' + - future==0.18.2 ; extra == 'docs' + - idna==2.10 ; extra == 'docs' + - imagesize==1.2.0 ; extra == 'docs' + - mock==1.0.1 ; extra == 'docs' + - packaging==20.9 ; extra == 'docs' + - pyparsing==2.4.7 ; extra == 'docs' + - pytz==2021.1 ; extra == 'docs' + - readthedocs-sphinx-ext==2.1.4 ; extra == 'docs' + - recommonmark==0.5.0 ; extra == 'docs' + - requests==2.25.1 ; extra == 'docs' + - six==1.15.0 ; extra == 'docs' + - snowballstemmer==2.1.0 ; extra == 'docs' + - sphinx-rtd-theme==0.4.3 ; extra == 'docs' + - sphinx==1.8.5 ; extra == 'docs' + - sphinxcontrib-serializinghtml==1.1.4 ; extra == 'docs' + - sphinxcontrib-websupport==1.2.4 ; extra == 'docs' + - urllib3==1.26.4 ; extra == 'docs' + - flake8==5.0.4 ; extra == 'qa' + - mypy==0.971 ; extra == 'qa' + - types-setuptools==67.2.0.1 ; extra == 'qa' + - django ; extra == 'testing' + - attrs ; extra == 'testing' + - colorama ; extra == 'testing' + - docopt ; extra == 'testing' + - pytest<9.0.0 ; extra == 'testing' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + name: jinja2 + version: 3.1.6 + sha256: 85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + requires_dist: + - markupsafe>=2.0 + - babel>=2.7 ; extra == 'i18n' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl + name: json5 + version: 0.12.1 + sha256: d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5 + requires_dist: + - build==1.2.2.post1 ; extra == 'dev' + - coverage==7.5.4 ; python_full_version < '3.9' and extra == 'dev' + - coverage==7.8.0 ; python_full_version >= '3.9' and extra == 'dev' + - mypy==1.14.1 ; python_full_version < '3.9' and extra == 'dev' + - mypy==1.15.0 ; python_full_version >= '3.9' and extra == 'dev' + - pip==25.0.1 ; extra == 'dev' + - pylint==3.2.7 ; python_full_version < '3.9' and extra == 'dev' + - pylint==3.3.6 ; python_full_version >= '3.9' and extra == 'dev' + - ruff==0.11.2 ; extra == 'dev' + - twine==6.1.0 ; extra == 'dev' + - uv==0.6.11 ; extra == 'dev' + requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + name: jsonpointer + version: 3.0.0 + sha256: 13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl + name: jsonschema + version: 4.25.1 + sha256: 3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63 + requires_dist: + - attrs>=22.2.0 + - jsonschema-specifications>=2023.3.6 + - referencing>=0.28.4 + - rpds-py>=0.7.1 + - fqdn ; extra == 'format' + - idna ; extra == 'format' + - isoduration ; extra == 'format' + - jsonpointer>1.13 ; extra == 'format' + - rfc3339-validator ; extra == 'format' + - rfc3987 ; extra == 'format' + - uri-template ; extra == 'format' + - webcolors>=1.11 ; extra == 'format' + - fqdn ; extra == 'format-nongpl' + - idna ; extra == 'format-nongpl' + - isoduration ; extra == 'format-nongpl' + - jsonpointer>1.13 ; extra == 'format-nongpl' + - rfc3339-validator ; extra == 'format-nongpl' + - rfc3986-validator>0.1.0 ; extra == 'format-nongpl' + - rfc3987-syntax>=1.1.0 ; extra == 'format-nongpl' + - uri-template ; extra == 'format-nongpl' + - webcolors>=24.6.0 ; extra == 'format-nongpl' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl + name: jsonschema-specifications + version: 2025.9.1 + sha256: 98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe + requires_dist: + - referencing>=0.31.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl + name: jupyter-client + version: 8.6.3 + sha256: e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f + requires_dist: + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - jupyter-core>=4.12,!=5.0.* + - python-dateutil>=2.8.2 + - pyzmq>=23.0 + - tornado>=6.2 + - traitlets>=5.3 + - ipykernel ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx>=4 ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - coverage ; extra == 'test' + - ipykernel>=6.14 ; extra == 'test' + - mypy ; extra == 'test' + - paramiko ; sys_platform == 'win32' and extra == 'test' + - pre-commit ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter[client]>=0.4.1 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest<8.2.0 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + name: jupyter-core + version: 5.8.1 + sha256: c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0 + requires_dist: + - platformdirs>=2.5 + - pywin32>=300 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' + - traitlets>=5.3 + - intersphinx-registry ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - traitlets ; extra == 'docs' + - ipykernel ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest<9 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl + name: jupyter-events + version: 0.12.0 + sha256: 6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb + requires_dist: + - jsonschema[format-nongpl]>=4.18.0 - packaging - - python >=3.9 - - python-json-logger >=2.0.4 - - pyyaml >=5.3 + - python-json-logger>=2.0.4 + - pyyaml>=5.3 - referencing - rfc3339-validator - - rfc3986-validator >=0.1.1 - - traitlets >=5.3 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-events?source=hash-mapping - size: 23647 - timestamp: 1738765986736 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - sha256: 74c4e642be97c538dae1895f7052599dfd740d8bd251f727bce6453ce8d6cd9a - md5: d79a87dcfa726bcea8e61275feed6f83 - depends: - - anyio >=3.1.0 - - argon2-cffi >=21.1 - - jinja2 >=3.0.3 - - jupyter_client >=7.4.4 - - jupyter_core >=4.12,!=5.0.* - - jupyter_events >=0.11.0 - - jupyter_server_terminals >=0.4.4 - - nbconvert-core >=6.4.4 - - nbformat >=5.3.0 - - overrides >=5.0 - - packaging >=22.0 - - prometheus_client >=0.9 - - python >=3.10 - - pyzmq >=24 - - send2trash >=1.8.2 - - terminado >=0.8.3 - - tornado >=6.2.0 - - traitlets >=5.6.0 - - websocket-client >=1.7 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-server?source=hash-mapping - size: 347094 - timestamp: 1755870522134 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.3-pyhd8ed1ab_1.conda - sha256: 0890fc79422191bc29edf17d7b42cff44ba254aa225d31eb30819f8772b775b8 - md5: 2d983ff1b82a1ccb6f2e9d8784bdd6bd - depends: - - python >=3.9 - - terminado >=0.8.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyter-server-terminals?source=hash-mapping - size: 19711 - timestamp: 1733428049134 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.4.7-pyhd8ed1ab_0.conda - sha256: 042bdb981ad5394530bee8329a10c76b9e17c12651d15a885d68e2cbbfef6869 - md5: 460d51bb21b7a4c4b6e100c824405fbb - depends: - - async-lru >=1.0.0 - - httpx >=0.25.0,<1 - - importlib-metadata >=4.8.3 - - ipykernel >=6.5.0,!=6.30.0 - - jinja2 >=3.0.3 - - jupyter-lsp >=2.0.0 - - jupyter_core - - jupyter_server >=2.4.0,<3 - - jupyterlab_server >=2.27.1,<3 - - notebook-shim >=0.2 + - rfc3986-validator>=0.1.1 + - traitlets>=5.3 + - click ; extra == 'cli' + - rich ; extra == 'cli' + - jupyterlite-sphinx ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme>=0.16 ; extra == 'docs' + - sphinx>=8 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - click ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-asyncio>=0.19.0 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - rich ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl + name: jupyter-lsp + version: 2.3.0 + sha256: e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f + requires_dist: + - jupyter-server>=1.1.2 + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl + name: jupyter-server + version: 2.17.0 + sha256: e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f + requires_dist: + - anyio>=3.1.0 + - argon2-cffi>=21.1 + - jinja2>=3.0.3 + - jupyter-client>=7.4.4 + - jupyter-core>=4.12,!=5.0.* + - jupyter-events>=0.11.0 + - jupyter-server-terminals>=0.4.4 + - nbconvert>=6.4.4 + - nbformat>=5.3.0 + - overrides>=5.0 ; python_full_version < '3.12' + - packaging>=22.0 + - prometheus-client>=0.9 + - pywinpty>=2.0.1 ; os_name == 'nt' + - pyzmq>=24 + - send2trash>=1.8.2 + - terminado>=0.8.3 + - tornado>=6.2.0 + - traitlets>=5.6.0 + - websocket-client>=1.7 + - ipykernel ; extra == 'docs' + - jinja2 ; extra == 'docs' + - jupyter-client ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbformat ; extra == 'docs' + - prometheus-client ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - send2trash ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-openapi>=0.8.0 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - sphinxemoji ; extra == 'docs' + - tornado ; extra == 'docs' + - typing-extensions ; extra == 'docs' + - flaky ; extra == 'test' + - ipykernel ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-jupyter[server]>=0.7 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0,<9 ; extra == 'test' + - requests ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl + name: jupyter-server-terminals + version: 0.5.3 + sha256: 41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa + requires_dist: + - pywinpty>=2.0.3 ; os_name == 'nt' + - terminado>=0.8.3 + - jinja2 ; extra == 'docs' + - jupyter-server ; extra == 'docs' + - mistune<4.0 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbformat ; extra == 'docs' + - packaging ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-openapi ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - sphinxemoji ; extra == 'docs' + - tornado ; extra == 'docs' + - jupyter-server>=2.0.0 ; extra == 'test' + - pytest-jupyter[server]>=0.5.3 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + name: jupyterlab + version: 4.4.9 + sha256: 394c902827350c017430a8370b9f40c03c098773084bc53930145c146d3d2cb2 + requires_dist: + - async-lru>=1.0.0 + - httpx>=0.25.0,<1 + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - ipykernel>=6.5.0,!=6.30.0 + - jinja2>=3.0.3 + - jupyter-core + - jupyter-lsp>=2.0.0 + - jupyter-server>=2.4.0,<3 + - jupyterlab-server>=2.27.1,<3 + - notebook-shim>=0.2 - packaging - - python >=3.10 - - setuptools >=41.1.0 - - tomli >=1.2.2 - - tornado >=6.2.0 + - setuptools>=41.1.0 + - tomli>=1.2.2 ; python_full_version < '3.11' + - tornado>=6.2.0 - traitlets - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyterlab?source=compressed-mapping - size: 8479512 - timestamp: 1756911706349 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 - md5: fd312693df06da3578383232528c468d - depends: - - pygments >=2.4.1,<3 - - python >=3.9 - constrains: - - jupyterlab >=4.0.8,<5.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyterlab-pygments?source=hash-mapping - size: 18711 - timestamp: 1733328194037 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.27.3-pyhd8ed1ab_1.conda - sha256: d03d0b7e23fa56d322993bc9786b3a43b88ccc26e58b77c756619a921ab30e86 - md5: 9dc4b2b0f41f0de41d27f3293e319357 - depends: - - babel >=2.10 - - importlib-metadata >=4.8.3 - - jinja2 >=3.0.3 - - json5 >=0.9.0 - - jsonschema >=4.18 - - jupyter_server >=1.21,<3 - - packaging >=21.3 - - python >=3.9 - - requests >=2.31 - constrains: - - openapi-core >=0.18.0,<0.19.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/jupyterlab-server?source=hash-mapping - size: 49449 - timestamp: 1733599666357 -- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 - md5: b38117a3c920364aff79f870c984b4a3 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-or-later - purls: [] - size: 134088 - timestamp: 1754905959823 + - build ; extra == 'dev' + - bump2version ; extra == 'dev' + - coverage ; extra == 'dev' + - hatch ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff==0.11.4 ; extra == 'dev' + - jsx-lexer ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme>=0.13.0 ; extra == 'docs' + - pytest ; extra == 'docs' + - pytest-check-links ; extra == 'docs' + - pytest-jupyter ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx>=1.8,<8.2.0 ; extra == 'docs' + - altair==5.5.0 ; extra == 'docs-screenshots' + - ipython==8.16.1 ; extra == 'docs-screenshots' + - ipywidgets==8.1.5 ; extra == 'docs-screenshots' + - jupyterlab-geojson==3.4.0 ; extra == 'docs-screenshots' + - jupyterlab-language-pack-zh-cn==4.3.post1 ; extra == 'docs-screenshots' + - matplotlib==3.10.0 ; extra == 'docs-screenshots' + - nbconvert>=7.0.0 ; extra == 'docs-screenshots' + - pandas==2.2.3 ; extra == 'docs-screenshots' + - scipy==1.15.1 ; extra == 'docs-screenshots' + - vega-datasets==0.9.0 ; extra == 'docs-screenshots' + - coverage ; extra == 'test' + - pytest-check-links>=0.7 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter>=0.5.3 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-tornasync ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - requests ; extra == 'test' + - requests-cache ; extra == 'test' + - virtualenv ; extra == 'test' + - copier>=9,<10 ; extra == 'upgrade-extension' + - jinja2-time<0.3 ; extra == 'upgrade-extension' + - pydantic<3.0 ; extra == 'upgrade-extension' + - pyyaml-include<3.0 ; extra == 'upgrade-extension' + - tomli-w<2.0 ; extra == 'upgrade-extension' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl + name: jupyterlab-pygments + version: 0.3.0 + sha256: 841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + name: jupyterlab-server + version: 2.27.3 + sha256: e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4 + requires_dist: + - babel>=2.10 + - importlib-metadata>=4.8.3 ; python_full_version < '3.10' + - jinja2>=3.0.3 + - json5>=0.9.0 + - jsonschema>=4.18.0 + - jupyter-server>=1.21,<3 + - packaging>=21.3 + - requests>=2.31 + - autodoc-traits ; extra == 'docs' + - jinja2<3.2.0 ; extra == 'docs' + - mistune<4 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinxcontrib-openapi>0.8 ; extra == 'docs' + - openapi-core~=0.18.0 ; extra == 'openapi' + - ruamel-yaml ; extra == 'openapi' + - hatch ; extra == 'test' + - ipykernel ; extra == 'test' + - openapi-core~=0.18.0 ; extra == 'test' + - openapi-spec-validator>=0.6.0,<0.8.0 ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-jupyter[server]>=0.6.2 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0,<8 ; extra == 'test' + - requests-mock ; extra == 'test' + - ruamel-yaml ; extra == 'test' + - sphinxcontrib-spelling ; extra == 'test' + - strict-rfc3339 ; extra == 'test' + - werkzeug ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f7/5a/488f492b49b712ca38326e12c78d0d48ab6be682a2989ce533f0f2c4dfd2/jupyterquiz-2.9.6.2-py2.py3-none-any.whl + name: jupyterquiz + version: 2.9.6.2 + sha256: f60f358c809d06a38c423c804740c1113c8840e130227aef50694a9201474bef +- pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl + name: jupytext + version: 1.17.3 + sha256: 09b0a94cd904416e823a5ba9f41bd181031215b6fc682d2b5c18e68354feb17c + requires_dist: + - markdown-it-py>=1.0 + - mdit-py-plugins + - nbformat + - packaging + - pyyaml + - tomli ; python_full_version < '3.11' + - autopep8 ; extra == 'dev' + - black ; extra == 'dev' + - flake8 ; extra == 'dev' + - gitpython ; extra == 'dev' + - ipykernel ; extra == 'dev' + - isort ; extra == 'dev' + - jupyter-fs[fs]>=1.0 ; extra == 'dev' + - jupyter-server!=2.11 ; extra == 'dev' + - nbconvert ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-asyncio ; extra == 'dev' + - pytest-cov>=2.6.1 ; extra == 'dev' + - pytest-randomly ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-gallery>=0.8 ; extra == 'dev' + - myst-parser ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - pytest ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - pytest-randomly ; extra == 'test' + - pytest-xdist ; extra == 'test' + - black ; extra == 'test-cov' + - ipykernel ; extra == 'test-cov' + - jupyter-server!=2.11 ; extra == 'test-cov' + - nbconvert ; extra == 'test-cov' + - pytest ; extra == 'test-cov' + - pytest-asyncio ; extra == 'test-cov' + - pytest-cov>=2.6.1 ; extra == 'test-cov' + - pytest-randomly ; extra == 'test-cov' + - pytest-xdist ; extra == 'test-cov' + - autopep8 ; extra == 'test-external' + - black ; extra == 'test-external' + - flake8 ; extra == 'test-external' + - gitpython ; extra == 'test-external' + - ipykernel ; extra == 'test-external' + - isort ; extra == 'test-external' + - jupyter-fs[fs]>=1.0 ; extra == 'test-external' + - jupyter-server!=2.11 ; extra == 'test-external' + - nbconvert ; extra == 'test-external' + - pre-commit ; extra == 'test-external' + - pytest ; extra == 'test-external' + - pytest-asyncio ; extra == 'test-external' + - pytest-randomly ; extra == 'test-external' + - pytest-xdist ; extra == 'test-external' + - sphinx ; extra == 'test-external' + - sphinx-gallery>=0.8 ; extra == 'test-external' + - black ; extra == 'test-functional' + - pytest ; extra == 'test-functional' + - pytest-asyncio ; extra == 'test-functional' + - pytest-randomly ; extra == 'test-functional' + - pytest-xdist ; extra == 'test-functional' + - black ; extra == 'test-integration' + - ipykernel ; extra == 'test-integration' + - jupyter-server!=2.11 ; extra == 'test-integration' + - nbconvert ; extra == 'test-integration' + - pytest ; extra == 'test-integration' + - pytest-asyncio ; extra == 'test-integration' + - pytest-randomly ; extra == 'test-integration' + - pytest-xdist ; extra == 'test-integration' + - bash-kernel ; extra == 'test-ui' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl name: kiwisolver version: 1.4.9 @@ -5228,76 +5271,19 @@ packages: version: 1.4.9 sha256: b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098 requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 - md5: 3f43953b7d3fb3aaa1d0d0723d91e368 - depends: - - keyutils >=1.6.1,<2.0a0 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1370023 - timestamp: 1719463201255 -- conda: https://conda.anaconda.org/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - sha256: 83b52685a4ce542772f0892a0f05764ac69d57187975579a0835ff255ae3ef9c - md5: d4765c524b1d91567886bde656fb514b - depends: - - __osx >=10.13 - - libcxx >=16 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1185323 - timestamp: 1719463492984 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - sha256: 4442f957c3c77d69d9da3521268cad5d54c9033f1a73f99cde0a3658937b159b - md5: c6dc8a0fdec13a0565936655c33069a1 - depends: - - __osx >=11.0 - - libcxx >=16 - - libedit >=3.1.20191231,<3.2.0a0 - - libedit >=3.1.20191231,<4.0a0 - - openssl >=3.3.1,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1155530 - timestamp: 1719463474401 -- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - sha256: 18e8b3430d7d232dad132f574268f56b3eb1a19431d6d5de8c53c29e6c18fa81 - md5: 31aec030344e962fbd7dbbbbd68e60a9 - depends: - - openssl >=3.3.1,<4.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: MIT - license_family: MIT - purls: [] - size: 712034 - timestamp: 1719463874284 -- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.2.2-pyhd8ed1ab_1.conda - sha256: 637a9c32e15a4333f1f9c91e0a506dbab4a6dab7ee83e126951159c916c81c99 - md5: 3a8063b25e603999188ed4bbf3485404 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/lark?source=hash-mapping - size: 92093 - timestamp: 1734709450256 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-h1423503_1.conda - sha256: 1a620f27d79217c1295049ba214c2f80372062fd251b569e9873d4a953d27554 - md5: 0be7c6e070c19105f966d3758448d018 +- pypi: https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl + name: lark + version: 1.3.0 + sha256: 80661f261fb2584a9828a097a2432efd575af27d20be0fd35d17f0fe37253831 + requires_dist: + - regex ; extra == 'regex' + - js2py ; extra == 'nearley' + - atomicwrites ; extra == 'atomic-cache' + - interegular>=0.3.1,<0.4.0 ; extra == 'interegular' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.44-ha97dd6f_2.conda + sha256: 707dfb8d55d7a5c6f95c772d778ef07a7ca85417d9971796f7d3daad0b615de8 + md5: 14bae321b8127b63cba276bd53fac237 depends: - __glibc >=2.17,<3.0.a0 constrains: @@ -5305,8 +5291,8 @@ packages: license: GPL-3.0-only license_family: GPL purls: [] - size: 676044 - timestamp: 1752032747103 + size: 747158 + timestamp: 1758810907507 - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda build_number: 35 sha256: 4180e7ab27ed03ddf01d7e599002fcba1b32dcb68214ee25da823bac371ed362 @@ -5338,63 +5324,26 @@ packages: purls: [] size: 66398 timestamp: 1757003514529 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.1-h3d58e20_0.conda - sha256: dd207d8882854f22072b7fd4f03726e0e182e0666986ec880168f1753f7415dc - md5: 7f5b7dfca71a5c165ce57f46e9e48480 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + sha256: 9bba2ce10e1c390a4091ca48fab0c71c010f6526c27ac2da53399940ad4c113f + md5: 432d125a340932454d777b66b09c32a1 depends: - - __osx >=10.13 - license: Apache-2.0 WITH LLVM-exception - license_family: Apache - purls: [] - size: 571163 - timestamp: 1757525814844 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.1-hf598326_0.conda - sha256: 6af03355967b7b097d5820dde05e0c709945fdb01f4bc56d11499d8bf7435239 - md5: d5790f3769fedeea4e021483272bdc53 - depends: - - __osx >=11.0 - license: Apache-2.0 WITH LLVM-exception - license_family: Apache - purls: [] - size: 568291 - timestamp: 1757525671408 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 - md5: c277e0a4d549b03ac1e9d6cbbe3d017b - depends: - - ncurses - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause - license_family: BSD - purls: [] - size: 134676 - timestamp: 1738479519902 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libedit-3.1.20250104-pl5321ha958ccf_0.conda - sha256: 6cc49785940a99e6a6b8c6edbb15f44c2dd6c789d9c283e5ee7bdfedd50b4cd6 - md5: 1f4ed31220402fcddc083b4bff406868 - depends: - - ncurses - - __osx >=10.13 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause - license_family: BSD + - __osx >=10.13 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache purls: [] - size: 115563 - timestamp: 1738479554273 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - sha256: 66aa216a403de0bb0c1340a88d1a06adaff66bae2cfd196731aa24db9859d631 - md5: 44083d2d2c2025afca315c7a172eab2b + size: 571632 + timestamp: 1760166417842 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda + sha256: b9bad452e3e1d0cc597d907681461341209cb7576178d5c1933026a650b381d1 + md5: e976227574dfcd0048324576adf8d60d depends: - - ncurses - __osx >=11.0 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause - license_family: BSD + license: Apache-2.0 WITH LLVM-exception + license_family: Apache purls: [] - size: 107691 - timestamp: 1738479560845 + size: 568715 + timestamp: 1760166479630 - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 md5: 4211416ecba1866fab0c6470986c22d6 @@ -5489,40 +5438,40 @@ packages: purls: [] size: 44978 timestamp: 1743435053850 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - sha256: 0caed73aac3966bfbf5710e06c728a24c6c138605121a3dacb2e03440e8baa6a - md5: 264fbfba7fb20acf3b29cde153e345ce +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-h767d61c_7.conda + sha256: 08f9b87578ab981c7713e4e6a7d935e40766e10691732bba376d4964562bcb45 + md5: c0374badb3a5d4b1372db28d19462c53 depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgomp 15.1.0 h767d61c_5 - - libgcc-ng ==15.1.0=*_5 + - libgomp 15.2.0 h767d61c_7 + - libgcc-ng ==15.2.0=*_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 824191 - timestamp: 1757042543820 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - sha256: f54bb9c3be12b24be327f4c1afccc2969712e0b091cdfbd1d763fb3e61cda03f - md5: 069afdf8ea72504e48d23ae1171d951c + size: 822552 + timestamp: 1759968052178 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_7.conda + sha256: 2045066dd8e6e58aaf5ae2b722fb6dfdbb57c862b5f34ac7bfb58c40ef39b6ad + md5: 280ea6eee9e2ddefde25ff799c4f0363 depends: - - libgcc 15.1.0 h767d61c_5 + - libgcc 15.2.0 h767d61c_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 29187 - timestamp: 1757042549554 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda - sha256: 125051d51a8c04694d0830f6343af78b556dd88cc249dfec5a97703ebfb1832d - md5: dcd5ff1940cd38f6df777cac86819d60 + size: 29313 + timestamp: 1759968065504 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-h767d61c_7.conda + sha256: e9fb1c258c8e66ee278397b5822692527c5f5786d372fe7a869b900853f3f5ca + md5: f7b4d76975aac7e5d9e6ad13845f92fe depends: - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 447215 - timestamp: 1757042483384 + size: 447919 + timestamp: 1759967942498 - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.1-default_h64bd3f2_1002.conda sha256: 266dfe151066c34695dbdc824ba1246b99f016115ef79339cbcf005ac50527c1 md5: b0cac6e5b06ca5eeb14b4f7cf908619f @@ -5534,6 +5483,7 @@ packages: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: BSD-3-Clause + license_family: BSD purls: [] size: 2414731 timestamp: 1757624335056 @@ -5649,44 +5599,6 @@ packages: purls: [] size: 33731 timestamp: 1750274110928 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - sha256: 0105bd108f19ea8e6a78d2d994a6d4a8db16d19a41212070d2d1d48a63c34161 - md5: a587892d3c13b6621a6091be690dbca2 - depends: - - libgcc-ng >=12 - license: ISC - purls: [] - size: 205978 - timestamp: 1716828628198 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libsodium-1.0.20-hfdf4475_0.conda - sha256: d3975cfe60e81072666da8c76b993af018cf2e73fe55acba2b5ba0928efaccf5 - md5: 6af4b059e26492da6013e79cbcb4d069 - depends: - - __osx >=10.13 - license: ISC - purls: [] - size: 210249 - timestamp: 1716828641383 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda - sha256: fade8223e1e1004367d7101dd17261003b60aa576df6d7802191f8972f7470b1 - md5: a7ce36e284c5faaf93c220dfc39e3abd - depends: - - __osx >=11.0 - license: ISC - purls: [] - size: 164972 - timestamp: 1716828607917 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.20-hc70643c_0.conda - sha256: 7bcb3edccea30f711b6be9601e083ecf4f435b9407d70fc48fbcf9e5d69a0fc6 - md5: 198bb594f202b205c7d18b936fa4524f - depends: - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: ISC - purls: [] - size: 202344 - timestamp: 1716828757533 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.50.4-h0c1763c_0.conda sha256: 6d9c32fc369af5a84875725f7ddfbfc2ace795c28f246dc70055a79f9b2003da md5: 0b367fad34931cb79e0d6b7e5c06bb1c @@ -5730,38 +5642,40 @@ packages: purls: [] size: 1288499 timestamp: 1753948889360 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - sha256: 0f5f61cab229b6043541c13538d75ce11bd96fb2db76f94ecf81997b1fde6408 - md5: 4e02a49aaa9d5190cb630fa43528fbe6 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h8f9b012_7.conda + sha256: 1b981647d9775e1cdeb2fab0a4dd9cd75a6b0de2963f6c3953dbd712f78334b3 + md5: 5b767048b1b3ee9a954b06f4084f93dc depends: - __glibc >=2.17,<3.0.a0 - - libgcc 15.1.0 h767d61c_5 + - libgcc 15.2.0 h767d61c_7 + constrains: + - libstdcxx-ng ==15.2.0=*_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 3896432 - timestamp: 1757042571458 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - sha256: 7b8cabbf0ab4fe3581ca28fe8ca319f964078578a51dd2ca3f703c1d21ba23ff - md5: 8bba50c7f4679f08c861b597ad2bda6b + size: 3898269 + timestamp: 1759968103436 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-h4852527_7.conda + sha256: 024fd46ac3ea8032a5ec3ea7b91c4c235701a8bf0e6520fe5e6539992a6bd05f + md5: f627678cf829bd70bccf141a19c3ad3e depends: - - libstdcxx 15.1.0 h8f9b012_5 + - libstdcxx 15.2.0 h8f9b012_7 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 29233 - timestamp: 1757042603319 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.1-he9a06e4_0.conda - sha256: 776e28735cee84b97e4d05dd5d67b95221a3e2c09b8b13e3d6dbe6494337d527 - md5: af930c65e9a79a3423d6d36e265cef65 + size: 29343 + timestamp: 1759968157195 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-he9a06e4_0.conda + sha256: e5ec6d2ad7eef538ddcb9ea62ad4346fde70a4736342c4ad87bd713641eb9808 + md5: 80c07c68d2f6870250959dcc95b209d1 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 license: BSD-3-Clause license_family: BSD purls: [] - size: 37087 - timestamp: 1757334557450 + size: 37135 + timestamp: 1758626800002 - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b md5: 0f03292cc56bf91a077a134ea8747118 @@ -5793,9 +5707,9 @@ packages: purls: [] size: 421195 timestamp: 1753948426421 -- conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - sha256: 373f2973b8a358528b22be5e8d84322c165b4c5577d24d94fd67ad1bb0a0f261 - md5: 08bfa5da6e242025304b206d152479ef +- conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + sha256: 0fccf2d17026255b6e10ace1f191d0a2a18f2d65088fd02430be17c701f8ffe0 + md5: 8a86073cf3b343b87d03f41790d8b4e5 depends: - ucrt constrains: @@ -5803,8 +5717,8 @@ packages: - msys2-conda-epoch <0.0a0 license: MIT AND BSD-3-Clause-Clear purls: [] - size: 35794 - timestamp: 1737099561703 + size: 36621 + timestamp: 1759768399557 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c md5: 5aa797f8787fe7a17d1b0821485b5adc @@ -5814,14 +5728,14 @@ packages: purls: [] size: 100393 timestamp: 1702724383534 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_0.conda - sha256: c3c2c74bd917d83b26c102b18bde97759c23f24e0260beb962acf7385627fc38 - md5: 5262552eb2f0d0b443adcfa265d97f0a +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda + sha256: 8890c03908a407649ac99257b63176b61d10dfa3468aa3db1994ac0973dc2803 + md5: 1d6e5fbbe84eebcd62e7cdccec799ce8 depends: - icu >=75.1,<76.0a0 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.1,<6.0a0 - - libxml2-16 2.15.0 h06f855e_0 + - libxml2-16 2.15.0 h06f855e_1 - libzlib >=1.3.1,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -5829,11 +5743,11 @@ packages: license: MIT license_family: MIT purls: [] - size: 42985 - timestamp: 1757953736703 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_0.conda - sha256: 15337581264464842ff28f616422b786161bee0169610ff292e0ea75fa78dba8 - md5: a1071825a90769083fce8dbcefcccd65 + size: 43274 + timestamp: 1758641414853 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_1.conda + sha256: f29159eef5af2adffe2fef2d89ff2f6feda07e194883f47a4cf366e9608fb91b + md5: a5d1a1f8745fcd93f39a4b80f389962f depends: - icu >=75.1,<76.0a0 - libiconv >=1.18,<2.0a0 @@ -5847,8 +5761,8 @@ packages: license: MIT license_family: MIT purls: [] - size: 512772 - timestamp: 1757953703099 + size: 518883 + timestamp: 1758641386772 - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 md5: edb0dca6bc32e4f4789199455a1dbeb8 @@ -5900,21 +5814,21 @@ packages: purls: [] size: 55476 timestamp: 1727963768015 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-20.1.8-hfa2b4ca_2.conda - sha256: 8970b7f9057a1c2c18bfd743c6f5ce73b86197d7724423de4fa3d03911d5874b - md5: 2dc2edf349464c8b83a576175fc2ad42 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda + sha256: 54826ea90c80ca04640b0fc1a0b3aabfd0f4e60e03c270b2a919a3655f21bc78 + md5: b1dd38bdf96540a6dedf0d196108c9a1 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - intel-openmp <0.0a0 - - openmp 20.1.8|20.1.8.* + - openmp 21.1.3|21.1.3.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 344490 - timestamp: 1756145011384 + size: 347945 + timestamp: 1760282911326 - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl name: lmfit version: 1.3.4 @@ -5950,6 +5864,31 @@ packages: - pytest-cov ; extra == 'test' - lmfit[dev,doc,test] ; extra == 'all' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + name: mando + version: 0.7.1 + sha256: 26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a + requires_dist: + - six + - argparse ; python_full_version < '2.7' + - funcsigs ; python_full_version < '3.3' + - rst2ansi ; extra == 'restructuredtext' +- pypi: https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl + name: markdown + version: '3.9' + sha256: 9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280 + requires_dist: + - importlib-metadata>=4.4 ; python_full_version < '3.10' + - coverage ; extra == 'testing' + - pyyaml ; extra == 'testing' + - mkdocs>=1.6 ; extra == 'docs' + - mkdocs-nature>=0.6 ; extra == 'docs' + - mdx-gh-links>=0.2 ; extra == 'docs' + - mkdocstrings[python] ; extra == 'docs' + - mkdocs-gen-files ; extra == 'docs' + - mkdocs-section-index ; extra == 'docs' + - mkdocs-literate-nav ; extra == 'docs' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl name: markdown-it-py version: 4.0.0 @@ -5983,138 +5922,50 @@ packages: - pytest-regressions ; extra == 'testing' - requests ; extra == 'testing' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda - sha256: 0291d90706ac6d3eea73e66cd290ef6d805da3fad388d1d476b8536ec92ca9a8 - md5: 6565a715337ae279e351d0abd8ffe88a - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 25354 - timestamp: 1733219879408 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py313h8060acc_1.conda - sha256: d812caf52efcea7c9fd0eafb21d45dadfd0516812f667b928bee50e87634fae5 - md5: 21b62c55924f01b6eef6827167b46acb - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24856 - timestamp: 1733219782830 -- conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py311ha3cf9ac_1.conda - sha256: e9965b5d4c29b17b1512035b24a7c126ed7bdb6b39103b52cae099d5bb4194a9 - md5: 1d6596ca7c7b66215c5c0d58b3cb0dd3 - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24688 - timestamp: 1733219887972 -- conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py313h717bdf5_1.conda - sha256: 297242943522a907c270bc2f191d16142707d970541b9a093640801b767d7aa7 - md5: a6fbde71416d6eb9898fcabf505a85c5 - depends: - - __osx >=10.13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24363 - timestamp: 1733219815199 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py311h4921393_1.conda - sha256: 4f738a7c80e34e5e5d558e946b06d08e7c40e3cc4bdf08140bf782c359845501 - md5: 249e2f6f5393bb6b36b3d3a3eebdcdf9 - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24976 - timestamp: 1733219849253 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py313ha9b7d5b_1.conda - sha256: 81759af8a9872c8926af3aa59dc4986eee90a0956d1ec820b42ac4f949a71211 - md5: 3acf05d8e42ff0d99820d2d889776fff - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 24757 - timestamp: 1733219916634 -- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py311h5082efb_1.conda - sha256: 6f756e13ccf1a521d3960bd3cadddf564e013e210eaeced411c5259f070da08e - md5: c1f2ddad665323278952a453912dc3bd - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 28238 - timestamp: 1733220208800 -- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.2-py313hb4c8b1a_1.conda - sha256: f16cb398915f52d582bcea69a16cf69a56dab6ea2fab6f069da9c2c10f09534c - md5: ec9ecf6ee4cceb73a0c9a8cdfdf58bed - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 27930 - timestamp: 1733220059655 -- pypi: https://files.pythonhosted.org/packages/80/d6/5d3665aa44c49005aaacaa68ddea6fcb27345961cd538a98bb0177934ede/matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl + name: markupsafe + version: 3.0.3 + sha256: 9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: 1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: 0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl + name: markupsafe + version: 3.0.3 + sha256: de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + name: markupsafe + version: 3.0.3 + sha256: 116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: markupsafe + version: 3.0.3 + sha256: ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl + name: markupsafe + version: 3.0.3 + sha256: 4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl name: matplotlib - version: 3.10.6 - sha256: 905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f + version: 3.10.7 + sha256: 1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6123,17 +5974,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/8c/af/30ddefe19ca67eebd70047dabf50f899eaff6f3c5e6a1a7edaecaf63f794/matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: matplotlib - version: 3.10.6 - sha256: 7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76 + version: 3.10.7 + sha256: 2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6142,17 +5993,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: matplotlib - version: 3.10.6 - sha256: 819e409653c1106c8deaf62e6de6b8611449c2cd9939acb0d7d4e57a3d95cc7a + version: 3.10.7 + sha256: b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6161,17 +6012,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl name: matplotlib - version: 3.10.6 - sha256: 59c8ac8382fefb9cb71308dde16a7c487432f5255d8f1fd32473523abecfecdf + version: 3.10.7 + sha256: 6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6180,17 +6031,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d3/29/4a8650a3dcae97fa4f375d46efcb25920d67b512186f8a6788b896062a81/matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl name: matplotlib - version: 3.10.6 - sha256: 942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6 + version: 3.10.7 + sha256: 37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6199,17 +6050,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl name: matplotlib - version: 3.10.6 - sha256: 84e82d9e0fd70c70bc55739defbd8055c54300750cbacf4740c9673a24d6933a + version: 3.10.7 + sha256: 744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6218,17 +6069,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fc/8e/0a18d6d7d2d0a2e66585032a760d13662e5250c784d53ad50434e9560991/matplotlib-3.10.6-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl name: matplotlib - version: 3.10.6 - sha256: abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e + version: 3.10.7 + sha256: d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6237,17 +6088,17 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl name: matplotlib - version: 3.10.6 - sha256: 1b53bd6337eba483e2e7d29c5ab10eee644bc3a2491ec67cc55f7b44583ffb18 + version: 3.10.7 + sha256: 53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6256,43 +6107,190 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - sha256: 69b7dc7131703d3d60da9b0faa6dd8acbf6f6c396224cf6aef3e855b8c0c41c6 - md5: af6ab708897df59bd6e7283ceab1b56b - depends: - - python >=3.9 +- pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl + name: matplotlib-inline + version: 0.1.7 + sha256: df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca + requires_dist: - traitlets - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/matplotlib-inline?source=hash-mapping - size: 14467 - timestamp: 1733417051523 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + name: mdit-py-plugins + version: 0.5.0 + sha256: 07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f + requires_dist: + - markdown-it-py>=2.0.0,<5.0.0 + - pre-commit ; extra == 'code-style' + - myst-parser ; extra == 'rtd' + - sphinx-book-theme ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl name: mdurl version: 0.1.2 sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.1.4-pyhcf101f3_0.conda - sha256: 609ea628ace5c6cdbdce772704e6cb159ead26969bb2f386ca1757632b0f74c6 - md5: f5a4d548d1d3bdd517260409fc21e205 - depends: - - python >=3.10 - - typing_extensions - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/mistune?source=hash-mapping - size: 72996 - timestamp: 1756495311698 +- pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + name: mergedeep + version: 1.3.4 + sha256: 70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl + name: mistune + version: 3.1.4 + sha256: 93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d + requires_dist: + - typing-extensions ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + name: mkdocs + version: 1.6.1 + sha256: db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e + requires_dist: + - click>=7.0 + - colorama>=0.4 ; sys_platform == 'win32' + - ghp-import>=1.0 + - importlib-metadata>=4.4 ; python_full_version < '3.10' + - jinja2>=2.11.1 + - markdown>=3.3.6 + - markupsafe>=2.0.1 + - mergedeep>=1.3.4 + - mkdocs-get-deps>=0.2.0 + - packaging>=20.5 + - pathspec>=0.11.1 + - pyyaml-env-tag>=0.1 + - pyyaml>=5.1 + - watchdog>=2.0 + - babel>=2.9.0 ; extra == 'i18n' + - babel==2.9.0 ; extra == 'min-versions' + - click==7.0 ; extra == 'min-versions' + - colorama==0.4 ; sys_platform == 'win32' and extra == 'min-versions' + - ghp-import==1.0 ; extra == 'min-versions' + - importlib-metadata==4.4 ; python_full_version < '3.10' and extra == 'min-versions' + - jinja2==2.11.1 ; extra == 'min-versions' + - markdown==3.3.6 ; extra == 'min-versions' + - markupsafe==2.0.1 ; extra == 'min-versions' + - mergedeep==1.3.4 ; extra == 'min-versions' + - mkdocs-get-deps==0.2.0 ; extra == 'min-versions' + - packaging==20.5 ; extra == 'min-versions' + - pathspec==0.11.1 ; extra == 'min-versions' + - pyyaml-env-tag==0.1 ; extra == 'min-versions' + - pyyaml==5.1 ; extra == 'min-versions' + - watchdog==2.0 ; extra == 'min-versions' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/71/26/4d39d52ea2219604053a4d05b98e90d6a335511cc01806436ec4886b1028/mkdocs_autorefs-1.2.0-py3-none-any.whl + name: mkdocs-autorefs + version: 1.2.0 + sha256: d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f + requires_dist: + - markdown>=3.3 + - markupsafe>=2.0.1 + - mkdocs>=1.1 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl + name: mkdocs-get-deps + version: 0.2.0 + sha256: 2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134 + requires_dist: + - importlib-metadata>=4.3 ; python_full_version < '3.10' + - mergedeep>=1.3.4 + - platformdirs>=2.2.0 + - pyyaml>=5.1 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + name: mkdocs-jupyter + version: 0.25.1 + sha256: 3f679a857609885d322880e72533ef5255561bbfdb13cfee2a1e92ef4d4ad8d8 + requires_dist: + - ipykernel>6.0.0,<7.0.0 + - jupytext>1.13.8,<2 + - mkdocs-material>9.0.0 + - mkdocs>=1.4.0,<2 + - nbconvert>=7.2.9,<8 + - pygments>2.12.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + name: mkdocs-markdownextradata-plugin + version: 0.2.6 + sha256: 34dd40870781784c75809596b2d8d879da783815b075336d541de1f150c94242 + requires_dist: + - mkdocs + - pyyaml + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/cc/82/6fdb9a7a04fb222f4849ffec1006f891a0280825a20314d11f3ccdee14eb/mkdocs_material-9.6.22-py3-none-any.whl + name: mkdocs-material + version: 9.6.22 + sha256: 14ac5f72d38898b2f98ac75a5531aaca9366eaa427b0f49fc2ecf04d99b7ad84 + requires_dist: + - babel~=2.10 + - backrefs~=5.7.post1 + - colorama~=0.4 + - jinja2~=3.1 + - markdown~=3.2 + - mkdocs-material-extensions~=1.3 + - mkdocs~=1.6 + - paginate~=0.5 + - pygments~=2.16 + - pymdown-extensions~=10.2 + - requests~=2.26 + - mkdocs-git-committers-plugin-2>=1.1,<3 ; extra == 'git' + - mkdocs-git-revision-date-localized-plugin~=1.2,>=1.2.4 ; extra == 'git' + - cairosvg~=2.6 ; extra == 'imaging' + - pillow>=10.2,<12.0 ; extra == 'imaging' + - mkdocs-minify-plugin~=0.7 ; extra == 'recommended' + - mkdocs-redirects~=1.2 ; extra == 'recommended' + - mkdocs-rss-plugin~=1.6 ; extra == 'recommended' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + name: mkdocs-material-extensions + version: 1.3.1 + sha256: adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + name: mkdocs-plugin-inline-svg + version: 0.1.0 + sha256: a5aab2d98a19b24019f8e650f54fc647c2f31e7d0e36fc5cf2d2161acc0ea49a + requires_dist: + - mkdocs + requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/cd/10/4c27c3063c2b3681a4b7942f8dbdeb4fa34fecb2c19b594e7345ebf4f86f/mkdocstrings-0.27.0-py3-none-any.whl + name: mkdocstrings + version: 0.27.0 + sha256: 6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332 + requires_dist: + - click>=7.0 + - jinja2>=2.11.1 + - markdown>=3.6 + - markupsafe>=1.1 + - mkdocs>=1.4 + - mkdocs-autorefs>=1.2 + - platformdirs>=2.2 + - pymdown-extensions>=6.3 + - importlib-metadata>=4.6 ; python_full_version < '3.10' + - typing-extensions>=4.1 ; python_full_version < '3.10' + - mkdocstrings-crystal>=0.3.4 ; extra == 'crystal' + - mkdocstrings-python-legacy>=0.2.1 ; extra == 'python-legacy' + - mkdocstrings-python>=0.5.2 ; extra == 'python' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/51/23/d02d86553327296c3bf369d444194ea83410cce8f0e690565264f37f3261/mkdocstrings_python-1.13.0-py3-none-any.whl + name: mkdocstrings-python + version: 1.13.0 + sha256: b88bbb207bab4086434743849f8e796788b373bd32e7bfefbf8560ac45d88f97 + requires_dist: + - mkdocstrings>=0.26 + - mkdocs-autorefs>=1.2 + - griffe>=0.49 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda sha256: ce841e7c3898764154a9293c0f92283c1eb28cdacf7a164c94b632a6af675d91 md5: 5cddc979c74b90cf5e5cda4f97d5d8bb @@ -6324,6 +6322,46 @@ packages: - sphinx ; extra == 'docs' - gmpy2>=2.1.0a4 ; platform_python_implementation != 'PyPy' and extra == 'gmpy' - pytest>=4.6 ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl name: msgspec version: 0.19.0 @@ -6620,70 +6658,70 @@ packages: - tomli ; python_full_version < '3.11' and extra == 'dev' - tomli-w ; extra == 'dev' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl name: multidict - version: 6.6.4 - sha256: e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52 + version: 6.7.0 + sha256: 14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl name: multidict - version: 6.6.4 - sha256: 22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657 + version: 6.7.0 + sha256: 95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl name: multidict - version: 6.6.4 - sha256: 6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb + version: 6.7.0 + sha256: 30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl name: multidict - version: 6.6.4 - sha256: d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e + version: 6.7.0 + sha256: 4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl name: multidict - version: 6.6.4 - sha256: 497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f + version: 6.7.0 + sha256: 7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: multidict - version: 6.6.4 - sha256: 40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf + version: 6.7.0 + sha256: 3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: multidict - version: 6.6.4 - sha256: 66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e + version: 6.7.0 + sha256: 9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32 requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl name: multidict - version: 6.6.4 - sha256: 5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da + version: 6.7.0 + sha256: 9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl name: narwhals - version: 2.5.0 - sha256: 7e213f9ca7db3f8bf6f7eff35eaee6a1cf80902997e1b78d49b7755775d8f423 + version: 2.8.0 + sha256: 6304856676ba4a79fd34148bda63aed8060dd6edb1227edf3659ce5e091de73c requires_dist: - cudf>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' - - duckdb>=1.0 ; extra == 'duckdb' + - duckdb>=1.1 ; extra == 'duckdb' - ibis-framework>=6.0.0 ; extra == 'ibis' - packaging ; extra == 'ibis' - pyarrow-hotfix ; extra == 'ibis' @@ -6696,66 +6734,149 @@ packages: - pyspark[connect]>=3.5.0 ; extra == 'pyspark-connect' - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.2-pyhd8ed1ab_0.conda - sha256: a20cff739d66c2f89f413e4ba4c6f6b59c50d5c30b5f0d840c13e8c9c2df9135 - md5: 6bb0d77277061742744176ab555b723c - depends: - - jupyter_client >=6.1.12 - - jupyter_core >=4.12,!=5.0.* - - nbformat >=5.1 - - python >=3.8 - - traitlets >=5.4 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbclient?source=hash-mapping - size: 28045 - timestamp: 1734628936013 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.6-pyh29332c3_0.conda - sha256: dcccb07c5a1acb7dc8be94330e62d54754c0e9c9cb2bb6865c8e3cfe44cf5a58 - md5: d24beda1d30748afcc87c429454ece1b - depends: +- pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl + name: nbclient + version: 0.10.2 + sha256: 4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d + requires_dist: + - jupyter-client>=6.1.12 + - jupyter-core>=4.12,!=5.0.* + - nbformat>=5.1 + - traitlets>=5.4 + - pre-commit ; extra == 'dev' + - autodoc-traits ; extra == 'docs' + - flaky ; extra == 'docs' + - ipykernel>=6.19.3 ; extra == 'docs' + - ipython ; extra == 'docs' + - ipywidgets ; extra == 'docs' + - mock ; extra == 'docs' + - moto ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbconvert>=7.1.0 ; extra == 'docs' + - pytest-asyncio ; extra == 'docs' + - pytest-cov>=4.0 ; extra == 'docs' + - pytest>=7.0,<8 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx>=1.7 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - testpath ; extra == 'docs' + - xmltodict ; extra == 'docs' + - flaky ; extra == 'test' + - ipykernel>=6.19.3 ; extra == 'test' + - ipython ; extra == 'test' + - ipywidgets ; extra == 'test' + - nbconvert>=7.1.0 ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest>=7.0,<8 ; extra == 'test' + - testpath ; extra == 'test' + - xmltodict ; extra == 'test' + requires_python: '>=3.9.0' +- pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl + name: nbconvert + version: 7.16.6 + sha256: 1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b + requires_dist: - beautifulsoup4 - - bleach-with-css !=5.0.0 + - bleach[css]!=5.0.0 - defusedxml - - importlib-metadata >=3.6 - - jinja2 >=3.0 - - jupyter_core >=4.7 - - jupyterlab_pygments - - markupsafe >=2.0 - - mistune >=2.0.3,<4 - - nbclient >=0.5.0 - - nbformat >=5.7 + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - jinja2>=3.0 + - jupyter-core>=4.7 + - jupyterlab-pygments + - markupsafe>=2.0 + - mistune>=2.0.3,<4 + - nbclient>=0.5.0 + - nbformat>=5.7 - packaging - - pandocfilters >=1.4.1 - - pygments >=2.4.1 - - python >=3.9 - - traitlets >=5.1 - - python - constrains: - - pandoc >=2.9.2,<4.0.0 - - nbconvert ==7.16.6 *_0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbconvert?source=hash-mapping - size: 200601 - timestamp: 1738067871724 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 - md5: bbe1963f1e47f594070ffe87cdf612ea - depends: - - jsonschema >=2.6 - - jupyter_core >=4.12,!=5.0.* - - python >=3.9 - - python-fastjsonschema >=2.15 - - traitlets >=5.1 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/nbformat?source=hash-mapping - size: 100945 - timestamp: 1733402844974 + - pandocfilters>=1.4.1 + - pygments>=2.4.1 + - traitlets>=5.1 + - flaky ; extra == 'all' + - ipykernel ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets>=7.5 ; extra == 'all' + - myst-parser ; extra == 'all' + - nbsphinx>=0.2.12 ; extra == 'all' + - playwright ; extra == 'all' + - pydata-sphinx-theme ; extra == 'all' + - pyqtwebengine>=5.15 ; extra == 'all' + - pytest>=7 ; extra == 'all' + - sphinx==5.0.2 ; extra == 'all' + - sphinxcontrib-spelling ; extra == 'all' + - tornado>=6.1 ; extra == 'all' + - ipykernel ; extra == 'docs' + - ipython ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbsphinx>=0.2.12 ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx==5.0.2 ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - pyqtwebengine>=5.15 ; extra == 'qtpdf' + - pyqtwebengine>=5.15 ; extra == 'qtpng' + - tornado>=6.1 ; extra == 'serve' + - flaky ; extra == 'test' + - ipykernel ; extra == 'test' + - ipywidgets>=7.5 ; extra == 'test' + - pytest>=7 ; extra == 'test' + - playwright ; extra == 'webpdf' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl + name: nbformat + version: 5.10.4 + sha256: 3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b + requires_dist: + - fastjsonschema>=2.15 + - jsonschema>=2.6 + - jupyter-core>=4.12,!=5.0.* + - traitlets>=5.1 + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinxcontrib-github-alt ; extra == 'docs' + - sphinxcontrib-spelling ; extra == 'docs' + - pep440 ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest ; extra == 'test' + - testpath ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + name: nbmake + version: 1.5.5 + sha256: c6fbe6e48b60cacac14af40b38bf338a3b88f47f085c54ac5b8639ff0babaf4b + requires_dist: + - ipykernel>=5.4.0 + - nbclient>=0.6.6 + - nbformat>=5.0.4 + - pygments>=2.7.3 + - pytest>=6.1.0 + requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + name: nbqa + version: 1.9.1 + sha256: 95552d2f6c2c038136252a805aa78d85018aef922586270c3a074332737282e5 + requires_dist: + - autopep8>=1.5 + - ipython>=7.8.0 + - tokenize-rt>=3.2.0 + - tomli + - black ; extra == 'toolchain' + - blacken-docs ; extra == 'toolchain' + - flake8 ; extra == 'toolchain' + - isort ; extra == 'toolchain' + - jupytext ; extra == 'toolchain' + - mypy ; extra == 'toolchain' + - pylint ; extra == 'toolchain' + - pyupgrade ; extra == 'toolchain' + - ruff ; extra == 'toolchain' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl + name: nbstripout + version: 0.8.1 + sha256: 79a8c8da488d98c54c112fa87185045f0271a97d84f1d46918d6a3ee561b30e7 + requires_dist: + - nbformat + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 md5: 47e340acb35de30501a76c7c799c41d7 @@ -6784,49 +6905,47 @@ packages: purls: [] size: 797030 timestamp: 1738196177597 -- conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 - md5: 598fd7d4d0de2455fb74f56063969a97 - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/nest-asyncio?source=hash-mapping - size: 11543 - timestamp: 1733325673691 -- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.4.1-heeeca48_0.conda - sha256: 1239ba36ea69eefcc55f107fe186810b59488923544667175f6976fa4903c8c9 - md5: d629b201c3fbc0c203ca0ad7b03f22ce +- pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl + name: nest-asyncio + version: 1.6.0 + sha256: 87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c + requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + name: nodeenv + version: 1.9.1 + sha256: ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' +- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda + sha256: 6abb823fd4d28e6474f40dfcf38e772e5869ee755be855cf5d2c0d49f888c75e + md5: 8a2a73951c1ea275e76fb1b92d97ff3e depends: - - libgcc >=14 - __glibc >=2.28,<3.0.a0 - libstdcxx >=14 - libgcc >=14 - libuv >=1.51.0,<2.0a0 + - openssl >=3.5.3,<4.0a0 - icu >=75.1,<76.0a0 - - openssl >=3.5.1,<4.0a0 - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT purls: [] - size: 25669735 - timestamp: 1752839464718 -- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.4.1-h2e7699b_0.conda - sha256: 1c9571726b5b5e85acfba50dda7ae9b22d2b29e590159a581bafde5bf2e04621 - md5: 9993063cfe84cf1fa928c7d021bd01a0 + size: 25557455 + timestamp: 1759064044872 +- conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda + sha256: 2819269de6183a7187ba8bb7b56bb3c8d0a5b68a017ba7d1f77aed88eef523f2 + md5: 6ef8bcc1b996eb3c8a23ef9a808044a3 depends: - - __osx >=10.15 - libcxx >=19 - - openssl >=3.5.1,<4.0a0 - - libuv >=1.51.0,<2.0a0 + - __osx >=10.15 - libzlib >=1.3.1,<2.0a0 - icu >=75.1,<76.0a0 + - libuv >=1.51.0,<2.0a0 + - openssl >=3.5.3,<4.0a0 license: MIT license_family: MIT purls: [] - size: 18918546 - timestamp: 1752839437994 + size: 18750023 + timestamp: 1759063982956 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda sha256: c79d2c81f80a9adedc77362f2e8b10879ed0f9806deb6ba2464c1287a05f0b9b md5: 463a537de602f8558604f27395b323d0 @@ -6842,69 +6961,68 @@ packages: purls: [] size: 17949155 timestamp: 1752839389217 -- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.4.1-he453025_0.conda - sha256: 1bb0d9e370bb0ffa2071ccfdd0ef3cb90bd183b07c67b646d1aa5c743004d233 - md5: cde0d5793a73ab343b5764fa6c002771 +- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda + sha256: 8d60a2e7a49cc9db7e8032db60333609ba9e9b8e7081843ea4e6a05d7ef12bdb + md5: da14fa3bcb863b34888a9c35991c3c81 license: MIT license_family: MIT purls: [] - size: 29967122 - timestamp: 1752839409586 -- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - sha256: 7b920e46b9f7a2d2aa6434222e5c8d739021dbc5cc75f32d124a8191d86f9056 - md5: e7f89ea5f7ea9401642758ff50a2d9c1 - depends: - - jupyter_server >=1.8,<3 - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/notebook-shim?source=hash-mapping - size: 16817 - timestamp: 1733408419340 -- pypi: https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl + size: 30246134 + timestamp: 1759064073124 +- pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl + name: notebook-shim + version: 0.2.4 + sha256: 411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef + requires_dist: + - jupyter-server>=1.8,<3 + - pytest ; extra == 'test' + - pytest-console-scripts ; extra == 'test' + - pytest-jupyter ; extra == 'test' + - pytest-tornasync ; extra == 'test' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl name: numpy - version: 2.3.3 - sha256: ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa + version: 2.3.4 + sha256: 56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl name: numpy - version: 2.3.3 - sha256: bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6 + version: 2.3.4 + sha256: a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl name: numpy - version: 2.3.3 - sha256: f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf + version: 2.3.4 + sha256: c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl name: numpy - version: 2.3.3 - sha256: e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569 + version: 2.3.4 + sha256: e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl name: numpy - version: 2.3.3 - sha256: 0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d + version: 2.3.4 + sha256: faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: numpy - version: 2.3.3 - sha256: f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf + version: 2.3.4 + sha256: fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953 requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: numpy - version: 2.3.3 - sha256: 5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93 + version: 2.3.4 + sha256: a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl name: numpy - version: 2.3.3 - sha256: d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7 + version: 2.3.4 + sha256: 7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.2-h26f9b46_0.conda - sha256: c9f54d4e8212f313be7b02eb962d0cb13a8dae015683a403d3accd4add3e520e - md5: ffffb341206dd0dab0c36053c048d621 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda + sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549 + md5: 14edad12b59ccbfa3910d42c72adc2a0 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates @@ -6912,33 +7030,33 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 3128847 - timestamp: 1754465526100 -- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.2-h6e31bce_0.conda - sha256: 8be57a11019666aa481122c54e29afd604405b481330f37f918e9fbcd145ef89 - md5: 22f5d63e672b7ba467969e9f8b740ecd + size: 3119624 + timestamp: 1759324353651 +- conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda + sha256: 3ce8467773b2472b2919412fd936413f05a9b10c42e52c27bbddc923ef5da78a + md5: 075eaad78f96bbf5835952afbe44466e depends: - __osx >=10.13 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 2743708 - timestamp: 1754466962243 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.2-he92f556_0.conda - sha256: f6d1c87dbcf7b39fad24347570166dade1c533ae2d53c60a70fa4dc874ef0056 - md5: bcb0d87dfbc199d0a461d2c7ca30b3d8 + size: 2747108 + timestamp: 1759326402264 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda + sha256: f0512629f9589392c2fb9733d11e753d0eab8fc7602f96e4d7f3bd95c783eb07 + md5: 71118318f37f717eefe55841adb172fd depends: - __osx >=11.0 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 3074848 - timestamp: 1754465710470 -- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.2-h725018a_0.conda - sha256: 2413f3b4606018aea23acfa2af3c4c46af786739ab4020422e9f0c2aec75321b - md5: 150d3920b420a27c0848acca158f94dc + size: 3067808 + timestamp: 1759324763146 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda + sha256: 5ddc1e39e2a8b72db2431620ad1124016f3df135f87ebde450d235c212a61994 + md5: f28ffa510fe055ab518cbd9d6ddfea23 depends: - ca-certificates - ucrt >=10.0.20348.0 @@ -6947,36 +7065,32 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 9275175 - timestamp: 1754467904482 -- conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c - md5: e51f1e4089cad105b6cac64bd8166587 - depends: - - python >=3.9 - - typing_utils - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/overrides?source=hash-mapping - size: 30139 - timestamp: 1734587755455 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda - sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 - md5: 58335b26c38bf4a20f399384c33cbcf9 - depends: - - python >=3.8 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/packaging?source=hash-mapping - size: 62477 - timestamp: 1745345660407 -- pypi: https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl + size: 9218823 + timestamp: 1759326176247 +- pypi: https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl + name: overrides + version: 7.7.0 + sha256: c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49 + requires_dist: + - typing ; python_full_version < '3.5' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl + name: packaging + version: '25.0' + sha256: 29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + name: paginate + version: 0.5.7 + sha256: b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591 + requires_dist: + - pytest ; extra == 'dev' + - tox ; extra == 'dev' + - black ; extra == 'lint' +- pypi: https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: pandas - version: 2.3.2 - sha256: 12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b + version: 2.3.3 + sha256: 318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7064,10 +7178,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl name: pandas - version: 2.3.2 - sha256: 0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e + version: 2.3.3 + sha256: bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8 requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7155,10 +7269,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/38/18/48f10f1cc5c397af59571d638d211f494dba481f449c19adbd282aa8f4ca/pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl name: pandas - version: 2.3.2 - sha256: 76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4 + version: 2.3.3 + sha256: f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7246,10 +7360,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl name: pandas - version: 2.3.2 - sha256: c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9 + version: 2.3.3 + sha256: f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7337,10 +7451,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/7a/59/f3e010879f118c2d400902d2d871c2226cef29b08c09fb8dc41111730400/pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl name: pandas - version: 2.3.2 - sha256: 1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743 + version: 2.3.3 + sha256: 8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45 requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7428,10 +7542,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8b/ef/0e2ffb30b1f7fbc9a588bd01e3c14a0d96854d09a887e15e30cc19961227/pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: pandas - version: 2.3.2 - sha256: 1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e + version: 2.3.3 + sha256: b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7519,10 +7633,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl name: pandas - version: 2.3.2 - sha256: 4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b + version: 2.3.3 + sha256: 602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523 requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7610,10 +7724,10 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a7/e7/ae86261695b6c8a36d6a4c8d5f9b9ede8248510d689a2f379a18354b37d7/pandas-2.3.2-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl name: pandas - version: 2.3.2 - sha256: 9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f + version: 2.3.3 + sha256: 56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713 requires_dist: - numpy>=1.22.4 ; python_full_version < '3.11' - numpy>=1.23.2 ; python_full_version == '3.11.*' @@ -7701,55 +7815,37 @@ packages: - xlsxwriter>=3.0.5 ; extra == 'all' - zstandard>=0.19.0 ; extra == 'all' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f - md5: 457c2c8c08e54905d6954e79cb5b5db9 - depends: - - python !=3.0,!=3.1,!=3.2,!=3.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pandocfilters?source=hash-mapping - size: 11627 - timestamp: 1631603397334 -- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - sha256: 30de7b4d15fbe53ffe052feccde31223a236dae0495bab54ab2479de30b2990f - md5: a110716cdb11cf51482ff4000dc253d7 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/parso?source=hash-mapping - size: 81562 - timestamp: 1755974222274 -- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a - md5: d0d408b1f18883a944376da5cf8101ea - depends: - - ptyprocess >=0.5 - - python >=3.9 - license: ISC - purls: - - pkg:pypi/pexpect?source=hash-mapping - size: 53561 - timestamp: 1733302019362 -- conda: https://conda.anaconda.org/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda - sha256: e2ac3d66c367dada209fc6da43e645672364b9fd5f9d28b9f016e24b81af475b - md5: 11a9d1d09a3615fc07c3faf79bc0b943 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pickleshare?source=hash-mapping - size: 11748 - timestamp: 1733327448200 -- pypi: https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl + name: pandocfilters + version: 1.5.1 + sha256: 93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl + name: parso + version: 0.8.5 + sha256: 646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887 + requires_dist: + - pytest ; extra == 'testing' + - docopt ; extra == 'testing' + - flake8==5.0.4 ; extra == 'qa' + - mypy==0.971 ; extra == 'qa' + - types-setuptools==67.2.0.1 ; extra == 'qa' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl + name: pathspec + version: 0.12.1 + sha256: a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl + name: pexpect + version: 4.9.0 + sha256: 7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 + requires_dist: + - ptyprocess>=0.5 +- pypi: https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl name: pillow - version: 11.3.0 - sha256: ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c + version: 12.0.0 + sha256: 0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7760,6 +7856,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7767,19 +7866,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl name: pillow - version: 11.3.0 - sha256: 0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51 + version: 12.0.0 + sha256: a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7790,6 +7888,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7797,19 +7898,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: pillow - version: 11.3.0 - sha256: 9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288 + version: 12.0.0 + sha256: f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7820,6 +7920,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7827,19 +7930,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: pillow - version: 11.3.0 - sha256: 13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8 + version: 12.0.0 + sha256: bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7850,6 +7952,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7857,19 +7962,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl name: pillow - version: 11.3.0 - sha256: 1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722 + version: 12.0.0 + sha256: b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7880,6 +7984,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7887,19 +7994,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl name: pillow - version: 11.3.0 - sha256: 1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac + version: 12.0.0 + sha256: 4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7910,6 +8016,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7917,19 +8026,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl name: pillow - version: 11.3.0 - sha256: 106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f + version: 12.0.0 + sha256: 5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7940,6 +8048,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7947,19 +8058,18 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl name: pillow - version: 11.3.0 - sha256: 7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd + version: 12.0.0 + sha256: c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -7970,6 +8080,9 @@ packages: - sphinxext-opengraph ; extra == 'docs' - olefile ; extra == 'fpx' - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' - pyarrow ; extra == 'test-arrow' - check-manifest ; extra == 'tests' - coverage>=7.4.2 ; extra == 'tests' @@ -7977,15 +8090,14 @@ packages: - markdown2 ; extra == 'tests' - olefile ; extra == 'tests' - packaging ; extra == 'tests' - - pyroma ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' - pytest ; extra == 'tests' - pytest-cov ; extra == 'tests' - pytest-timeout ; extra == 'tests' - pytest-xdist ; extra == 'tests' - trove-classifiers>=2024.10.12 ; extra == 'tests' - - typing-extensions ; python_full_version < '3.10' and extra == 'typing' - defusedxml ; extra == 'xmp' - requires_python: '>=3.9' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda sha256: 20fe420bb29c7e655988fd0b654888e6d7755c1d380f82ca2f1bd2493b95d650 md5: e7ab34d5a93e0819b62563c78635d937 @@ -7994,7 +8106,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pip?source=compressed-mapping + - pkg:pypi/pip?source=hash-mapping size: 1179951 timestamp: 1753925011027 - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh8b19718_0.conda @@ -8022,22 +8134,26 @@ packages: - returns>=0.23 - tomli>=2 ; python_full_version < '3.11' requires_python: '>=3.9,<4.0' -- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.4.0-pyhcf101f3_0.conda - sha256: dfe0fa6e351d2b0cef95ac1a1533d4f960d3992f9e0f82aeb5ec3623a699896b - md5: cc9d9a3929503785403dbfad9f707145 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/platformdirs?source=compressed-mapping - size: 23653 - timestamp: 1756227402815 -- pypi: https://files.pythonhosted.org/packages/95/a9/12e2dc726ba1ba775a2c6922d5d5b4488ad60bdab0888c337c194c8e6de8/plotly-6.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl + name: platformdirs + version: 4.5.0 + sha256: e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3 + requires_dist: + - furo>=2025.9.25 ; extra == 'docs' + - proselint>=0.14 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.2 ; extra == 'docs' + - sphinx>=8.2.3 ; extra == 'docs' + - appdirs==1.4.4 ; extra == 'test' + - covdefaults>=2.3 ; extra == 'test' + - pytest-cov>=7 ; extra == 'test' + - pytest-mock>=3.15.1 ; extra == 'test' + - pytest>=8.4.2 ; extra == 'test' + - mypy>=1.18.2 ; extra == 'type' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl name: plotly - version: 6.3.0 - sha256: 7ad806edce9d3cdd882eaebaf97c0c9e252043ed1ed3d382c3e3520ec07806d4 + version: 6.3.1 + sha256: 8b4420d1dcf2b040f5983eed433f95732ed24930e496d36eb70d211923532e64 requires_dist: - narwhals>=1.15.1 - packaging @@ -8074,6 +8190,17 @@ packages: - xarray ; extra == 'dev-optional' - plotly[dev-optional] ; extra == 'dev' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + name: pluggy + version: 1.6.0 + sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + requires_dist: + - pre-commit ; extra == 'dev' + - tox ; extra == 'dev' + - pytest ; extra == 'testing' + - pytest-benchmark ; extra == 'testing' + - coverage ; extra == 'testing' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl name: ply version: '3.11' @@ -8090,6 +8217,17 @@ packages: - paramiko>=2.7.0 ; extra == 'sftp' - xxhash>=1.4.3 ; extra == 'xxhash' requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl + name: pre-commit + version: 4.3.0 + sha256: 2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8 + requires_dist: + - cfgv>=2.0.0 + - identify>=1.0.0 + - nodeenv>=0.11.1 + - pyyaml>=5.1 + - virtualenv>=20.10.0 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/02/c7/5613524e606ea1688b3bdbf48aa64bafb6d0a4ac3750274c43b6158a390f/prettytable-3.16.0-py3-none-any.whl name: prettytable version: 3.16.0 @@ -8100,208 +8238,247 @@ packages: - pytest-cov ; extra == 'tests' - pytest-lazy-fixtures ; extra == 'tests' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.22.1-pyhd8ed1ab_0.conda - sha256: 454e2c0ef14accc888dd2cd2e8adb8c6a3a607d2d3c2f93962698b5718e6176d - md5: c64b77ccab10b822722904d889fa83b5 - depends: - - python >=3.9 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/prometheus-client?source=hash-mapping - size: 52641 - timestamp: 1748896836631 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae - md5: edb16f14d920fb3faf17f5ce582942d6 - depends: - - python >=3.10 +- pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl + name: prometheus-client + version: 0.23.1 + sha256: dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99 + requires_dist: + - twisted ; extra == 'twisted' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl + name: prompt-toolkit + version: 3.0.52 + sha256: 9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955 + requires_dist: - wcwidth - constrains: - - prompt_toolkit 3.0.52 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/prompt-toolkit?source=hash-mapping - size: 273927 - timestamp: 1756321848365 -- pypi: https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: propcache - version: 0.3.2 - sha256: a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9 + version: 0.4.1 + sha256: fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl name: propcache - version: 0.3.2 - sha256: c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df + version: 0.4.1 + sha256: cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl name: propcache - version: 0.3.2 - sha256: 9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252 + version: 0.4.1 + sha256: d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl name: propcache - version: 0.3.2 - sha256: 7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f + version: 0.4.1 + sha256: c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl name: propcache - version: 0.3.2 - sha256: 4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3 + version: 0.4.1 + sha256: 6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: propcache - version: 0.3.2 - sha256: e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05 + version: 0.4.1 + sha256: d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl name: propcache - version: 0.3.2 - sha256: 06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f + version: 0.4.1 + sha256: 364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl name: propcache - version: 0.3.2 - sha256: e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39 + version: 0.4.1 + sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py311h49ec1c0_1.conda - sha256: 729720d777b14329af411220fd305f78e8914356f963af0053420e1cf5e58a53 - md5: d30c3f3b089100634f93e97e5ee3aa85 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 483612 - timestamp: 1755851438911 -- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.0.0-py313h07c4f96_1.conda - sha256: 9182273778a10b2a82343c5c1c8b57f4551dd07d9a639585d468f4a7fe5ff1e8 - md5: 5a7c24c9dc49128731ae565cf598cde4 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 474571 - timestamp: 1755851494108 -- conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py311h13e5629_1.conda - sha256: ce1b788a4bae81bd2246c7284d620152832b899394259f2f938755e13f3afc6c - md5: d69888db150233f54b39919c12070de5 - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 490770 - timestamp: 1755851533700 -- conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.0.0-py313h585f44e_1.conda - sha256: df943fa46f030b043ca28bd939d7e4110273aa41197080a598da467cbd300c6b - md5: a1457ea8cfd6104cea63410320772abc - depends: - - __osx >=10.13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 480270 - timestamp: 1755851507696 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py311h3696347_1.conda - sha256: c21cd67c4037f232ba539f221839d1bcc7dbcc416d51f821fd319d91b5b61c3b - md5: c449b450f0c81bc09e6a59a07adf95a1 - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 493127 - timestamp: 1755851546773 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.0.0-py313hcdf3177_1.conda - sha256: 4964c94067fdf290d4790095ead992b2a3afb438bff8bd9b51c444d97fb63914 - md5: 1ce8cf644e210b54665d8e46850d7567 - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 484934 - timestamp: 1755851718841 -- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py311h3485c13_1.conda - sha256: f48c2e47fda7259235f8abb55d219c419df3cc52e2e15ee9ee17da20b86393e5 - md5: cd66a378835a5da422201faac2c114c7 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 499413 - timestamp: 1755851559633 -- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.0.0-py313h5ea7bf4_1.conda - sha256: 9e63542ffd8ac4104cff34e722019fc3eb6eef274c77740eef1d73056c56cade - md5: 00c2580acce9c51004818c6981c586d9 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 490305 - timestamp: 1755851561624 -- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 - md5: 7d9daffbb8d8e0af0f769dbbcd173a54 - depends: - - python >=3.9 - license: ISC - purls: - - pkg:pypi/ptyprocess?source=hash-mapping - size: 19457 - timestamp: 1733302371990 -- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 - md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pure-eval?source=hash-mapping - size: 16668 - timestamp: 1733569518868 -- pypi: https://files.pythonhosted.org/packages/a3/17/c476487ba903c7d793db633b4e8ca4a420ae272890302189d9402ba8ff85/py3dmol-2.5.2-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + name: psutil + version: 7.1.0 + sha256: 5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5 + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + name: psutil + version: 7.1.0 + sha256: 76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13 + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: psutil + version: 7.1.0 + sha256: 8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3 + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + name: psutil + version: 7.1.0 + sha256: 57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d + requires_dist: + - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' + - pytest-subtests ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - setuptools ; extra == 'dev' + - abi3audit ; extra == 'dev' + - black ; extra == 'dev' + - check-manifest ; extra == 'dev' + - coverage ; extra == 'dev' + - packaging ; extra == 'dev' + - pylint ; extra == 'dev' + - pyperf ; extra == 'dev' + - pypinfo ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - requests ; extra == 'dev' + - rstcheck ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - toml-sort ; extra == 'dev' + - twine ; extra == 'dev' + - virtualenv ; extra == 'dev' + - vulture ; extra == 'dev' + - wheel ; extra == 'dev' + - pyreadline ; os_name == 'nt' and extra == 'dev' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'dev' + - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest-xdist ; extra == 'test' + - setuptools ; extra == 'test' + - pywin32 ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl + name: ptyprocess + version: 0.7.0 + sha256: 4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 +- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl + name: pure-eval + version: 0.2.3 + sha256: 1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0 + requires_dist: + - pytest ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + name: py + version: 1.11.0 + sha256: 607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/0b/a6/e2e8535d8146bce05de6e0ecf1099a7e2887d840ae2a7b3a09385543fd02/py3dmol-2.5.3-py2.py3-none-any.whl name: py3dmol - version: 2.5.2 - sha256: b921940ff046cf7ca008a249cbd5debec561dcf337f1e5f3df7ac5d4a1954e8e + version: 2.5.3 + sha256: 5c1c9ee40bda82b91978e75f3c144be5b90cdf472e765bcef4890db76cc8f843 requires_dist: - ipython ; extra == 'ipython' - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -8355,221 +8532,124 @@ packages: - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl name: pycifrw version: 5.0.1 - sha256: 8632df76b99932408ab432e09b30aa9a6c390df884a5f34f1e1f76201e625156 - requires_dist: - - prettytable - - ply - - numpy -- pypi: https://files.pythonhosted.org/packages/f5/5c/b999ea3e64981018d52846b9b69193fa581a70cd255912cb6962a33a666a/pycifrw-5.0.1-cp311-cp311-macosx_11_0_arm64.whl - name: pycifrw - version: 5.0.1 - sha256: 9f200aae9d7cf9c8857326587ee949dad9de8480f38d98ac6d6ee2dff5ab2308 - requires_dist: - - prettytable - - ply - - numpy -- pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - name: pycifstar - version: 0.3.0 - sha256: 5892fdf16c83372ee5f32557127d5f36e14b0bbe520883a4e2e70365382f70ed -- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 - md5: 12c566707c80111f9799308d9e265aef - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pycparser?source=hash-mapping - size: 110100 - timestamp: 1733195786147 -- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a - md5: 6b6ece66ebcae2d5f326c77ef2c5a066 - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/pygments?source=hash-mapping - size: 889287 - timestamp: 1750615908735 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py311h2f44256_1.conda - sha256: d0800d251981e3d124bd960e799dd0c56e8b16cd1c6fb99a32990d62f492d754 - md5: 840b246b6b86c37321e79ca56518273f - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 486295 - timestamp: 1756813198835 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-11.1-py313h6971d95_1.conda - sha256: b71f6977e3dab2220530b15c8567da173db739be24badc2144d7da4d1755ca9c - md5: c6a63b6715af2ba52cc730a77f79e946 - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 488771 - timestamp: 1756813420666 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py311hf0763de_1.conda - sha256: c5cbb88710d690ae2f571037708026abb8f15de1a4d764e4b825aaad6c4549c8 - md5: 8c42827190081e9c29a1007454890067 - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 477833 - timestamp: 1756813465248 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-11.1-py313had225c5_1.conda - sha256: 9d4d32942bb2b11141836dadcebd7f54b60837447bc7eef3a9ad01a8adc11547 - md5: 60277e90c6cd9972a9e53f812e5ce83f - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 478509 - timestamp: 1756813300773 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py311hbc8e8a3_1.conda - sha256: 71394c6e3f77898e856dab863f283e92f65b03e02e12fcf228bd3715a3459431 - md5: ab2c871073a077d149d6da6d03d10e44 - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 385837 - timestamp: 1756824131805 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-11.1-py313h55ae1d7_1.conda - sha256: 1ae4eb045d62e94e842b803d7d1c1da0ac08cc0b14aeda96196b2f820fcba511 - md5: 15f9b82c8a6cbbf05e136adca38864b0 - depends: - - __osx >=10.13 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 382649 - timestamp: 1756824068318 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py311hc5b188e_1.conda - sha256: 62db0aa2d3a23ae2ca2c28f2c462e63c0911903169c72a139a9e659e3ed02b19 - md5: ee9a511c6ffa04ac806bd2987b2b093d - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 387176 - timestamp: 1756824223349 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-11.1-py313h4e140e3_1.conda - sha256: 139322c875d35199836802fc545b814ffb2001af2cee956b9da4d2fc7d3aec45 - md5: 1057463a9f16501716f978534aa2d838 - depends: - - __osx >=11.0 - - libffi >=3.4.6,<3.5.0a0 - - pyobjc-core 11.1.* - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 381682 - timestamp: 1756824258635 -- pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl + sha256: 8632df76b99932408ab432e09b30aa9a6c390df884a5f34f1e1f76201e625156 + requires_dist: + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/f5/5c/b999ea3e64981018d52846b9b69193fa581a70cd255912cb6962a33a666a/pycifrw-5.0.1-cp311-cp311-macosx_11_0_arm64.whl + name: pycifrw + version: 5.0.1 + sha256: 9f200aae9d7cf9c8857326587ee949dad9de8480f38d98ac6d6ee2dff5ab2308 + requires_dist: + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + name: pycifstar + version: 0.3.0 + sha256: 5892fdf16c83372ee5f32557127d5f36e14b0bbe520883a4e2e70365382f70ed +- pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + name: pycodestyle + version: 2.14.0 + sha256: dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + name: pycparser + version: '2.23' + sha256: e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + name: pygments + version: 2.19.2 + sha256: 86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + requires_dist: + - colorama>=0.4.6 ; extra == 'windows-terminal' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl + name: pymdown-extensions + version: 10.16.1 + sha256: d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d + requires_dist: + - markdown>=3.6 + - pyyaml + - pygments>=2.19.1 ; extra == 'extra' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl name: pyparsing - version: 3.2.4 - sha256: 91d0fcde680d42cd031daf3a6ba20da3107e08a75de50da58360e7d94ab24d36 + version: 3.2.5 + sha256: e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e requires_dist: - railroad-diagrams ; extra == 'diagrams' - jinja2 ; extra == 'diagrams' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca - md5: e2fd202833c4a981ce8a65974fe4abd1 - depends: - - __win - - python >=3.9 - - win_inet_pton - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pysocks?source=hash-mapping - size: 21784 - timestamp: 1733217448189 -- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 - md5: 461219d1a5bd61342293efa2c0c90eac - depends: - - __unix - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pysocks?source=hash-mapping - size: 21085 - timestamp: 1733217331982 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.13-h9e4cc4f_0_cpython.conda - sha256: 9979a7d4621049388892489267139f1aa629b10c26601ba5dce96afc2b1551d4 - md5: 8c399445b6dc73eab839659e6c7b5ad1 +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl + name: pytest + version: 8.4.2 + sha256: 872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79 + requires_dist: + - colorama>=0.4 ; sys_platform == 'win32' + - exceptiongroup>=1 ; python_full_version < '3.11' + - iniconfig>=1 + - packaging>=20 + - pluggy>=1.5,<2 + - pygments>=2.7.2 + - tomli>=1 ; python_full_version < '3.11' + - argcomplete ; extra == 'dev' + - attrs>=19.2 ; extra == 'dev' + - hypothesis>=3.56 ; extra == 'dev' + - mock ; extra == 'dev' + - requests ; extra == 'dev' + - setuptools ; extra == 'dev' + - xmlschema ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + name: pytest-cov + version: 7.0.0 + sha256: 3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861 + requires_dist: + - coverage[toml]>=7.10.6 + - pluggy>=1.2 + - pytest>=7 + - process-tests ; extra == 'testing' + - pytest-xdist ; extra == 'testing' + - virtualenv ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + name: pytest-xdist + version: 3.8.0 + sha256: 202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88 + requires_dist: + - execnet>=2.1 + - pytest>=7.0.0 + - filelock ; extra == 'testing' + - psutil>=3.0 ; extra == 'psutil' + - setproctitle ; extra == 'setproctitle' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.14-hfe2f287_1_cpython.conda + build_number: 1 + sha256: 6515ef4018fda2826570f6f5c068e26dbd3e41a8b642f052c346812b3af28789 + md5: e87c753e04bffcda4cbfde7d052c1f7a depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.0,<3.0a0 + - libexpat >=2.7.1,<3.0a0 - libffi >=3.4.6,<3.5.0a0 - - libgcc >=13 + - libgcc >=14 - liblzma >=5.8.1,<6.0a0 - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.50.0,<4.0a0 - - libuuid >=2.38.1,<3.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libuuid >=2.41.2,<3.0a0 - libxcrypt >=4.4.36 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -8577,12 +8657,12 @@ packages: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 30629559 - timestamp: 1749050021812 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.7-h2b335a9_100_cp313.conda - build_number: 100 - sha256: 16cc30a5854f31ca6c3688337d34e37a79cdc518a06375fe3482ea8e2d6b34c8 - md5: 724dcf9960e933838247971da07fe5cf + size: 30812188 + timestamp: 1760365816536 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda + build_number: 101 + sha256: b429867f0faf5b9b71e2ebdbe8fedd6f84f4ba53fd2010a1f1458e1e1a038b98 + md5: ae8cf86b9140c7b6a6593a582a8eab8a depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 @@ -8593,32 +8673,33 @@ packages: - liblzma >=5.8.1,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - libsqlite >=3.50.4,<4.0a0 - - libuuid >=2.38.1,<3.0a0 + - libuuid >=2.41.2,<3.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - python_abi 3.13.* *_cp313 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 33583088 - timestamp: 1756911465277 + size: 37149783 + timestamp: 1760366432739 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.13-h9ccd52b_0_cpython.conda - sha256: d8e15db837c10242658979bc475298059bd6615524f2f71365ab8e54fbfea43c - md5: 6e28c31688c6f1fdea3dc3d48d33e1c0 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.14-h3999593_1_cpython.conda + build_number: 1 + sha256: 42a5106ae9f2a745f258ca187f811da1736e5d2f39ce33df200b00a34cfb47e6 + md5: 30383eebcafb2bdb3f1f039f7d79fb6e depends: - __osx >=10.13 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 + - libexpat >=2.7.1,<3.0a0 - libffi >=3.4.6,<3.5.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -8626,12 +8707,12 @@ packages: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 15423460 - timestamp: 1749049420299 -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.7-h5eba815_100_cp313.conda - build_number: 100 - sha256: 581e4db7462c383fbb64d295a99a3db73217f8c24781cbe7ab583ff9d0305968 - md5: 1759e1c9591755521bd50489756a599d + size: 15565919 + timestamp: 1760366149530 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda + build_number: 101 + sha256: 1e127a5a1b35e4f8187d9810f7eaf00993cc6854b7cf6b38245d4f5c63f7522a + md5: 90ec169b37f0e65c5ccceeb7a00d5696 depends: - __osx >=10.13 - bzip2 >=1.0.8,<2.0a0 @@ -8642,29 +8723,30 @@ packages: - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - python_abi 3.13.* *_cp313 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 12575616 - timestamp: 1756911460182 + size: 17462902 + timestamp: 1760366920956 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.13-hc22306f_0_cpython.conda - sha256: 2c966293ef9e97e66b55747c7a97bc95ba0311ac1cf0d04be4a51aafac60dcb1 - md5: 95facc4683b7b3b9cf8ae0ed10f30dce +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.14-hec0b533_1_cpython.conda + build_number: 1 + sha256: f1fc90c0929f744d0db11d1247cd97632134494cea2a99fa24996ad928e904a8 + md5: 64d46fd57bf5b2793f75fceb6f3b6189 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 + - libexpat >=2.7.1,<3.0a0 - libffi >=3.4.6,<3.5.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -8672,12 +8754,12 @@ packages: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 14573820 - timestamp: 1749048947732 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.7-h5c937ed_100_cp313.conda - build_number: 100 - sha256: b9776cc330fa4836171a42e0e9d9d3da145d7702ba6ef9fad45e94f0f016eaef - md5: 445d057271904b0e21e14b1fa1d07ba5 + size: 14794480 + timestamp: 1760366123572 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda + build_number: 101 + sha256: 5ff88415341058814a035375a6d9a0616769e280eebf72cddf8a8a426f572cec + md5: 71824735260cf57df846eb88aeb4fd99 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 @@ -8688,42 +8770,43 @@ packages: - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - python_abi 3.13.* *_cp313 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata license: Python-2.0 purls: [] - size: 11926240 - timestamp: 1756909724811 + size: 12026833 + timestamp: 1760366828517 python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.13-h3f84c4b_0_cpython.conda - sha256: 723dbca1384f30bd2070f77dd83eefd0e8d7e4dda96ac3332fbf8fe5573a8abb - md5: bedbb6f7bb654839719cd528f9b298ad +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.14-h30ce641_1_cpython.conda + build_number: 1 + sha256: a2e3daeccf06a6271d32d99e851a56fd938d913d2bc7a21eaa8f8219f20cd5a6 + md5: 0213e004a0cddcdc9618fa141294db39 depends: - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.0,<3.0a0 + - libexpat >=2.7.1,<3.0a0 - libffi >=3.4.6,<3.5.0a0 - liblzma >=5.8.1,<6.0a0 - - libsqlite >=3.50.0,<4.0a0 + - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.0,<4.0a0 + - openssl >=3.5.4,<4.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 constrains: - python_abi 3.11.* *_cp311 license: Python-2.0 purls: [] - size: 18242669 - timestamp: 1749048351218 -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.7-hdf00ec1_100_cp313.conda - build_number: 100 - sha256: b86b5b3a960de2fff0bb7e0932b50846b22b75659576a257b1872177aab444cd - md5: 7cd6ebd1a32d4a5d99f8f8300c2029d5 + size: 18568514 + timestamp: 1760364337404 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda + build_number: 101 + sha256: 944fcdc88b452972a829a70f115e24c120bc573d6a34810e0a418c1ddcd73553 + md5: ce6c7617eccf6671a93c867c37cd8fa4 depends: - bzip2 >=1.0.8,<2.0a0 - libexpat >=2.7.1,<3.0a0 @@ -8732,7 +8815,7 @@ packages: - libmpdec >=4.0.0,<5.0a0 - libsqlite >=3.50.4,<4.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.4,<4.0a0 - python_abi 3.13.* *_cp313 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -8741,26 +8824,20 @@ packages: - vc14_runtime >=14.44.35208 license: Python-2.0 purls: [] - size: 16386672 - timestamp: 1756909324921 + size: 16639232 + timestamp: 1760364470278 python_site_packages_path: Lib/site-packages -- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 - md5: 5b8d21249ff20967101ffa321cab24e8 - depends: - - python >=3.9 - - six >=1.5 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/python-dateutil?source=hash-mapping - size: 233310 - timestamp: 1751104122689 -- pypi: https://files.pythonhosted.org/packages/0c/fa/df59acedf7bbb937f69174d00f921a7b93aa5a5f5c17d05296c814fff6fc/python_engineio-4.12.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + name: python-dateutil + version: 2.9.0.post0 + sha256: a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + requires_dist: + - six>=1.5 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/d8/f0/c5aa0a69fd9326f013110653543f36ece4913c17921f3e1dbd78e1b423ee/python_engineio-4.12.3-py3-none-any.whl name: python-engineio - version: 4.12.2 - sha256: 8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f + version: 4.12.3 + sha256: 7c099abb2a27ea7ab429c04da86ab2d82698cdd6c52406cb73766fe454feb7e1 requires_dist: - simple-websocket>=0.10.0 - requests>=2.21.0 ; extra == 'client' @@ -8768,62 +8845,45 @@ packages: - aiohttp>=3.4 ; extra == 'asyncio-client' - sphinx ; extra == 'docs' requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 - md5: 23029aae904a2ba587daba708208012f - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/fastjsonschema?source=hash-mapping - size: 244628 - timestamp: 1755304154927 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.7-h4df99d1_100.conda - sha256: 109794a80cf31450903522e2613b6d760ae4655e65d6fff68467934fbe297ea1 - md5: 47a123ca8e727d886a2c6d0c71658f8c - depends: - - cpython 3.13.7.* - - python_abi * *_cp313 - license: Python-2.0 - purls: [] - size: 48178 - timestamp: 1756909461701 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - sha256: 4790787fe1f4e8da616edca4acf6a4f8ed4e7c6967aa31b920208fc8f95efcca - md5: a61bf9ec79426938ff785eb69dbb1960 - depends: - - python >=3.6 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/python-json-logger?source=hash-mapping - size: 13383 - timestamp: 1677079727691 -- pypi: https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + name: python-json-logger + version: 4.0.0 + sha256: af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2 + requires_dist: + - typing-extensions ; python_full_version < '3.10' + - orjson ; implementation_name != 'pypy' and extra == 'dev' + - msgspec ; implementation_name != 'pypy' and extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - black ; extra == 'dev' + - pylint ; extra == 'dev' + - mypy ; extra == 'dev' + - pytest ; extra == 'dev' + - freezegun ; extra == 'dev' + - backports-zoneinfo ; python_full_version < '3.9' and extra == 'dev' + - tzdata ; extra == 'dev' + - build ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-material>=8.5 ; extra == 'dev' + - mkdocs-awesome-pages-plugin ; extra == 'dev' + - mdx-truly-sane-lists ; extra == 'dev' + - mkdocstrings[python] ; extra == 'dev' + - mkdocs-gen-files ; extra == 'dev' + - mkdocs-literate-nav ; extra == 'dev' + - mike ; extra == 'dev' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl name: python-socketio - version: 5.13.0 - sha256: 51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf + version: 5.14.2 + sha256: d7133f040ac7ba540f9a907cb4c36d6c4d1d54eb35e482aad9d45c22fca10654 requires_dist: - bidict>=0.21.0 - python-engineio>=4.11.0 - requests>=2.21.0 ; extra == 'client' - websocket-client>=0.54.0 ; extra == 'client' - aiohttp>=3.4 ; extra == 'asyncio-client' + - tox ; extra == 'dev' - sphinx ; extra == 'docs' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - build_number: 8 - sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 - md5: 8fcb6b0e2161850556231336dae58358 - constrains: - - python 3.11.* *_cpython - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 7003 - timestamp: 1752805919375 - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda build_number: 8 sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 @@ -8835,344 +8895,126 @@ packages: purls: [] size: 7002 timestamp: 1752805902938 -- conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2025.2-pyhd8ed1ab_0.conda - sha256: 8d2a8bf110cc1fc3df6904091dead158ba3e614d8402a83e51ed3a8aa93cdeb0 - md5: bc8e3267d44011051f2eb14d22fb0960 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pytz?source=hash-mapping - size: 189015 - timestamp: 1742920947249 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda - sha256: e3ef7e0cc53111ab81b8a9dd3eabc1374d7420d4c9fce3c8631e73310203ad55 - md5: c1cfe9f5d8e278cc4d2d4c7b0126634d - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/pywin32?source=hash-mapping - size: 6729388 - timestamp: 1756487145061 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - sha256: 87eaeb79b5961e0f216aa840bc35d5f0b9b123acffaecc4fda4de48891901f20 - md5: 1ce4f826332dca56c76a5b0cc89fb19e - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/pywin32?source=hash-mapping - size: 6695114 - timestamp: 1756487139550 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_0.conda - sha256: fbf3e3f2d5596e755bd4b83b5007fa629b184349781f46e137a4e80b6754c7c0 - md5: 8a142e0fcd43513c2e876d97ba98c0fa - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - winpty - license: MIT - license_family: MIT - purls: - - pkg:pypi/pywinpty?source=hash-mapping - size: 217009 - timestamp: 1738661736085 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_0.conda - sha256: 4210038442e3f34d67de9aeab2691fa2a6f80dc8c16ab77d5ecbb2b756e04ff0 - md5: cd1fadcdf82a423c2441a95435eeab3c - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - winpty - license: MIT - license_family: MIT - purls: - - pkg:pypi/pywinpty?source=hash-mapping - size: 217133 - timestamp: 1738661059040 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py311h2dc5d0c_2.conda - sha256: d107ad62ed5c62764fba9400f2c423d89adf917d687c7f2e56c3bfed605fb5b3 - md5: 014417753f948da1f70d132b2de573be - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 213136 - timestamp: 1737454846598 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.2-py313h8060acc_2.conda - sha256: 6826217690cfe92d6d49cdeedb6d63ab32f51107105d6a459d30052a467037a0 - md5: 50992ba61a8a1f8c2d346168ae1c86df - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 205919 - timestamp: 1737454783637 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py311ha3cf9ac_2.conda - sha256: 4855c51eedcde05f3d9666a0766050c7cbdff29b150d63c1adc4071637ba61d7 - md5: f49b0da3b1e172263f4f1e2f261a490d - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 197287 - timestamp: 1737454852180 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyyaml-6.0.2-py313h717bdf5_2.conda - sha256: 27501e9b3b5c6bfabb3068189fd40c650356a258e4a82b0cfe31c60f568dcb85 - md5: b7f2984724531d2233b77c89c54be594 - depends: - - __osx >=10.13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 196573 - timestamp: 1737455046063 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py311h4921393_2.conda - sha256: 2af6006c9f692742181f4aa2e0656eb112981ccb0b420b899d3dd42c881bd72f - md5: 250b2ee8777221153fd2de9c279a7efa - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 196951 - timestamp: 1737454935552 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.2-py313ha9b7d5b_2.conda - sha256: 58c41b86ff2dabcf9ccd9010973b5763ec28b14030f9e1d9b371d22b538bce73 - md5: 03a7926e244802f570f25401c25c13bc - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 194243 - timestamp: 1737454911892 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py311h5082efb_2.conda - sha256: 6095e1d58c666f6a06c55338df09485eac34c76e43d92121d5786794e195aa4d - md5: e474ba674d780f0fa3b979ae9e81ba91 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 187430 - timestamp: 1737454904007 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.2-py313hb4c8b1a_2.conda - sha256: 5b496c96e48f495de41525cb1b603d0147f2079f88a8cf061aaf9e17a2fe1992 - md5: d14f685b5d204b023c641b188a8d0d7c - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 182783 - timestamp: 1737455202579 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h2315fbb_0.conda - sha256: 719104f31c414166a20281c973b6e29d1a2ab35e7930327368949895b8bc5629 - md5: 6c87a0f4566469af3585b11d89163fd7 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - libgcc >=14 - - zeromq >=4.3.5,<4.4.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 386618 - timestamp: 1757387012835 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hfb55c3c_0.conda - noarch: python - sha256: a00a41b66c12d9c60e66b391e9a4832b7e28743348cf4b48b410b91927cd7819 - md5: 3399d43f564c905250c1aea268ebb935 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - libgcc >=14 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=compressed-mapping - size: 212218 - timestamp: 1757387023399 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py311h0ab6910_0.conda - sha256: a0200b5e781c22a5d7b64fd0edf5e9ab881772f7a15a6bc2359db8d0ac437bb3 - md5: 840bdfbb93e35d650205af10883ff8a0 - depends: - - python - - libcxx >=19 - - __osx >=10.13 - - zeromq >=4.3.5,<4.4.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 362304 - timestamp: 1757387126324 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pyzmq-27.1.0-py312hb7d603e_0.conda - noarch: python - sha256: 4e052fa3c4ed319e7bcc441fca09dee4ee4006ac6eb3d036a8d683fceda9304b - md5: 81511d0be03be793c622c408c909d6f9 - depends: - - python - - __osx >=10.13 - - libcxx >=19 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 191697 - timestamp: 1757387104297 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h13abfa4_0.conda - sha256: 5a213744d267241e23f849c7671dc97eb98d7789fb559bf5d423ae1884294c7e - md5: 0d16883d4ab2d3fcb38460d018d6762f - depends: - - python - - __osx >=11.0 - - libcxx >=19 - - python 3.11.* *_cpython - - zeromq >=4.3.5,<4.4.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 359600 - timestamp: 1757387159663 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312hd65ceae_0.conda - noarch: python - sha256: ef33812c71eccf62ea171906c3e7fc1c8921f31e9cc1fbc3f079f3f074702061 - md5: bbd22b0f0454a5972f68a5f200643050 - depends: - - python - - __osx >=11.0 - - libcxx >=19 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 191115 - timestamp: 1757387128258 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hb77b9c8_0.conda - sha256: 1f146a62329093139fbe7fc109b595f19ca2b44beb921d0e1c6e61d2cb5ebef1 - md5: 96460f14570e237d27b475ef8238fdf3 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zeromq >=4.3.5,<4.3.6.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 363690 - timestamp: 1757387035331 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312hbb5da91_0.conda - noarch: python - sha256: fd46b30e6a1e4c129045e3174446de3ca90da917a595037d28595532ab915c5d - md5: 808d263ec97bbd93b41ca01552b5fbd4 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zeromq >=4.3.5,<4.3.6.0a0 - - _python_abi3_support 1.* - - cpython >=3.12 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 185711 - timestamp: 1757387025899 +- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl + name: pytz + version: '2025.2' + sha256: 5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 +- pypi: https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl + name: pywin32 + version: '311' + sha256: 3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503 +- pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl + name: pywin32 + version: '311' + sha256: 718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d +- pypi: https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl + name: pywinpty + version: 3.0.2 + sha256: 327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl + name: pywinpty + version: 3.0.2 + sha256: 18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl + name: pyyaml + version: 6.0.3 + sha256: 652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: 44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: 0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl + name: pyyaml + version: 6.0.3 + sha256: 79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl + name: pyyaml + version: 6.0.3 + sha256: 2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl + name: pyyaml + version: 6.0.3 + sha256: 8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl + name: pyyaml + version: 6.0.3 + sha256: 9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + name: pyyaml-env-tag + version: '1.1' + sha256: 17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 + requires_dist: + - pyyaml + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl + name: pyzmq + version: 27.1.0 + sha256: 226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86 + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl + name: pyzmq + version: 27.1.0 + sha256: 190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97 + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl + name: pyzmq + version: 27.1.0 + sha256: 452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: pyzmq + version: 27.1.0 + sha256: 5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: pyzmq + version: 27.1.0 + sha256: 43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31 + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl + name: pyzmq + version: 27.1.0 + sha256: 9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf + requires_dist: + - cffi ; implementation_name == 'pypy' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + name: radon + version: 6.0.1 + sha256: 632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859 + requires_dist: + - mando>=0.6,<0.8 + - colorama==0.4.1 ; python_full_version < '3.5' + - colorama>=0.4.1 ; python_full_version >= '3.5' + - tomli>=2.0.1 ; extra == 'toml' - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c md5: 283b96675859b20a825f8fa30f311446 @@ -9204,38 +9046,27 @@ packages: purls: [] size: 252359 timestamp: 1740379663071 -- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda - sha256: e20909f474a6cece176dfc0dc1addac265deb5fa92ea90e975fbca48085b20c3 - md5: 9140f1c09dd5489549c6a33931b943c7 - depends: - - attrs >=22.2.0 - - python >=3.9 - - rpds-py >=0.7.0 - - typing_extensions >=4.4.0 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/referencing?source=hash-mapping - size: 51668 - timestamp: 1737836872415 -- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.5-pyhd8ed1ab_0.conda - sha256: 8dc54e94721e9ab545d7234aa5192b74102263d3e704e6d0c8aa7008f2da2a7b - md5: db0c6b99149880c8ba515cf4abe93ee4 - depends: - - certifi >=2017.4.17 - - charset-normalizer >=2,<4 - - idna >=2.5,<4 - - python >=3.9 - - urllib3 >=1.21.1,<3 - constrains: - - chardet >=3.0.2,<6 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/requests?source=compressed-mapping - size: 59263 - timestamp: 1755614348400 +- pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl + name: referencing + version: 0.37.0 + sha256: 381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 + requires_dist: + - attrs>=22.2.0 + - rpds-py>=0.7.0 + - typing-extensions>=4.4.0 ; python_full_version < '3.13' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + name: requests + version: 2.32.5 + sha256: 2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 + requires_dist: + - charset-normalizer>=2,<4 + - idna>=2.5,<4 + - urllib3>=1.21.1,<3 + - certifi>=2017.4.17 + - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' + - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/57/4d/a7545bf6c62b0dbe5795f22ea9e88cc070fdced5c34663ebc5bed2f610c0/returns-0.26.0-py3-none-any.whl name: returns version: 0.26.0 @@ -9246,181 +9077,95 @@ packages: - pytest>=8.0,<9.0 ; extra == 'check-laws' - typing-extensions>=4.0,<5.0 requires_python: '>=3.10,<4.0' -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 - md5: 36de09a8d3e5d5e6f4ee63af49e59706 - depends: - - python >=3.9 +- pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl + name: rfc3339-validator + version: 0.1.4 + sha256: 24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa + requires_dist: - six - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3339-validator?source=hash-mapping - size: 10209 - timestamp: 1733600040800 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - sha256: 2a5b495a1de0f60f24d8a74578ebc23b24aa53279b1ad583755f223097c41c37 - md5: 912a71cc01012ee38e6b90ddd561e36f - depends: - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3986-validator?source=hash-mapping - size: 7818 - timestamp: 1598024297745 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - sha256: 70001ac24ee62058557783d9c5a7bbcfd97bd4911ef5440e3f7a576f9e43bc92 - md5: 7234f99325263a5af6d4cd195035e8f2 - depends: - - python >=3.9 - - lark >=1.2.2 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3987-syntax?source=hash-mapping - size: 22913 - timestamp: 1752876729969 -- pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl + name: rfc3986-validator + version: 0.1.1 + sha256: 2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl + name: rfc3987-syntax + version: 1.1.0 + sha256: 6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f + requires_dist: + - lark>=1.2.2 + - pytest>=8.3.5 ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl name: rich - version: 14.1.0 - sha256: 536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f + version: 14.2.0 + sha256: 76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd requires_dist: - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' - markdown-it-py>=2.2.0 - pygments>=2.13.0,<3.0.0 requires_python: '>=3.8.0' -- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py311h902ca64_1.conda - sha256: d9bc1564949ede4abd32aea34cf1997d704b6091e547f255dc0168996f5d5ec8 - md5: 622c389c080689ba1575a0750eb0209d - depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.11.* *_cp311 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 387057 - timestamp: 1756737832651 -- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py313h843e2db_1.conda - sha256: a976e90dbd229f7dcd357b0f9a5b637bf85243f3a3e844c1e266472cae53e359 - md5: 06c117e49934b564ef9ff6e61f279301 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.13.* *_cp313 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 389189 - timestamp: 1756737629819 -- conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py311hd3d88a1_1.conda - sha256: 85357c87af076680c071a8ea843bea554d58694d011104b721cc13bbf9ad0e75 - md5: 4b9839b15de18289ee5289a6dbcb8a45 - depends: - - python - - __osx >=10.13 - - python_abi 3.11.* *_cp311 - constrains: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 376118 - timestamp: 1756737583772 -- conda: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py313h66e1e84_1.conda - sha256: a4f887801670167d92152bfe240e9c5c0ed2431c3576241058bc6724e691c486 - md5: 5525368b99f51b2593de0f0b335fec8f - depends: - - python - - __osx >=10.13 - - python_abi 3.13.* *_cp313 - constrains: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 368896 - timestamp: 1756737495945 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py311h1c3fc1a_1.conda - sha256: 95714a24265b6b4d4b218e303dcb075ba435826cb1d5927792ec94a8196c3e72 - md5: 5236ffaff99e6421aa4431b4c00ca47a - depends: - - python - - python 3.11.* *_cpython - - __osx >=11.0 - - python_abi 3.11.* *_cp311 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 362213 - timestamp: 1756737586989 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_1.conda - sha256: 26a8e509a1fc87986c24534bc3eddfa25ed3bbcea32ed64663f380b5b28e8c94 - md5: 22c8096afd85182d01d95f5a411ef804 - depends: - - python - - python 3.13.* *_cp313 - - __osx >=11.0 - - python_abi 3.13.* *_cp313 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 354648 - timestamp: 1756737507794 -- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py311hf51aa87_1.conda - sha256: e61607627213b70e7be73570e7ef5e2d36b583512def108aaf78a6ab16f0cdd9 - md5: 3c5b42969dae70e100154750d29d43cc - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 247101 - timestamp: 1756737437304 -- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.27.1-py313hfbe8231_1.conda - sha256: 0a300df3466cb98834d60f1863db60fc8d4d0cbbd732b153d3a656654f307fa2 - md5: 9fbdb4338b80392e5d59529712f30763 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 250236 - timestamp: 1756737484957 +- pypi: https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: rpds-py + version: 0.27.1 + sha256: 2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl + name: rpds-py + version: 0.27.1 + sha256: 62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl + name: rpds-py + version: 0.27.1 + sha256: b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl + name: rpds-py + version: 0.27.1 + sha256: 1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl + name: rpds-py + version: 0.27.1 + sha256: be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl + name: rpds-py + version: 0.27.1 + sha256: e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl + name: rpds-py + version: 0.27.1 + sha256: f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: rpds-py + version: 0.27.1 + sha256: cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: ruff + version: 0.14.0 + sha256: eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl + name: ruff + version: 0.14.0 + sha256: ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl + name: ruff + version: 0.14.0 + sha256: 703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl + name: ruff + version: 0.14.0 + sha256: 838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8 + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl name: scipy version: 1.16.2 @@ -9773,44 +9518,73 @@ packages: - doit>=0.36.0 ; extra == 'dev' - pydevtool ; extra == 'dev' requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh0d859eb_1.conda - sha256: 00926652bbb8924e265caefdb1db100f86a479e8f1066efe395d5552dde54d02 - md5: 938c8de6b9de091997145b3bf25cdbf9 - depends: - - __linux - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 22736 - timestamp: 1733322148326 -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh31c8845_1.conda - sha256: 5282eb5b462502c38df8cb37cd1542c5bbe26af2453a18a0a0602d084ca39f53 - md5: e67b1b1fa7a79ff9e8e326d0caf55854 - depends: - - __osx - - pyobjc-framework-cocoa - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 23100 - timestamp: 1733322309409 -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-1.8.3-pyh5737063_1.conda - sha256: ba8b93df52e0d625177907852340d735026c81118ac197f61f1f5baea19071ad - md5: e6a4e906051565caf5fdae5b0415b654 - depends: - - __win - - python >=3.9 - - pywin32 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 23359 - timestamp: 1733322590167 +- pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl + name: send2trash + version: 1.8.3 + sha256: 0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9 + requires_dist: + - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'nativelib' + - pywin32 ; sys_platform == 'win32' and extra == 'nativelib' + - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'objc' + - pywin32 ; sys_platform == 'win32' and extra == 'win32' + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*' +- pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl + name: setuptools + version: 80.9.0 + sha256: 062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 + requires_dist: + - pytest>=6,!=8.1.* ; extra == 'test' + - virtualenv>=13.0.0 ; extra == 'test' + - wheel>=0.44.0 ; extra == 'test' + - pip>=19.1 ; extra == 'test' + - packaging>=24.2 ; extra == 'test' + - jaraco-envs>=2.2 ; extra == 'test' + - pytest-xdist>=3 ; extra == 'test' + - jaraco-path>=3.7.2 ; extra == 'test' + - build[virtualenv]>=1.0.3 ; extra == 'test' + - filelock>=3.4.0 ; extra == 'test' + - ini2toml[lite]>=0.14 ; extra == 'test' + - tomli-w>=1.0.0 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-perf ; sys_platform != 'cygwin' and extra == 'test' + - jaraco-develop>=7.21 ; python_full_version >= '3.9' and sys_platform != 'cygwin' and extra == 'test' + - pytest-home>=0.5 ; extra == 'test' + - pytest-subprocess ; extra == 'test' + - pyproject-hooks!=1.1 ; extra == 'test' + - jaraco-test>=5.5 ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pygments-github-lexers==0.0.5 ; extra == 'doc' + - sphinx-favicon ; extra == 'doc' + - sphinx-inline-tabs ; extra == 'doc' + - sphinx-reredirects ; extra == 'doc' + - sphinxcontrib-towncrier ; extra == 'doc' + - sphinx-notfound-page>=1,<2 ; extra == 'doc' + - pyproject-hooks!=1.1 ; extra == 'doc' + - towncrier<24.7 ; extra == 'doc' + - packaging>=24.2 ; extra == 'core' + - more-itertools>=8.8 ; extra == 'core' + - jaraco-text>=3.7 ; extra == 'core' + - importlib-metadata>=6 ; python_full_version < '3.10' and extra == 'core' + - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'core' + - wheel>=0.43.0 ; extra == 'core' + - platformdirs>=4.2.2 ; extra == 'core' + - jaraco-functools>=4 ; extra == 'core' + - more-itertools ; extra == 'core' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - ruff>=0.8.0 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=2.2 ; extra == 'enabler' + - pytest-mypy ; extra == 'type' + - mypy==1.14.* ; extra == 'type' + - importlib-metadata>=7.0.2 ; python_full_version < '3.10' and extra == 'type' + - jaraco-develop>=7.21 ; sys_platform != 'cygwin' and extra == 'type' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda sha256: 972560fcf9657058e3e1f97186cc94389144b46dbdf58c807ce62e83f977e863 md5: 4de79c071274a53dcaf2a8c749d1499e @@ -9839,47 +9613,28 @@ packages: - pytest-cov ; extra == 'dev' - sphinx ; extra == 'docs' requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d - md5: 3339e3b65d58accf4ca4fb8748ab16b3 - depends: - - python >=3.9 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/six?source=hash-mapping - size: 18455 - timestamp: 1753199211006 -- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_1.conda - sha256: c2248418c310bdd1719b186796ae50a8a77ce555228b6acd32768e2543a15012 - md5: bf7a226e58dfb8346c70df36065d86c9 - depends: - - python >=3.9 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/sniffio?source=hash-mapping - size: 15019 - timestamp: 1733244175724 -- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda - sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c - md5: 18c019ccf43769d211f2cf78e9ad46c2 - depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/soupsieve?source=compressed-mapping - size: 37803 - timestamp: 1756330614547 -- pypi: https://files.pythonhosted.org/packages/03/b5/cacf432e6f1fc9d156eca0560ac61d4355d2181e751ba8c0cd9cb232c8c1/sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + name: six + version: 1.17.0 + sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl + name: sniffio + version: 1.3.1 + sha256: 2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl + name: soupsieve + version: '2.8' + sha256: 0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl name: sqlalchemy - version: 2.0.43 - sha256: db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca + version: 2.0.44 + sha256: c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -9911,13 +9666,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl name: sqlalchemy - version: 2.0.43 - sha256: e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3 + version: 2.0.44 + sha256: 0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45 requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -9949,13 +9704,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl name: sqlalchemy - version: 2.0.43 - sha256: 14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa + version: 2.0.44 + sha256: ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1 requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -9987,13 +9742,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/99/ea/92ac27f2fbc2e6c1766bb807084ca455265707e041ba027c09c17d697867/sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/94/2d/fdb9246d9d32518bda5d90f4b65030b9bf403a935cfe4c36a474846517cb/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: sqlalchemy - version: 2.0.43 - sha256: f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631 + version: 2.0.44 + sha256: 3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10025,13 +9780,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/9d/77/fa7189fe44114658002566c6fe443d3ed0ec1fa782feb72af6ef7fbe98e7/sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: sqlalchemy - version: 2.0.43 - sha256: 52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29 + version: 2.0.44 + sha256: b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10063,13 +9818,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/c7/23/907193c2f4d680aedbfbdf7bf24c13925e3c7c292e813326c1b84a0b878e/sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl name: sqlalchemy - version: 2.0.43 - sha256: 971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197 + version: 2.0.44 + sha256: 7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5 requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10101,13 +9856,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/d4/d5/4abd13b245c7d91bdf131d4916fd9e96a584dac74215f8b5bc945206a974/sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl name: sqlalchemy - version: 2.0.43 - sha256: 9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f + version: 2.0.44 + sha256: de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10139,13 +9894,13 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ee/13/744a32ebe3b4a7a9c7ea4e57babae7aa22070d47acf330d8e5a1359607f1/sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e3/81/15d7c161c9ddf0900b076b55345872ed04ff1ed6a0666e5e94ab44b0163c/sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl name: sqlalchemy - version: 2.0.43 - sha256: c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8 + version: 2.0.44 + sha256: 0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd requires_dist: - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; (python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64') + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - typing-extensions>=4.6.0 - greenlet>=1 ; extra == 'asyncio' - mypy>=0.910 ; extra == 'mypy' @@ -10177,20 +9932,19 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 - md5: b1b505328da7a6b246787df4b5a49fbc - depends: - - asttokens - - executing - - pure_eval - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/stack-data?source=hash-mapping - size: 26988 - timestamp: 1733569565672 +- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl + name: stack-data + version: 0.6.3 + sha256: d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695 + requires_dist: + - executing>=1.2.0 + - asttokens>=2.1.0 + - pure-eval + - pytest ; extra == 'tests' + - typeguard ; extra == 'tests' + - pygments ; extra == 'tests' + - littleutils ; extra == 'tests' + - cython ; extra == 'tests' - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl name: sympy version: 1.14.0 @@ -10220,60 +9974,34 @@ packages: purls: [] size: 150266 timestamp: 1755776172092 -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh0d859eb_0.conda - sha256: b300557c0382478cf661ddb520263508e4b3b5871b471410450ef2846e8c352c - md5: efba281bbdae5f6b0a1d53c6d4a97c93 - depends: - - __linux - - ptyprocess - - python >=3.8 - - tornado >=6.1.0 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 22452 - timestamp: 1710262728753 -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh31c8845_0.conda - sha256: 4daae56fc8da17784578fbdd064f17e3b3076b394730a14119e571707568dc8a - md5: 00b54981b923f5aefcd5e8547de056d5 - depends: - - __osx - - ptyprocess - - python >=3.8 - - tornado >=6.1.0 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 22717 - timestamp: 1710265922593 -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh5737063_0.conda - sha256: 8cb078291fd7882904e3de594d299c8de16dd3af7405787fce6919a385cfc238 - md5: 4abd500577430a942a995fd0d09b76a2 - depends: - - __win - - python >=3.8 - - pywinpty >=1.1.0 - - tornado >=6.1.0 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 22883 - timestamp: 1710262943966 -- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 - md5: f1acf5fdefa8300de697982bcb1761c9 - depends: - - python >=3.5 - - webencodings >=0.4 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/tinycss2?source=hash-mapping - size: 28285 - timestamp: 1729802975370 +- pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl + name: terminado + version: 0.18.1 + sha256: a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0 + requires_dist: + - ptyprocess ; os_name != 'nt' + - pywinpty>=1.1.0 ; os_name == 'nt' + - tornado>=6.1.0 + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - pre-commit ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - mypy~=1.6 ; extra == 'typing' + - traitlets>=5.11.1 ; extra == 'typing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl + name: tinycss2 + version: 1.4.0 + sha256: 3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289 + requires_dist: + - webencodings>=0.4 + - sphinx ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - pytest ; extra == 'test' + - ruff ; extra == 'test' + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1 md5: a0116df4f4ed05c303811a837d5b39d8 @@ -10304,210 +10032,126 @@ packages: - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: TCL - license_family: BSD - purls: [] - size: 3125538 - timestamp: 1748388189063 -- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda - sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896 - md5: ebd0e761de9aa879a51d22cc721bd095 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: TCL - license_family: BSD - purls: [] - size: 3466348 - timestamp: 1748388121356 -- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - sha256: 040a5a05c487647c089ad5e05ad5aff5942830db2a4e656f1e300d73436436f1 - md5: 30a0a26c8abccf4b7991d590fe17c699 - depends: - - python >=3.9 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/tomli?source=compressed-mapping - size: 21238 - timestamp: 1753796677376 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py311h49ec1c0_1.conda - sha256: b1d686806d6b913e42aadb052b12d9cc91aae295640df3acfef645142fc33b3d - md5: 18a98f4444036100d78b230c94453ff4 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 868049 - timestamp: 1756855060036 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.2-py313h07c4f96_1.conda - sha256: c8bfe883aa2d5b59cb1d962729a12b3191518f7decbe9e3505c2aacccb218692 - md5: 45821154b9cb2fb63c2b354c76086954 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 877215 - timestamp: 1756855010312 -- conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py311h13e5629_1.conda - sha256: 397226b6314aec2b076294816c941c1e9b7e3bbcf7a2e9dc9ba08f3ac10b0590 - md5: 06fd6a712ee16b3e7998e8ee2e7fc8b1 - depends: - - __osx >=10.13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 869912 - timestamp: 1756855230920 -- conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.2-py313h585f44e_1.conda - sha256: 530ac0f1864dcdb81a3c802d2b01cd83ec9a288f01dc6404b3f538b2ec84c3b6 - md5: 3fa5548d42d026657a1cd8e4305cee9d - depends: - - __osx >=10.13 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 873301 - timestamp: 1756855040989 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py311h3696347_1.conda - sha256: 4941963a1f0046b2813bfbe4c2ded15cb0b0048436fe62237d69467e8c0e1692 - md5: 25833dd6cb94341239aec42dd5370c33 - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 870380 - timestamp: 1756855179889 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.2-py313hcdf3177_1.conda - sha256: 30fbb92cc119595e4ac7691789d45d367f5d6850103b97ca4a130d98e8ec27f0 - md5: 728311ebaa740a1efa6fab80bbcdf335 - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 874955 - timestamp: 1756855212446 -- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py311h3485c13_1.conda - sha256: 87527996d1297442bbc432369a5791af740762c1dda642d52cd55d32d5577937 - md5: ec9179a7226659bd15d8085c8de15360 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=compressed-mapping - size: 871179 - timestamp: 1756855104869 -- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.2-py313h5ea7bf4_1.conda - sha256: ec4d9ef994018acc59d83e1024f7b08a3e8f2d917ec42d6db873aaf6102fec3c - md5: ae40ad307ecb3181694714a40002af6c + license_family: BSD + purls: [] + size: 3125538 + timestamp: 1748388189063 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda + sha256: e3614b0eb4abcc70d98eae159db59d9b4059ed743ef402081151a948dce95896 + md5: ebd0e761de9aa879a51d22cc721bd095 depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=compressed-mapping - size: 876511 - timestamp: 1756855260509 -- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - sha256: f39a5620c6e8e9e98357507262a7869de2ae8cc07da8b7f84e517c9fd6c2b959 - md5: 019a7385be9af33791c989871317e1ed - depends: - - python >=3.9 - license: BSD-3-Clause + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: TCL license_family: BSD - purls: - - pkg:pypi/traitlets?source=hash-mapping - size: 110051 - timestamp: 1733367480074 -- pypi: https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl + purls: [] + size: 3466348 + timestamp: 1748388121356 +- pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + name: tokenize-rt + version: 6.2.0 + sha256: a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl + name: tomli + version: 2.3.0 + sha256: 4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl + name: tomli + version: 2.3.0 + sha256: 883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl + name: tomli + version: 2.3.0 + sha256: 5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: tomli + version: 2.3.0 + sha256: a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl + name: tomli + version: 2.3.0 + sha256: 0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl + name: tomli + version: 2.3.0 + sha256: 88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: tomli + version: 2.3.0 + sha256: 4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl + name: tomli + version: 2.3.0 + sha256: be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl + name: tornado + version: 6.5.2 + sha256: e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl + name: tornado + version: 6.5.2 + sha256: 583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl + name: tornado + version: 6.5.2 + sha256: 2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: tornado + version: 6.5.2 + sha256: e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl + name: traitlets + version: 5.14.3 + sha256: b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f + requires_dist: + - myst-parser ; extra == 'docs' + - pydata-sphinx-theme ; extra == 'docs' + - sphinx ; extra == 'docs' + - argcomplete>=3.0.3 ; extra == 'test' + - mypy>=1.7.0 ; extra == 'test' + - pre-commit ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-mypy-testing ; extra == 'test' + - pytest>=7.0,<8.2 ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl + name: trove-classifiers + version: 2025.9.11.17 + sha256: 5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd +- pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl name: typer - version: 0.17.4 - sha256: 015534a6edaa450e7007eba705d5c18c3349dcea50a6ad79a5ed530967575824 + version: 0.19.2 + sha256: 755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9 requires_dist: - click>=8.0.0 - typing-extensions>=3.7.4.3 - shellingham>=1.3.0 - rich>=10.11.0 - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/types-python-dateutil-2.9.0.20250822-pyhd8ed1ab_0.conda - sha256: dfdf6e3dea87c873a86cfa47f7cba6ffb500bad576d083b3de6ad1b17e1a59c3 - md5: 5e9220c892fe069da8de2b9c63663319 - depends: - - python >=3.10 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/types-python-dateutil?source=hash-mapping - size: 24939 - timestamp: 1755865615651 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c - md5: edd329d7d3a4ab45dcf905899a7a6115 - depends: - - typing_extensions ==4.15.0 pyhcf101f3_0 - license: PSF-2.0 - license_family: PSF - purls: [] - size: 91383 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 - md5: 0caa1af407ecff61170c9437a808404d - depends: - - python >=3.10 - - python - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/typing-extensions?source=compressed-mapping - size: 51692 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c - md5: f6d7aa696c67756a650e91e15e88223c - depends: - - python >=3.9 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/typing-utils?source=hash-mapping - size: 15183 - timestamp: 1733331395943 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + name: types-python-dateutil + version: 2.9.0.20251008 + sha256: b9a5232c8921cf7661b29c163ccc56055c418ab2c6eabe8f917cbcc73a4c4157 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + name: typing-extensions + version: 4.15.0 + sha256: f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl name: tzdata version: '2025.2' @@ -10545,51 +10189,77 @@ packages: - python-docs-theme ; extra == 'doc' - uncertainties[arrays,doc,test] ; extra == 'all' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 - md5: e7cb0f5745e4c5035a460248334af7eb - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/uri-template?source=hash-mapping - size: 23990 - timestamp: 1733323714454 -- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda - sha256: 4fb9789154bd666ca74e428d973df81087a697dbb987775bc3198d2215f240f8 - md5: 436c165519e140cb08d246a4472a9d6a - depends: - - brotli-python >=1.0.9 - - h2 >=4,<5 - - pysocks >=1.5.6,<2.0,!=1.5.7 - - python >=3.9 - - zstandard >=0.18.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/urllib3?source=hash-mapping - size: 101735 - timestamp: 1750271478254 -- pypi: https://files.pythonhosted.org/packages/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz + name: untokenize + version: 0.1.1 + sha256: 3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2 +- pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl + name: uri-template + version: 1.3.0 + sha256: a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363 + requires_dist: + - types-pyyaml ; extra == 'dev' + - mypy ; extra == 'dev' + - flake8 ; extra == 'dev' + - flake8-annotations ; extra == 'dev' + - flake8-bandit ; extra == 'dev' + - flake8-bugbear ; extra == 'dev' + - flake8-commas ; extra == 'dev' + - flake8-comprehensions ; extra == 'dev' + - flake8-continuation ; extra == 'dev' + - flake8-datetimez ; extra == 'dev' + - flake8-docstrings ; extra == 'dev' + - flake8-import-order ; extra == 'dev' + - flake8-literal ; extra == 'dev' + - flake8-modern-annotations ; extra == 'dev' + - flake8-noqa ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - flake8-requirements ; extra == 'dev' + - flake8-typechecking-import ; extra == 'dev' + - flake8-use-fstring ; extra == 'dev' + - pep8-naming ; extra == 'dev' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl + name: urllib3 + version: 2.5.0 + sha256: e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + requires_dist: + - brotli>=1.0.9 ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' + - h2>=4,<5 ; extra == 'h2' + - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' + - zstandard>=0.18.0 ; extra == 'zstd' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl name: uv - version: 0.8.17 - sha256: cf85b84b81b41d57a9b6eeded8473ec06ace8ee959ad0bb57e102b5ad023bd34 + version: 0.9.3 + sha256: 741e80c4230e1b9a5d0869aca2fb082b3832b251ef61537bc9278364b8e74df2 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: uv - version: 0.8.17 - sha256: c28fba6d7bb5c34ade2c8da5000faebe8425a287f42a043ca01ceb24ebc81590 + version: 0.9.3 + sha256: 73ae4bbc7d555ba1738da08c64b55f21ab0ea0ff85636708cebaf460d98a440d requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl name: uv - version: 0.8.17 - sha256: b6d30d02fb65193309fc12a20f9e1a9fab67f469d3e487a254ca1145fd06788f + version: 0.9.3 + sha256: 55516bf85c44a00b948472ddda80d7c5cd9990e9b5c085dc5005da93f40266a7 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl name: uv - version: 0.8.17 - sha256: b009f1ec9e28de00f76814ad66e35aaae82c98a0f24015de51943dcd1c2a1895 + version: 0.9.3 + sha256: 596a982c5a061d58412824a2ebe2960b52db23f1b1658083ba9c0e7ae390308a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl + name: validate-pyproject + version: 0.24.1 + sha256: b7b05fa9117204c9c4606ab317acd095b18d1bfc78fb7dc8cc06f77d0582ca2d + requires_dist: + - fastjsonschema>=2.16.2,<=3 + - packaging>=24.2 ; extra == 'all' + - trove-classifiers>=2021.10.20 ; extra == 'all' + - tomli>=1.2.1 ; python_full_version < '3.11' and extra == 'all' + - validate-pyproject-schema-store ; extra == 'store' requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl name: varname @@ -10601,87 +10271,151 @@ packages: - pure-eval==0.* ; extra == 'all' - typing-extensions>=4.13,<5.0 ; python_full_version < '3.10' requires_python: '>=3.8,<4.0' -- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_31.conda - sha256: cb357591d069a1e6cb74199a8a43a7e3611f72a6caed9faa49dbb3d7a0a98e0b - md5: 28f4ca1e0337d0f27afb8602663c5723 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h2b53caa_32.conda + sha256: 82250af59af9ff3c6a635dd4c4764c631d854feb334d6747d356d949af44d7cf + md5: ef02bbe151253a72b8eda264a935db66 depends: - - vc14_runtime >=14.44.35208 + - vc14_runtime >=14.42.34433 track_features: - vc14 license: BSD-3-Clause license_family: BSD purls: [] - size: 18249 - timestamp: 1753739241465 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_31.conda - sha256: af4b4b354b87a9a8d05b8064ff1ea0b47083274f7c30b4eb96bc2312c9b5f08f - md5: 603e41da40a765fd47995faa021da946 + size: 18861 + timestamp: 1760418772353 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda + sha256: e3a3656b70d1202e0d042811ceb743bd0d9f7e00e2acdf824d231b044ef6c0fd + md5: 378d5dcec45eaea8d303da6f00447ac0 depends: - ucrt >=10.0.20348.0 - - vcomp14 14.44.35208 h818238b_31 + - vcomp14 14.44.35208 h818238b_32 constrains: - - vs2015_runtime 14.44.35208.* *_31 + - vs2015_runtime 14.44.35208.* *_32 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 682424 - timestamp: 1753739239305 -- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_31.conda - sha256: 67b317b64f47635415776718d25170a9a6f9a1218c0f5a6202bfd687e07b6ea4 - md5: a6b1d5c1fc3cb89f88f7179ee6a9afe3 + size: 682706 + timestamp: 1760418629729 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda + sha256: f3790c88fbbdc55874f41de81a4237b1b91eab75e05d0e58661518ff04d2a8a1 + md5: 58f67b437acbf2764317ba273d731f1d depends: - ucrt >=10.0.20348.0 constrains: - - vs2015_runtime 14.44.35208.* *_31 + - vs2015_runtime 14.44.35208.* *_32 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary purls: [] - size: 113963 - timestamp: 1753739198723 -- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - sha256: f21e63e8f7346f9074fd00ca3b079bd3d2fa4d71f1f89d5b6934bf31446dc2a5 - md5: b68980f2495d096e71c7fd9d7ccf63e6 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/wcwidth?source=hash-mapping - size: 32581 - timestamp: 1733231433877 -- conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.11.1-pyhd8ed1ab_0.conda - sha256: 08315dc2e61766a39219b2d82685fc25a56b2817acf84d5b390176080eaacf99 - md5: b49f7b291e15494aafb0a7d74806f337 - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/webcolors?source=hash-mapping - size: 18431 - timestamp: 1733359823938 -- conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 - md5: 2841eb5bfc75ce15e9a0054b98dcd64d - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/webencodings?source=hash-mapping - size: 15496 - timestamp: 1733236131358 -- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.8.0-pyhd8ed1ab_1.conda - sha256: 1dd84764424ffc82030c19ad70607e6f9e3b9cb8e633970766d697185652053e - md5: 84f8f77f0a9c6ef401ee96611745da8f - depends: - - python >=3.9 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/websocket-client?source=hash-mapping - size: 46718 - timestamp: 1733157432924 + size: 114846 + timestamp: 1760418593847 +- pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + name: versioningit + version: 3.3.0 + sha256: 23b1db3c4756cded9bd6b0ddec6643c261e3d0c471707da3e0b230b81ce53e4b + requires_dist: + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - packaging>=17.1 + - tomli>=1.2,<3.0 ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl + name: virtualenv + version: 20.35.3 + sha256: 63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a + requires_dist: + - distlib>=0.3.7,<1 + - filelock>=3.12.2,<4 + - importlib-metadata>=6.6 ; python_full_version < '3.8' + - platformdirs>=3.9.1,<5 + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - furo>=2023.7.26 ; extra == 'docs' + - proselint>=0.13 ; extra == 'docs' + - sphinx>=7.1.2,!=7.3 ; extra == 'docs' + - sphinx-argparse>=0.4 ; extra == 'docs' + - sphinxcontrib-towncrier>=0.2.1a0 ; extra == 'docs' + - towncrier>=23.6 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'test' + - coverage-enable-subprocess>=1 ; extra == 'test' + - coverage>=7.2.7 ; extra == 'test' + - flaky>=3.7 ; extra == 'test' + - packaging>=23.1 ; extra == 'test' + - pytest-env>=0.8.2 ; extra == 'test' + - pytest-freezer>=0.4.8 ; (python_full_version >= '3.13' and platform_python_implementation == 'CPython' and sys_platform == 'win32' and extra == 'test') or (platform_python_implementation == 'GraalVM' and extra == 'test') or (platform_python_implementation == 'PyPy' and extra == 'test') + - pytest-mock>=3.11.1 ; extra == 'test' + - pytest-randomly>=3.12 ; extra == 'test' + - pytest-timeout>=2.1 ; extra == 'test' + - pytest>=7.4 ; extra == 'test' + - setuptools>=68 ; extra == 'test' + - time-machine>=2.10 ; platform_python_implementation == 'CPython' and extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl + name: watchdog + version: 6.0.0 + sha256: ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl + name: watchdog + version: 6.0.0 + sha256: 76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + name: watchdog + version: 6.0.0 + sha256: 20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl + name: watchdog + version: 6.0.0 + sha256: afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + name: watchdog + version: 6.0.0 + sha256: cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + name: watchdog + version: 6.0.0 + sha256: a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl + name: wcwidth + version: 0.2.14 + sha256: a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl + name: webcolors + version: 24.11.1 + sha256: 515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl + name: webencodings + version: 0.5.1 + sha256: a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 +- pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl + name: websocket-client + version: 1.9.0 + sha256: af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef + requires_dist: + - pytest ; extra == 'test' + - websockets ; extra == 'test' + - python-socks ; extra == 'optional' + - wsaccel ; extra == 'optional' + - sphinx>=6.0 ; extra == 'docs' + - sphinx-rtd-theme>=1.1.0 ; extra == 'docs' + - myst-parser>=2.0.0 ; extra == 'docs' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce md5: 75cb7132eb58d97896e173ef12ac9986 @@ -10693,24 +10427,6 @@ packages: - pkg:pypi/wheel?source=hash-mapping size: 62931 timestamp: 1733130309598 -- conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f - md5: 46e441ba871f524e2b067929da3051c2 - depends: - - __win - - python >=3.9 - license: LicenseRef-Public-Domain - purls: - - pkg:pypi/win-inet-pton?source=hash-mapping - size: 9555 - timestamp: 1733130678956 -- conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - sha256: 9df10c5b607dd30e05ba08cbd940009305c75db242476f4e845ea06008b0a283 - md5: 1cee351bf20b830d991dbe0bc8cd7dfe - license: MIT - license_family: MIT - purls: [] - size: 1176306 - pypi: https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl name: wsproto version: 1.2.0 @@ -10735,372 +10451,75 @@ packages: - coverage ; extra == 'test' - xraydb[dev,doc,test] ; extra == 'all' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad - md5: a77f85f77be52ff59391544bfe73390a - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - license: MIT - license_family: MIT - purls: [] - size: 85189 - timestamp: 1753484064210 -- conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda - sha256: a335161bfa57b64e6794c3c354e7d49449b28b8d8a7c4ed02bf04c3f009953f9 - md5: a645bb90997d3fc2aea0adf6517059bd - depends: - - __osx >=10.13 - license: MIT - license_family: MIT - purls: [] - size: 79419 - timestamp: 1753484072608 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - sha256: b03433b13d89f5567e828ea9f1a7d5c5d697bf374c28a4168d71e9464f5dafac - md5: 78a0fe9e9c50d2c381e8ee47e3ea437d - depends: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: [] - size: 83386 - timestamp: 1753484079473 -- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - sha256: 80ee68c1e7683a35295232ea79bcc87279d31ffeda04a1665efdb43cbd50a309 - md5: 433699cba6602098ae8957a323da2664 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - license: MIT - license_family: MIT - purls: [] - size: 63944 - timestamp: 1753484092156 -- pypi: https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl name: yarl - version: 1.20.1 - sha256: 98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8 + version: 1.22.0 + sha256: 01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: yarl - version: 1.20.1 - sha256: d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b + version: 1.22.0 + sha256: bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl name: yarl - version: 1.20.1 - sha256: d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5 + version: 1.22.0 + sha256: 22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: yarl - version: 1.20.1 - sha256: 14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3 + version: 1.22.0 + sha256: 4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl name: yarl - version: 1.20.1 - sha256: 495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c + version: 1.22.0 + sha256: 669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl name: yarl - version: 1.20.1 - sha256: f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7 + version: 1.22.0 + sha256: 47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl name: yarl - version: 1.20.1 - sha256: 7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b + version: 1.22.0 + sha256: 078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl name: yarl - version: 1.20.1 - sha256: 26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e + version: 1.22.0 + sha256: 792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028 requires_dist: - idna>=2.0 - multidict>=4.0 - propcache>=0.2.1 requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h387f397_9.conda - sha256: 47cfe31255b91b4a6fa0e9dbaf26baa60ac97e033402dbc8b90ba5fee5ffe184 - md5: 8035e5b54c08429354d5d64027041cad - depends: - - libstdcxx >=14 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 310648 - timestamp: 1757370847287 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zeromq-4.3.5-h6c33b1e_9.conda - sha256: 30aa5a2e9c7b8dbf6659a2ccd8b74a9994cdf6f87591fcc592970daa6e7d3f3c - md5: d940d809c42fbf85b05814c3290660f5 - depends: - - __osx >=10.13 - - libcxx >=19 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 259628 - timestamp: 1757371000392 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h888dc83_9.conda - sha256: b6f9c130646e5971f6cad708e1eee278f5c7eea3ca97ec2fdd36e7abb764a7b8 - md5: 26f39dfe38a2a65437c29d69906a0f68 - depends: - - __osx >=11.0 - - libcxx >=19 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 244772 - timestamp: 1757371008525 -- conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h5bddc39_9.conda - sha256: 690cf749692c8ea556646d1a47b5824ad41b2f6dfd949e4cdb6c44a352fcb1aa - md5: a6c8f8ee856f7c3c1576e14b86cd8038 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - libsodium >=1.0.20,<1.0.21.0a0 - - krb5 >=1.21.3,<1.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 265212 - timestamp: 1757370864284 -- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhd8ed1ab_0.conda - sha256: 7560d21e1b021fd40b65bfb72f67945a3fcb83d78ad7ccf37b8b3165ec3b68ad - md5: df5e78d904988eb55042c0c97446079f - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/zipp?source=hash-mapping - size: 22963 - timestamp: 1749421737203 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py311haee01d2_0.conda - sha256: ed149760ea78e038e6424d8a327ea95da351727536c0e9abedccf5a61fc19932 - md5: 0fd242142b0691eb9311dc32c1d4ab76 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.11.* *_cp311 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 466651 - timestamp: 1757930101225 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.25.0-py313h54dd161_0.conda - sha256: 9d79d176afe50361cc3fd4366bedff20852dbea1e5b03f358b55f12aca22d60d - md5: 1fe43bd1fc86e22ad3eb0edec637f8a2 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 471152 - timestamp: 1757930114245 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py311h62e9434_0.conda - sha256: be241ea3ca603d68654beeab4c991c225c9361378a107f72c2433ddfdff88132 - md5: 5425495af6b0b010230320d618022f20 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=10.13 - - python_abi 3.11.* *_cp311 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 462903 - timestamp: 1757930157317 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zstandard-0.25.0-py313hcb05632_0.conda - sha256: 1df6717571a177a135622399708878c84620be5bd9a72dc67814486595ecb509 - md5: 7fbc3b11a6c969de321061575594eba7 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=10.13 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 469089 - timestamp: 1757930140546 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py311h5bb9006_0.conda - sha256: fb1443a1479a6d709b4af7a2cdcea3ef2a5e859378de19814086fa86ca6f934e - md5: c7e0f1b714bd12d39899a4f0c296dd86 - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=11.0 - - python 3.11.* *_cpython - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 390089 - timestamp: 1757930124840 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstandard-0.25.0-py313h9734d34_0.conda - sha256: 8a1bf8a66c05f724e8a56cb1918eae70bcb467a7c5d43818e37e04d86332c513 - md5: ce17795bf104a29a2c7ed0bba7a804cb - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - __osx >=11.0 - - python 3.13.* *_cp313 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 396477 - timestamp: 1757930170468 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py311hf893f09_0.conda - sha256: 3b66d3cb738a9993e8432d1a03402d207128166c4ef0c928e712958e51aff325 - md5: d26077d20b4bba54f4c718ed1313805f - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 375866 - timestamp: 1757930134099 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstandard-0.25.0-py313h5fd188c_0.conda - sha256: d20a163a466621e41c3d06bc5dc3040603b8b0bfa09f493e2bfc0dbd5aa6e911 - md5: edfe43bb6e955d4d9b5e7470ea92dead - depends: - - python - - cffi >=1.11 - - zstd >=1.5.7,<1.5.8.0a0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause - purls: - - pkg:pypi/zstandard?source=hash-mapping - size: 380849 - timestamp: 1757930139118 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb - md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 567578 - timestamp: 1742433379869 -- conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda - sha256: c171c43d0c47eed45085112cb00c8c7d4f0caa5a32d47f2daca727e45fb98dca - md5: cd60a4a5a8d6a476b30d8aa4bb49251a - depends: - - __osx >=10.13 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 485754 - timestamp: 1742433356230 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda - sha256: 0d02046f57f7a1a3feae3e9d1aa2113788311f3cf37a3244c71e61a93177ba67 - md5: e6f69c7bcccdefa417f056fa593b40f0 - depends: - - __osx >=11.0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 399979 - timestamp: 1742433432699 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - sha256: bc64864377d809b904e877a98d0584f43836c9f2ef27d3d2a1421fa6eae7ca04 - md5: 21f56217d6125fb30c3c3f10c786d751 - depends: - - libzlib >=1.3.1,<2.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 354697 - timestamp: 1742433568506 diff --git a/pixi.toml b/pixi.toml index bcc2feb8..da1fc721 100644 --- a/pixi.toml +++ b/pixi.toml @@ -2,7 +2,7 @@ # ENVIRONMENT VARIABLES ####################### -# Platform-independent +# Platform-independent [activation.env] PYTHONIOENCODING = "utf-8" @@ -16,11 +16,11 @@ PYTHONIOENCODING = "utf-8" # Unix/macOS [target.unix.activation.env] -PYTHONPATH = "src${PYTHONPATH:+:${PYTHONPATH}}" # remove ":" if needed +PYTHONPATH = "${PIXI_PROJECT_ROOT}/src${PYTHONPATH:+:${PYTHONPATH}}" # Windows [target.win.activation.env] -PYTHONPATH = "src;%PYTHONPATH%" +PYTHONPATH = "${PIXI_PROJECT_ROOT}/src;%PYTHONPATH%" ########### # WORKSPACE @@ -40,16 +40,19 @@ channels = ['conda-forge'] # Default feature configuration [dependencies] # == [feature.default.dependencies] -pip = '*' # Required to install from PyPI -jupyterlab = '*' # JupyterLab for notebooks +pip = '*' # Required to install from PyPI [target.win-64.dependencies] libcblas = '*' # CBLAS library for linear algebra; required for pdffit2. [pypi-dependencies] # == [feature.default.pypi-dependencies] -pixi-kernel = '*' # Pixi Jupyter kernel -uv = '*' # Python package manager -easydiffraction = { version = '*', extras = ['visualization'] } # Main package +jupyterlab = '*' # JupyterLab for notebooks +pixi-kernel = '*' # Pixi Jupyter kernel +uv = '*' # Python package manager +easydiffraction = { version = '*', extras = ['all'] } # Main package + +[target.osx.pypi-dependencies] +h5py = "==3.14.0" # macOS only: avoid broken 3.15.0 wheel # Features for specific Python versions @@ -93,6 +96,7 @@ unit-tests = 'python -m pytest tests/unit/ --color=yes -v' func-tests = 'python -m pytest tests/functional/ --color=yes -n auto -v' notebook-tests = 'python -m pytest --nbmake tutorials/ --nbmake-timeout=600 --color=yes -n auto -v' script-tests = 'python -m pytest tools/test_scripts.py --color=yes -n auto -v' +extra = 'python -m pytest tests/unit/extra.py -q --tb=no --disable-warnings --color=yes' test = { depends-on = ['unit-tests'] } @@ -125,7 +129,8 @@ check = { depends-on = [ ### 🛠️ Fixes py-lint-fix = 'pixi run py-lint-check --fix' -py-format-fix = "python -m ruff format $(git diff --cached --name-only -- '*.py')" +#py-format-fix = "python -m ruff format $(git diff --cached --name-only -- '*.py')" +py-format-fix = "python -m ruff format" nonpy-format-fix = 'pixi run nonpy-format-check --write' nonpy-format-fix-modified = "pixi run nonpy-format-check-modified --write" notebook-format-fix = 'pixi run notebook-format-check --fix' @@ -184,11 +189,12 @@ docs-setup = { depends-on = [ dist-build = 'python -m build --wheel --outdir dist' spdx-update = 'python tools/update_spdx.py' #dev-install = 'uv pip install --requirements pyproject.toml --extra all' -dev-install = "uv pip install --editable '.[all]'" +dev-install = "python -m uv pip install --editable '.[all]'" npm-config = 'npm config set registry https://registry.npmjs.org/' prettier-install = 'npm install --no-save --no-audit --no-fund prettier prettier-plugin-toml' pre-commit-setup = 'pre-commit clean && pre-commit uninstall && pre-commit install --hook-type pre-commit --hook-type pre-push --overwrite' pre-commit-update = 'pre-commit autoupdate' +clean-pycache = "find . -type d -name '__pycache__' -prune -exec rm -rf '{}' +" dev = { depends-on = [ 'dev-install', @@ -199,7 +205,5 @@ dev = { depends-on = [ wheel = { depends-on = ['npm-config', 'prettier-install'] } -## 🔗 Shortcuts +## 🔗 Main Package Shortcut easydiffraction = 'python -m easydiffraction' -tutorials-list = 'python -m easydiffraction list-tutorials' -tutorials-fetch = 'python -m easydiffraction fetch-tutorials' diff --git a/pyproject.toml b/pyproject.toml index 45b0beea..bfe639e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,8 @@ dependencies = [ 'cryspy', # Calculations of diffraction patterns 'diffpy.pdffit2', # Calculations of Pair Distribution Function (PDF), Python >=3.11,<3.14 'diffpy.utils', # Utilities for PDF calculations + 'uncertainties', # Propagation of uncertainties + 'typeguard', # Runtime type checking ] [project.optional-dependencies] @@ -65,7 +67,7 @@ docs = [ 'mkdocs', # Static site generator 'mkdocs-material', # Documentation framework on top of MkDocs 'mkdocs-autorefs<1.3.0', # MkDocs: Auto-references support. 1.3.0 => DeprecationWarning: Setting a fallback anchor function is deprecated and ... - 'mkdocs-jupyter', # MkDocs: Jupyter notebook support + 'mkdocs-jupyter', # MkDocs: Jupyter notebook support. 'mkdocs-plugin-inline-svg', # MkDocs: Inline SVG support 'mkdocs-markdownextradata-plugin', # MkDocs: Markdown extra data support, such as global variables 'mkdocstrings-python', # MkDocs: Python docstring support @@ -78,9 +80,9 @@ visualization = [ 'py3Dmol', # Visualisation of crystal structures ] all = [ - "easydiffraction[dev]", - "easydiffraction[docs]", - "easydiffraction[visualization]", + 'easydiffraction[dev]', + 'easydiffraction[docs]', + 'easydiffraction[visualization]', ] [project.urls] @@ -160,8 +162,9 @@ close-quotes-on-newline = true # https://interrogate.readthedocs.io/en/latest/ [tool.interrogate] -fail-under = 35 # Temporarily reduce to allow gradual improvement +fail-under = 35 # Temporarily reduce to allow gradual improvement verbose = 1 +exclude = ["src/**/__init__.py"] ####################################### # Configuration for coverage/pytest-cov @@ -169,12 +172,12 @@ verbose = 1 [tool.coverage.run] branch = true # Measure branch coverage as well -source = ["src/easydiffraction"] # Limit coverage to the source code directory +source = ['src/easydiffraction'] # Limit coverage to the source code directory [tool.coverage.report] show_missing = true # Show missing lines skip_covered = true # Skip files with 100% coverage in the report -fail_under = 58 # Temporarily reduce to allow gradual improvement +fail_under = 65 # Temporarily reduce to allow gradual improvement ######################## # Configuration for ruff @@ -185,7 +188,7 @@ fail_under = 58 # Temporarily reduce to allow gradual improvement [tool.ruff] # Temporarily exclude some directories until we have improved the code quality there -exclude = ['tests', 'tutorials-drafts'] +exclude = ['tests', 'tmp'] indent-width = 4 line-length = 99 # Enable new rules that are not yet stable, like DOC @@ -247,10 +250,10 @@ ignore = [ 'DTZ005', # Ignore: `datetime.datetime.now()` called without a `tz` argument ] -# Temporarily increase McCabe complexity limit to 17 to allow +# Temporarily increase McCabe complexity limit to 19 to allow # refactoring in smaller steps. [tool.ruff.lint.mccabe] -max-complexity = 17 # default is 10 +max-complexity = 19 # default is 10 [tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = 'all' @@ -262,11 +265,11 @@ force-single-line = true '*test_*.py' = ['S101'] # allow asserts in test files [tool.ruff.lint.pycodestyle] -max-line-length = 99 # https://peps.python.org/pep-0008/#maximum-line-length -max-doc-length = 72 # https://peps.python.org/pep-0008/#maximum-line-length +max-line-length = 100 #99# https://peps.python.org/pep-0008/#maximum-line-length +max-doc-length = 72 # https://peps.python.org/pep-0008/#maximum-line-length [tool.ruff.lint.pydocstyle] -convention = "google" +convention = 'google' [tool.ruff.format] docstring-code-format = true # Whether to format code snippets in docstrings diff --git a/pytest.ini b/pytest.ini index 9aae88b7..bb1f5ba8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,10 @@ [pytest] +addopts = --import-mode=importlib markers = fast: mark test as fast (should be run on every push) + functional: mark test as functional (slow/integration; opt-in) +testpaths = + tests +filterwarnings = + ignore::DeprecationWarning:cryspy\. + ignore:.*scipy\.misc is deprecated.*:DeprecationWarning diff --git a/src/easydiffraction/__init__.py b/src/easydiffraction/__init__.py index c2ace01e..ac7a581c 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -1,128 +1,42 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import TYPE_CHECKING - -# This is needed for static type checkers like mypy and IDEs to -# recognize the imports without actually importing them at runtime, -# which helps avoid circular dependencies and reduces initial load time. -if TYPE_CHECKING: - # Analysis - from easydiffraction.analysis.analysis import Analysis - - # Experiments - from easydiffraction.experiments.experiment import Experiment - from easydiffraction.experiments.experiments import Experiments - - # Project management - from easydiffraction.project import Project - from easydiffraction.project import ProjectInfo - - # Sample model - from easydiffraction.sample_models.sample_model import SampleModel - from easydiffraction.sample_models.sample_models import SampleModels - - # Summary - from easydiffraction.summary import Summary - - # Utils - from easydiffraction.utils.formatting import chapter - from easydiffraction.utils.formatting import paragraph - from easydiffraction.utils.formatting import section - from easydiffraction.utils.utils import download_from_repository - from easydiffraction.utils.utils import fetch_tutorials - from easydiffraction.utils.utils import get_value_from_xye_header - from easydiffraction.utils.utils import list_tutorials - from easydiffraction.utils.utils import show_version - - -# Lazy loading of submodules and classes -# This improves initial import time and reduces memory usage -# when only a subset of functionality is needed. -def __getattr__(name): - if name == 'Analysis': - from easydiffraction.analysis.analysis import Analysis - - return Analysis - elif name == 'Experiment': - from easydiffraction.experiments.experiment import Experiment - - return Experiment - elif name == 'Experiments': - from easydiffraction.experiments.experiments import Experiments - - return Experiments - elif name == 'Project': - from easydiffraction.project import Project - - return Project - elif name == 'ProjectInfo': - from easydiffraction.project import ProjectInfo - - return ProjectInfo - elif name == 'SampleModel': - from easydiffraction.sample_models.sample_model import SampleModel - - return SampleModel - elif name == 'SampleModels': - from easydiffraction.sample_models.sample_models import SampleModels - - return SampleModels - elif name == 'Summary': - from easydiffraction.summary import Summary - - return Summary - elif name == 'chapter': - from easydiffraction.utils.formatting import chapter - - return chapter - elif name == 'section': - from easydiffraction.utils.formatting import section - - return section - elif name == 'paragraph': - from easydiffraction.utils.formatting import paragraph - - return paragraph - elif name == 'download_from_repository': - from easydiffraction.utils.utils import download_from_repository - - return download_from_repository - elif name == 'fetch_tutorials': - from easydiffraction.utils.utils import fetch_tutorials - - return fetch_tutorials - elif name == 'list_tutorials': - from easydiffraction.utils.utils import list_tutorials - - return list_tutorials - elif name == 'get_value_from_xye_header': - from easydiffraction.utils.utils import get_value_from_xye_header - - return get_value_from_xye_header - elif name == 'show_version': - from easydiffraction.utils.utils import show_version - - return show_version - raise AttributeError(f"module 'easydiffraction' has no attribute {name}") +from importlib import import_module + +from easydiffraction.utils.formatting import chapter +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import section +from easydiffraction.utils.logging import Logger +from easydiffraction.utils.logging import log + +Logger.configure() + +_LAZY_ENTRIES = [ + ('easydiffraction.project.project', 'Project'), + ('easydiffraction.experiments.experiment.factory', 'ExperimentFactory'), + ('easydiffraction.sample_models.sample_model.factory', 'SampleModelFactory'), + ('easydiffraction.utils.utils', 'download_from_repository'), + ('easydiffraction.utils.utils', 'fetch_tutorials'), + ('easydiffraction.utils.utils', 'list_tutorials'), + ('easydiffraction.utils.utils', 'get_value_from_xye_header'), + ('easydiffraction.utils.utils', 'show_version'), +] +_LAZY_MAP = {attr_name: module_name for module_name, attr_name in _LAZY_ENTRIES} -# Expose the public API -__all__ = [ - 'Project', - 'ProjectInfo', - 'SampleModel', - 'SampleModels', - 'Experiment', - 'Experiments', - 'Analysis', - 'Summary', +__all__ = list(_LAZY_MAP.keys()) + [ + 'Logger', + 'log', 'chapter', 'section', 'paragraph', - 'download_from_repository', - 'fetch_tutorials', - 'list_tutorials', - 'get_value_from_xye_header', - 'show_version', ] + + +def __getattr__(name): + if name not in _LAZY_MAP: + raise AttributeError() + module_name = _LAZY_MAP[name] + module = import_module(module_name) + attr = getattr(module, name) + return attr diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index 700f21b6..b0bbc732 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -7,14 +7,14 @@ import pandas as pd -from easydiffraction.analysis.calculators.calculator_factory import CalculatorFactory -from easydiffraction.analysis.collections.aliases import Aliases -from easydiffraction.analysis.collections.constraints import Constraints -from easydiffraction.analysis.collections.joint_fit_experiments import JointFitExperiments -from easydiffraction.analysis.minimization import DiffractionMinimizer -from easydiffraction.analysis.minimizers.minimizer_factory import MinimizerFactory -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter +from easydiffraction.analysis.calculators.factory import CalculatorFactory +from easydiffraction.analysis.categories.aliases import Aliases +from easydiffraction.analysis.categories.constraints import Constraints +from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiments +from easydiffraction.analysis.fitting import Fitter +from easydiffraction.analysis.minimizers.factory import MinimizerFactory +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.parameters import Parameter from easydiffraction.core.singletons import ConstraintsHandler from easydiffraction.experiments.experiments import Experiments from easydiffraction.utils.formatting import paragraph @@ -24,9 +24,34 @@ class Analysis: + """High-level orchestration of analysis tasks for a Project. + + This class wires calculators and minimizers, exposes a compact + interface for parameters, constraints and results, and coordinates + computations across the project's sample models and experiments. + + Typical usage: + + - Display or filter parameters to fit. + - Select a calculator/minimizer implementation. + - Calculate patterns and run single or joint fits. + + Attributes: + project: The parent Project object. + aliases: A registry of human-friendly aliases for parameters. + constraints: Symbolic constraints between parameters. + calculator: Active calculator used for computations. + fitter: Active fitter/minimizer driver. + """ + _calculator = CalculatorFactory.create_calculator('cryspy') def __init__(self, project) -> None: + """Create a new Analysis instance bound to a project. + + Args: + project: The project that owns models and experiments. + """ self.project = project self.aliases = Aliases() self.constraints = Constraints() @@ -34,16 +59,16 @@ def __init__(self, project) -> None: self.calculator = Analysis._calculator # Default calculator shared by project self._calculator_key: str = 'cryspy' # Added to track the current calculator self._fit_mode: str = 'single' - self.fitter = DiffractionMinimizer('lmfit (leastsq)') + self.fitter = Fitter('lmfit (leastsq)') def _get_params_as_dataframe( self, - params: List[Union[Descriptor, Parameter]], + params: List[Union[NumericDescriptor, Parameter]], ) -> pd.DataFrame: """Convert a list of parameters to a DataFrame. Args: - params: List of Descriptor or Parameter objects. + params: List of DescriptorFloat or Parameter objects. Returns: A pandas DataFrame containing parameter information. @@ -51,11 +76,11 @@ def _get_params_as_dataframe( rows = [] for param in params: common_attrs = {} - if isinstance(param, (Descriptor, Parameter)): + if isinstance(param, (NumericDescriptor, Parameter)): common_attrs = { - 'datablock': param.datablock_id, - 'category': param.category_key, - 'entry': param.collection_entry_id, + 'datablock': param._identity.datablock_entry_name, + 'category': param._identity.category_code, + 'entry': param._identity.category_entry_name, 'parameter': param.name, 'value': param.value, 'units': param.units, @@ -66,8 +91,8 @@ def _get_params_as_dataframe( param_attrs = { 'fittable': True, 'free': param.free, - 'min': param.min, - 'max': param.max, + 'min': param.fit_min, + 'max': param.fit_max, 'uncertainty': f'{param.uncertainty:.4f}' if param.uncertainty else '', 'value': f'{param.value:.4f}', 'units': param.units, @@ -79,8 +104,11 @@ def _get_params_as_dataframe( return dataframe def show_all_params(self) -> None: - sample_models_params = self.project.sample_models.get_all_params() - experiments_params = self.project.experiments.get_all_params() + """Print a table with all parameters for sample models and + experiments. + """ + sample_models_params = self.project.sample_models.parameters + experiments_params = self.project.experiments.parameters if not sample_models_params and not experiments_params: print(warning('No parameters found.')) @@ -126,8 +154,11 @@ def show_all_params(self) -> None: ) def show_fittable_params(self) -> None: - sample_models_params = self.project.sample_models.get_fittable_params() - experiments_params = self.project.experiments.get_fittable_params() + """Print a table with parameters that can be included in + fitting. + """ + sample_models_params = self.project.sample_models.fittable_parameters + experiments_params = self.project.experiments.fittable_parameters if not sample_models_params and not experiments_params: print(warning('No fittable parameters found.')) @@ -177,8 +208,11 @@ def show_fittable_params(self) -> None: ) def show_free_params(self) -> None: - sample_models_params = self.project.sample_models.get_free_params() - experiments_params = self.project.experiments.get_free_params() + """Print a table with only currently-free (varying) + parameters. + """ + sample_models_params = self.project.sample_models.free_parameters + experiments_params = self.project.experiments.free_parameters free_params = sample_models_params + experiments_params if not free_params: @@ -225,8 +259,15 @@ def show_free_params(self) -> None: ) def how_to_access_parameters(self) -> None: - sample_models_params = self.project.sample_models.get_all_params() - experiments_params = self.project.experiments.get_all_params() + """Show Python access paths and CIF unique IDs for all + parameters. + + The output explains how to reference specific parameters in code + and which unique identifiers are used when creating CIF-based + constraints. + """ + sample_models_params = self.project.sample_models.parameters + experiments_params = self.project.experiments.parameters all_params = { 'sample_models': sample_models_params, 'experiments': experiments_params, @@ -258,22 +299,23 @@ def how_to_access_parameters(self) -> None: project_varname = self.project._varname for datablock_type, params in all_params.items(): for param in params: - if isinstance(param, (Descriptor, Parameter)): - datablock_id = param.datablock_id - category_key = param.category_key - entry_id = param.collection_entry_id + if isinstance(param, (NumericDescriptor, Parameter)): + datablock_entry_name = param._identity.datablock_entry_name + category_code = param._identity.category_code + category_entry_name = param._identity.category_entry_name param_key = param.name code_variable = ( - f"{project_varname}.{datablock_type}['{datablock_id}'].{category_key}" + f'{project_varname}.{datablock_type}' + f"['{datablock_entry_name}'].{category_code}" ) - if entry_id: - code_variable += f"['{entry_id}']" + if category_entry_name: + code_variable += f"['{category_entry_name}']" code_variable += f'.{param_key}' - cif_uid = param._generate_human_readable_unique_id() + cif_uid = param._cif_handler.uid columns_data.append([ - datablock_id, - category_key, - entry_id, + datablock_entry_name, + category_code, + category_entry_name, param_key, code_variable, cif_uid, @@ -288,19 +330,31 @@ def how_to_access_parameters(self) -> None: ) def show_current_calculator(self) -> None: + """Print the name of the currently selected calculator + engine. + """ print(paragraph('Current calculator')) print(self.current_calculator) @staticmethod def show_supported_calculators() -> None: + """Print a table of available calculator backends on this + system. + """ CalculatorFactory.show_supported_calculators() @property def current_calculator(self) -> str: + """The key/name of the active calculator backend.""" return self._calculator_key @current_calculator.setter def current_calculator(self, calculator_name: str) -> None: + """Switch to a different calculator backend. + + Args: + calculator_name: Calculator key to use (e.g. 'cryspy'). + """ calculator = CalculatorFactory.create_calculator(calculator_name) if calculator is None: return @@ -310,41 +364,68 @@ def current_calculator(self, calculator_name: str) -> None: print(self.current_calculator) def show_current_minimizer(self) -> None: + """Print the name of the currently selected minimizer.""" print(paragraph('Current minimizer')) print(self.current_minimizer) @staticmethod def show_available_minimizers() -> None: + """Print a table of available minimizer drivers on this + system. + """ MinimizerFactory.show_available_minimizers() @property def current_minimizer(self) -> Optional[str]: + """The identifier of the active minimizer, if any.""" return self.fitter.selection if self.fitter else None @current_minimizer.setter def current_minimizer(self, selection: str) -> None: - self.fitter = DiffractionMinimizer(selection) + """Switch to a different minimizer implementation. + + Args: + selection: Minimizer selection string, e.g. + 'lmfit (leastsq)'. + """ + self.fitter = Fitter(selection) print(paragraph('Current minimizer changed to')) print(self.current_minimizer) @property def fit_mode(self) -> str: + """Current fitting strategy: either 'single' or 'joint'.""" return self._fit_mode @fit_mode.setter def fit_mode(self, strategy: str) -> None: + """Set the fitting strategy. + + When set to 'joint', all experiments get default weights and + are used together in a single optimization. + + Args: + strategy: Either 'single' or 'joint'. + + Raises: + ValueError: If an unsupported strategy value is + provided. + """ if strategy not in ['single', 'joint']: raise ValueError("Fit mode must be either 'single' or 'joint'") self._fit_mode = strategy if strategy == 'joint' and not hasattr(self, 'joint_fit_experiments'): # Pre-populate all experiments with weight 0.5 self.joint_fit_experiments = JointFitExperiments() - for id in self.project.experiments.ids: - self.joint_fit_experiments.add(id, weight=0.5) + for id in self.project.experiments.names: + self.joint_fit_experiments.add_from_args(id=id, weight=0.5) print(paragraph('Current fit mode changed to')) print(self._fit_mode) def show_available_fit_modes(self) -> None: + """Print all supported fitting strategies and their + descriptions. + """ strategies = [ { 'Strategy': 'single', @@ -373,22 +454,26 @@ def show_available_fit_modes(self) -> None: ) def show_current_fit_mode(self) -> None: + """Print the currently active fitting strategy.""" print(paragraph('Current fit mode')) print(self.fit_mode) def calculate_pattern(self, expt_name: str) -> None: - """Calculate the diffraction pattern for a given experiment. The - calculated pattern is stored within the experiment's datastore. + """Calculate and store the diffraction pattern for an + experiment. + + The pattern is stored in the target experiment's datastore. Args: - expt_name: The name of the experiment. + expt_name: The identifier of the experiment to compute. """ experiment = self.project.experiments[expt_name] sample_models = self.project.sample_models self.calculator.calculate_pattern(sample_models, experiment) def show_constraints(self) -> None: - constraints_dict = self.constraints._items + """Print a table of all user-defined symbolic constraints.""" + constraints_dict = dict(self.constraints) if not self.constraints._items: print(warning('No constraints defined.')) @@ -415,6 +500,9 @@ def show_constraints(self) -> None: ) def apply_constraints(self): + """Apply the currently defined constraints to the active + project. + """ if not self.constraints._items: print(warning('No constraints defined.')) return @@ -424,6 +512,14 @@ def apply_constraints(self): self.constraints_handler.apply() def fit(self): + """Execute fitting using the selected mode, calculator and + minimizer. + + In 'single' mode, fits each experiment independently. In + 'joint' mode, performs a simultaneous fit across experiments + with weights. + Sets :attr:`fit_results` on success. + """ sample_models = self.project.sample_models if not sample_models: print('No sample models found in the project. Cannot run fit.') @@ -440,7 +536,7 @@ def fit(self): return # Run the fitting process - experiment_ids = experiments.ids + experiment_ids = experiments.names if self.fit_mode == 'joint': print( @@ -455,7 +551,7 @@ def fit(self): weights=self.joint_fit_experiments, ) elif self.fit_mode == 'single': - for expt_name in experiments.ids: + for expt_name in experiments.names: print( paragraph(f"Using experiment 🔬 '{expt_name}' for '{self.fit_mode}' fitting") ) @@ -470,24 +566,19 @@ def fit(self): self.fit_results = self.fitter.results def as_cif(self): - current_minimizer = self.current_minimizer - if ' ' in current_minimizer: - current_minimizer = f'"{current_minimizer}"' - - lines = [] - lines.append(f'_analysis.calculator_engine {self.current_calculator}') - lines.append(f'_analysis.fitting_engine {current_minimizer}') - lines.append(f'_analysis.fit_mode {self.fit_mode}') + """Serialize the analysis section to a CIF string. - lines.append('') - lines.append(self.aliases.as_cif()) - - lines.append('') - lines.append(self.constraints.as_cif()) + Returns: + The analysis section represented as a CIF document string. + """ + from easydiffraction.io.cif.serialize import analysis_to_cif - return '\n'.join(lines) + return analysis_to_cif(self) def show_as_cif(self) -> None: + """Render the analysis section as CIF in a formatted console + view. + """ cif_text: str = self.as_cif() paragraph_title: str = paragraph('Analysis 🧮 info as cif') render_cif(cif_text, paragraph_title) diff --git a/src/easydiffraction/analysis/calculation.py b/src/easydiffraction/analysis/calculation.py index 3456973f..6d3ee9fc 100644 --- a/src/easydiffraction/analysis/calculation.py +++ b/src/easydiffraction/analysis/calculation.py @@ -5,18 +5,18 @@ from typing import List from typing import Optional -from easydiffraction.analysis.calculators.calculator_factory import CalculatorFactory -from easydiffraction.experiments.experiment import Experiment +from easydiffraction.analysis.calculators.factory import CalculatorFactory +from easydiffraction.experiments.experiment.base import ExperimentBase from easydiffraction.experiments.experiments import Experiments from easydiffraction.sample_models.sample_models import SampleModels -class DiffractionCalculator: +class Calculator: """Invokes calculation engines for pattern generation.""" def __init__(self, engine: str = 'cryspy') -> None: - """Initialize the DiffractionCalculator with a specified backend - engine. + """Initialize the diffraction calculator with a specified + backend engine. Args: engine: Type of the calculation engine to use. @@ -54,7 +54,7 @@ def calculate_structure_factors( def calculate_pattern( self, sample_models: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, ) -> None: """Calculate diffraction pattern based on sample models and experiment. The calculated pattern is stored within the diff --git a/src/easydiffraction/analysis/calculators/calculator_base.py b/src/easydiffraction/analysis/calculators/base.py similarity index 85% rename from src/easydiffraction/analysis/calculators/calculator_base.py rename to src/easydiffraction/analysis/calculators/base.py index 7fef0b91..27d87b6e 100644 --- a/src/easydiffraction/analysis/calculators/calculator_base.py +++ b/src/easydiffraction/analysis/calculators/base.py @@ -9,8 +9,8 @@ import numpy as np from easydiffraction.core.singletons import ConstraintsHandler -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.sample_models.sample_model import SampleModel +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.sample_models.sample_model.base import SampleModelBase from easydiffraction.sample_models.sample_models import SampleModels @@ -30,8 +30,8 @@ def engine_imported(self) -> bool: @abstractmethod def calculate_structure_factors( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, ) -> None: """Calculate structure factors for a single sample model and experiment. @@ -41,7 +41,7 @@ def calculate_structure_factors( def calculate_pattern( self, sample_models: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> None: """Calculate the diffraction pattern for multiple sample models @@ -66,7 +66,7 @@ def calculate_pattern( # Calculate contributions from valid linked sample models y_calc_scaled = y_calc_zeros for linked_phase in valid_linked_phases: - sample_model_id = linked_phase._entry_id + sample_model_id = linked_phase._identity.category_entry_name sample_model_scale = linked_phase.scale.value sample_model = sample_models[sample_model_id] @@ -87,7 +87,9 @@ def calculate_pattern( # Calculate background contribution y_bkg = np.zeros_like(x_data) - if hasattr(experiment, 'background'): + # TODO: Change to the following check in other places instead of + # old `hasattr` check, because `hasattr` triggers warnings? + if 'background' in experiment._public_attrs(): y_bkg = experiment.background.calculate(x_data) experiment.datastore.bkg = y_bkg @@ -99,7 +101,7 @@ def calculate_pattern( def _calculate_single_model_pattern( self, sample_model: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, called_by_minimizer: bool, ) -> np.ndarray: """Calculate the diffraction pattern for a single sample model @@ -119,7 +121,7 @@ def _calculate_single_model_pattern( def _get_valid_linked_phases( self, sample_models: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, ) -> List[Any]: """Get valid linked phases from the experiment. @@ -136,10 +138,10 @@ def _get_valid_linked_phases( valid_linked_phases = [] for linked_phase in experiment.linked_phases: - if linked_phase._entry_id not in sample_models.get_ids(): + if linked_phase._identity.category_entry_name not in sample_models.names: print( f"Warning: Linked phase '{linked_phase.id.value}' not " - f'found in Sample Models {sample_models.get_ids()}' + f'found in Sample Models {sample_models.names}' ) continue valid_linked_phases.append(linked_phase) diff --git a/src/easydiffraction/analysis/calculators/calculator_crysfml.py b/src/easydiffraction/analysis/calculators/crysfml.py similarity index 94% rename from src/easydiffraction/analysis/calculators/calculator_crysfml.py rename to src/easydiffraction/analysis/calculators/crysfml.py index 76edb5a7..b58bddb5 100644 --- a/src/easydiffraction/analysis/calculators/calculator_crysfml.py +++ b/src/easydiffraction/analysis/calculators/crysfml.py @@ -8,10 +8,10 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.experiments.experiment import Experiment +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.experiments.experiment.base import ExperimentBase from easydiffraction.experiments.experiments import Experiments -from easydiffraction.sample_models.sample_models import SampleModel +from easydiffraction.sample_models.sample_model.base import SampleModelBase from easydiffraction.sample_models.sample_models import SampleModels try: @@ -54,7 +54,7 @@ def calculate_structure_factors( def _calculate_single_model_pattern( self, sample_model: SampleModels, - experiment: Experiment, + experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> Union[np.ndarray, List[float]]: """Calculates the diffraction pattern using Crysfml for the @@ -105,8 +105,8 @@ def _adjust_pattern_length( def _crysfml_dict( self, sample_model: SampleModels, - experiment: Experiment, - ) -> Dict[str, Union[Experiment, SampleModel]]: + experiment: ExperimentBase, + ) -> Dict[str, Union[ExperimentBase, SampleModelBase]]: """Converts the sample model and experiment into a dictionary format for Crysfml. @@ -127,7 +127,7 @@ def _crysfml_dict( def _convert_sample_model_to_dict( self, - sample_model: SampleModel, + sample_model: SampleModelBase, ) -> Dict[str, Any]: """Converts a sample model into a dictionary format. @@ -167,7 +167,7 @@ def _convert_sample_model_to_dict( def _convert_experiment_to_dict( self, - experiment: Experiment, + experiment: ExperimentBase, ) -> Dict[str, Any]: """Converts an experiment into a dictionary format. diff --git a/src/easydiffraction/analysis/calculators/calculator_cryspy.py b/src/easydiffraction/analysis/calculators/cryspy.py similarity index 81% rename from src/easydiffraction/analysis/calculators/calculator_cryspy.py rename to src/easydiffraction/analysis/calculators/cryspy.py index d2a345a3..0a3d665a 100644 --- a/src/easydiffraction/analysis/calculators/calculator_cryspy.py +++ b/src/easydiffraction/analysis/calculators/cryspy.py @@ -11,10 +11,10 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.sample_models.sample_model import SampleModel +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.sample_models.sample_model.base import SampleModelBase try: import cryspy @@ -49,8 +49,8 @@ def __init__(self) -> None: def calculate_structure_factors( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, ) -> None: """Raises a NotImplementedError as HKL calculation is not implemented. @@ -65,8 +65,8 @@ def calculate_structure_factors( def _calculate_single_model_pattern( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, called_by_minimizer: bool = False, ) -> Union[np.ndarray, List[float]]: """Calculates the diffraction pattern using Cryspy for the given @@ -141,8 +141,8 @@ def _calculate_single_model_pattern( def _recreate_cryspy_dict( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, ) -> Dict[str, Any]: """Recreates the Cryspy dictionary for the given sample model and experiment. @@ -160,6 +160,8 @@ def _recreate_cryspy_dict( cryspy_model_id = f'crystal_{sample_model.name}' cryspy_model_dict = cryspy_dict[cryspy_model_id] + # Update sample model parameters + # Cell cryspy_cell = cryspy_model_dict['unit_cell_parameters'] cryspy_cell[0] = sample_model.cell.length_a.value @@ -186,15 +188,18 @@ def _recreate_cryspy_dict( for idx, atom_site in enumerate(sample_model.atom_sites): cryspy_biso[idx] = atom_site.b_iso.value - # ---------- Update experiment parameters ---------- + # Update experiment parameters + if experiment.type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: cryspy_expt_name = f'pd_{experiment.name}' cryspy_expt_dict = cryspy_dict[cryspy_expt_name] + # Instrument cryspy_expt_dict['offset_ttheta'][0] = np.deg2rad( experiment.instrument.calib_twotheta_offset.value ) cryspy_expt_dict['wavelength'][0] = experiment.instrument.setup_wavelength.value + # Peak cryspy_resolution = cryspy_expt_dict['resolution_parameters'] cryspy_resolution[0] = experiment.peak.broad_gauss_u.value @@ -206,6 +211,7 @@ def _recreate_cryspy_dict( elif experiment.type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: cryspy_expt_name = f'tof_{experiment.name}' cryspy_expt_dict = cryspy_dict[cryspy_expt_name] + # Instrument cryspy_expt_dict['zero'][0] = experiment.instrument.calib_d_to_tof_offset.value cryspy_expt_dict['dtt1'][0] = experiment.instrument.calib_d_to_tof_linear.value @@ -213,6 +219,7 @@ def _recreate_cryspy_dict( cryspy_expt_dict['ttheta_bank'] = np.deg2rad( experiment.instrument.setup_twotheta_bank.value ) + # Peak cryspy_sigma = cryspy_expt_dict['profile_sigmas'] cryspy_sigma[0] = experiment.peak.broad_gauss_sigma_0.value @@ -231,8 +238,8 @@ def _recreate_cryspy_dict( def _recreate_cryspy_obj( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, ) -> Any: """Recreates the Cryspy object for the given sample model and experiment. @@ -263,7 +270,7 @@ def _recreate_cryspy_obj( def _convert_sample_model_to_cryspy_cif( self, - sample_model: SampleModel, + sample_model: SampleModelBase, ) -> str: """Converts a sample model to a Cryspy CIF string. @@ -273,11 +280,11 @@ def _convert_sample_model_to_cryspy_cif( Returns: The Cryspy CIF string representation of the sample model. """ - return sample_model.as_cif() + return sample_model.as_cif def _convert_experiment_to_cryspy_cif( self, - experiment: Experiment, + experiment: ExperimentBase, linked_phase: Any, ) -> str: """Converts an experiment to a Cryspy CIF string. @@ -304,42 +311,54 @@ def _convert_experiment_to_cryspy_cif( cif_lines.append(f'_setup_radiation {radiation_probe}') if instrument: - instrument_mapping = { - 'setup_wavelength': '_setup_wavelength', - 'calib_twotheta_offset': '_setup_offset_2theta', - 'setup_twotheta_bank': '_tof_parameters_2theta_bank', - 'calib_d_to_tof_offset': '_tof_parameters_Zero', - 'calib_d_to_tof_linear': '_tof_parameters_Dtt1', - 'calib_d_to_tof_quad': '_tof_parameters_dtt2', - } + # Restrict to only attributes relevant for the beam mode to + # avoid probing non-existent guarded attributes (which + # triggers diagnostics). + if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + instrument_mapping = { + 'setup_wavelength': '_setup_wavelength', + 'calib_twotheta_offset': '_setup_offset_2theta', + } + elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + instrument_mapping = { + 'setup_twotheta_bank': '_tof_parameters_2theta_bank', + 'calib_d_to_tof_offset': '_tof_parameters_Zero', + 'calib_d_to_tof_linear': '_tof_parameters_Dtt1', + 'calib_d_to_tof_quad': '_tof_parameters_dtt2', + } cif_lines.append('') for local_attr_name, engine_key_name in instrument_mapping.items(): - if hasattr(instrument, local_attr_name): - attr_value = getattr(instrument, local_attr_name).value - cif_lines.append(f'{engine_key_name} {attr_value}') + # attr_obj = instrument.__dict__.get(local_attr_name) + attr_obj = getattr(instrument, local_attr_name) + if attr_obj is not None: + cif_lines.append(f'{engine_key_name} {attr_obj.value}') if peak: - peak_mapping = { - 'broad_gauss_u': '_pd_instr_resolution_U', - 'broad_gauss_v': '_pd_instr_resolution_V', - 'broad_gauss_w': '_pd_instr_resolution_W', - 'broad_lorentz_x': '_pd_instr_resolution_X', - 'broad_lorentz_y': '_pd_instr_resolution_Y', - 'broad_gauss_sigma_0': '_tof_profile_sigma0', - 'broad_gauss_sigma_1': '_tof_profile_sigma1', - 'broad_gauss_sigma_2': '_tof_profile_sigma2', - 'broad_mix_beta_0': '_tof_profile_beta0', - 'broad_mix_beta_1': '_tof_profile_beta1', - 'asym_alpha_0': '_tof_profile_alpha0', - 'asym_alpha_1': '_tof_profile_alpha1', - } - cif_lines.append('') - if expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + if expt_type.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH: + peak_mapping = { + 'broad_gauss_u': '_pd_instr_resolution_U', + 'broad_gauss_v': '_pd_instr_resolution_V', + 'broad_gauss_w': '_pd_instr_resolution_W', + 'broad_lorentz_x': '_pd_instr_resolution_X', + 'broad_lorentz_y': '_pd_instr_resolution_Y', + } + elif expt_type.beam_mode.value == BeamModeEnum.TIME_OF_FLIGHT: + peak_mapping = { + 'broad_gauss_sigma_0': '_tof_profile_sigma0', + 'broad_gauss_sigma_1': '_tof_profile_sigma1', + 'broad_gauss_sigma_2': '_tof_profile_sigma2', + 'broad_mix_beta_0': '_tof_profile_beta0', + 'broad_mix_beta_1': '_tof_profile_beta1', + 'asym_alpha_0': '_tof_profile_alpha0', + 'asym_alpha_1': '_tof_profile_alpha1', + } cif_lines.append('_tof_profile_peak_shape Gauss') + cif_lines.append('') for local_attr_name, engine_key_name in peak_mapping.items(): - if hasattr(peak, local_attr_name): - attr_value = getattr(peak, local_attr_name).value - cif_lines.append(f'{engine_key_name} {attr_value}') + # attr_obj = peak.__dict__.get(local_attr_name) + attr_obj = getattr(peak, local_attr_name) + if attr_obj is not None: + cif_lines.append(f'{engine_key_name} {attr_obj.value}') x_data = experiment.datastore.x twotheta_min = float(x_data.min()) diff --git a/src/easydiffraction/analysis/calculators/calculator_factory.py b/src/easydiffraction/analysis/calculators/factory.py similarity index 64% rename from src/easydiffraction/analysis/calculators/calculator_factory.py rename to src/easydiffraction/analysis/calculators/factory.py index d354dc29..e4e447c2 100644 --- a/src/easydiffraction/analysis/calculators/calculator_factory.py +++ b/src/easydiffraction/analysis/calculators/factory.py @@ -7,16 +7,24 @@ from typing import Type from typing import Union -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.analysis.calculators.calculator_crysfml import CrysfmlCalculator -from easydiffraction.analysis.calculators.calculator_cryspy import CryspyCalculator -from easydiffraction.analysis.calculators.calculator_pdffit import PdffitCalculator +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator +from easydiffraction.analysis.calculators.cryspy import CryspyCalculator +from easydiffraction.analysis.calculators.pdffit import PdffitCalculator from easydiffraction.utils.formatting import error from easydiffraction.utils.formatting import paragraph from easydiffraction.utils.utils import render_table class CalculatorFactory: + """Factory for creating calculation engine instances. + + The factory exposes discovery helpers to list and show available + calculators in the current environment and a creator that returns an + instantiated calculator or ``None`` if the requested one is not + available. + """ + _potential_calculators: Dict[str, Dict[str, Union[str, Type[CalculatorBase]]]] = { 'crysfml': { 'description': 'CrysFML library for crystallographic calculations', @@ -36,6 +44,14 @@ class CalculatorFactory: def _supported_calculators( cls, ) -> Dict[str, Dict[str, Union[str, Type[CalculatorBase]]]]: + """Return calculators whose engines are importable. + + This filters the list of potential calculators by instantiating + their classes and checking the ``engine_imported`` property. + + Returns: + Mapping from calculator name to its config dict. + """ return { name: cfg for name, cfg in cls._potential_calculators.items() @@ -44,10 +60,16 @@ def _supported_calculators( @classmethod def list_supported_calculators(cls) -> List[str]: + """List names of calculators available in the environment. + + Returns: + List of calculator identifiers, e.g. ``["crysfml", ...]``. + """ return list(cls._supported_calculators().keys()) @classmethod def show_supported_calculators(cls) -> None: + """Pretty-print supported calculators and their descriptions.""" columns_headers: List[str] = ['Calculator', 'Description'] columns_alignment = ['left', 'left'] columns_data: List[List[str]] = [] @@ -64,6 +86,14 @@ def show_supported_calculators(cls) -> None: @classmethod def create_calculator(cls, calculator_name: str) -> Optional[CalculatorBase]: + """Create a calculator instance by name. + + Args: + calculator_name: Identifier of the calculator to create. + + Returns: + A calculator instance or ``None`` if unknown or unsupported. + """ config = cls._supported_calculators().get(calculator_name) if not config: print(error(f"Unknown calculator '{calculator_name}'")) diff --git a/src/easydiffraction/analysis/calculators/calculator_pdffit.py b/src/easydiffraction/analysis/calculators/pdffit.py similarity index 87% rename from src/easydiffraction/analysis/calculators/calculator_pdffit.py rename to src/easydiffraction/analysis/calculators/pdffit.py index 25d55acf..153110d9 100644 --- a/src/easydiffraction/analysis/calculators/calculator_pdffit.py +++ b/src/easydiffraction/analysis/calculators/pdffit.py @@ -1,5 +1,10 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""PDF calculation backend using diffpy.pdffit2 if available. + +The class adapts the engine to EasyDiffraction calculator interface and +silences stdio on import to avoid noisy output in notebooks and logs. +""" import os import re @@ -8,9 +13,9 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.sample_models.sample_model import SampleModel +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.sample_models.sample_model.base import SampleModelBase try: from diffpy.pdffit2 import PdfFit @@ -51,8 +56,8 @@ def calculate_structure_factors(self, sample_models, experiments): def _calculate_single_model_pattern( self, - sample_model: SampleModel, - experiment: Experiment, + sample_model: SampleModelBase, + experiment: ExperimentBase, called_by_minimizer: bool = False, ): # Intentionally unused, required by public API/signature @@ -67,7 +72,7 @@ def _calculate_single_model_pattern( # TODO: move CIF v2 -> CIF v1 conversion to a separate module # Convert the sample model to CIF supported by PDFfit - cif_string_v2 = sample_model.as_cif() + cif_string_v2 = sample_model.as_cif # convert to version 1 of CIF format # this means: replace all dots with underscores for # cases where the dot is surrounded by letters on both sides. diff --git a/src/easydiffraction/analysis/collections/__init__.py b/src/easydiffraction/analysis/categories/__init__.py similarity index 100% rename from src/easydiffraction/analysis/collections/__init__.py rename to src/easydiffraction/analysis/categories/__init__.py diff --git a/src/easydiffraction/analysis/categories/aliases.py b/src/easydiffraction/analysis/categories/aliases.py new file mode 100644 index 00000000..5a7faaa6 --- /dev/null +++ b/src/easydiffraction/analysis/categories/aliases.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Alias category for mapping friendly names to parameter UIDs. + +Defines a small record type used by analysis configuration to refer to +parameters via readable labels instead of raw unique identifiers. +""" + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +class Alias(CategoryItem): + """Single alias entry. + + Maps a human-readable ``label`` to a concrete ``param_uid`` used by + the engine. + + Args: + label: Alias label. Must match ``^[A-Za-z_][A-Za-z0-9_]*$``. + param_uid: Target parameter uid. Same identifier pattern as + ``label``. + """ + + def __init__( + self, + *, + label: str, + param_uid: str, + ) -> None: + super().__init__() + + self._label: StringDescriptor = StringDescriptor( + name='label', + description='...', + value_spec=AttributeSpec( + value=label, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_alias.label', + ] + ), + ) + self._param_uid: StringDescriptor = StringDescriptor( + name='param_uid', + description='...', + value_spec=AttributeSpec( + value=param_uid, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_alias.param_uid', + ] + ), + ) + + self._identity.category_code = 'alias' + self._identity.category_entry_name = lambda: self.label.value + + @property + def label(self): + """Alias label descriptor.""" + return self._label + + @label.setter + def label(self, value): + """Set alias label. + + Args: + value: New label. + """ + self._label.value = value + + @property + def param_uid(self): + """Parameter uid descriptor the alias points to.""" + return self._param_uid + + @param_uid.setter + def param_uid(self, value): + """Set the parameter uid. + + Args: + value: New uid. + """ + self._param_uid.value = value + + +class Aliases(CategoryCollection): + """Collection of :class:`Alias` items.""" + + def __init__(self): + """Create an empty collection of aliases.""" + super().__init__(item_type=Alias) diff --git a/src/easydiffraction/analysis/categories/constraints.py b/src/easydiffraction/analysis/categories/constraints.py new file mode 100644 index 00000000..f99f93b1 --- /dev/null +++ b/src/easydiffraction/analysis/categories/constraints.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Simple symbolic constraint between parameters. + +Represents an equation of the form ``lhs_alias = rhs_expr`` where +``rhs_expr`` is evaluated elsewhere by the analysis engine. +""" + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +class Constraint(CategoryItem): + """Single constraint item. + + Args: + lhs_alias: Left-hand side alias name being constrained. + rhs_expr: Right-hand side expression as a string. + """ + + def __init__( + self, + *, + lhs_alias: str, + rhs_expr: str, + ) -> None: + super().__init__() + + self._lhs_alias: StringDescriptor = StringDescriptor( + name='lhs_alias', + description='...', + value_spec=AttributeSpec( + value=lhs_alias, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'.*'), + ), + cif_handler=CifHandler( + names=[ + '_constraint.lhs_alias', + ] + ), + ) + self._rhs_expr: StringDescriptor = StringDescriptor( + name='rhs_expr', + description='...', + value_spec=AttributeSpec( + value=rhs_expr, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'.*'), + ), + cif_handler=CifHandler( + names=[ + '_constraint.rhs_expr', + ] + ), + ) + + self._identity.category_code = 'constraint' + self._identity.category_entry_name = lambda: self.lhs_alias.value + + @property + def lhs_alias(self): + """Alias name on the left-hand side of the equation.""" + return self._lhs_alias + + @lhs_alias.setter + def lhs_alias(self, value): + """Set the left-hand side alias. + + Args: + value: New alias string. + """ + self._lhs_alias.value = value + + @property + def rhs_expr(self): + """Right-hand side expression string.""" + return self._rhs_expr + + @rhs_expr.setter + def rhs_expr(self, value): + """Set the right-hand side expression. + + Args: + value: New expression string. + """ + self._rhs_expr.value = value + + +class Constraints(CategoryCollection): + """Collection of :class:`Constraint` items.""" + + def __init__(self): + """Create an empty constraints collection.""" + super().__init__(item_type=Constraint) diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments.py b/src/easydiffraction/analysis/categories/joint_fit_experiments.py new file mode 100644 index 00000000..119e413d --- /dev/null +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Joint-fit experiment weighting configuration. + +Stores per-experiment weights to be used when multiple experiments are +fitted simultaneously. +""" + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +class JointFitExperiment(CategoryItem): + """A single joint-fit entry. + + Args: + id: Experiment identifier used in the fit session. + weight: Relative weight factor in the combined objective. + """ + + def __init__( + self, + *, + id: str, + weight: float, + ) -> None: + super().__init__() + + self._id: StringDescriptor = StringDescriptor( + name='id', # TODO: need new name instead of id + description='...', + value_spec=AttributeSpec( + value=id, + type_=DataTypes.STRING, + default='...', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_joint_fit_experiment.id', + ] + ), + ) + self._weight: NumericDescriptor = NumericDescriptor( + name='weight', + description='...', + value_spec=AttributeSpec( + value=weight, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_joint_fit_experiment.weight', + ] + ), + ) + + self._identity.category_code = 'joint_fit_experiment' + self._identity.category_entry_name = lambda: self.id.value + + @property + def id(self): + """Experiment identifier descriptor.""" + return self._id + + @id.setter + def id(self, value): + """Set the experiment identifier. + + Args: + value: New id string. + """ + self._id.value = value + + @property + def weight(self): + """Weight factor descriptor.""" + return self._weight + + @weight.setter + def weight(self, value): + """Set the weight factor. + + Args: + value: New weight value. + """ + self._weight.value = value + + +class JointFitExperiments(CategoryCollection): + """Collection of :class:`JointFitExperiment` items.""" + + def __init__(self): + """Create an empty joint-fit experiments collection.""" + super().__init__(item_type=JointFitExperiment) diff --git a/src/easydiffraction/analysis/collections/aliases.py b/src/easydiffraction/analysis/collections/aliases.py deleted file mode 100644 index 76820fde..00000000 --- a/src/easydiffraction/analysis/collections/aliases.py +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class Alias(Component): - @property - def category_key(self) -> str: - return 'alias' - - @property - def cif_category_key(self) -> str: - return 'alias' - - def __init__(self, label: str, param_uid: str) -> None: - super().__init__() - - self.label: Descriptor = Descriptor( - value=label, - name='label', - cif_name='label', - ) - self.param_uid: Descriptor = Descriptor( - value=param_uid, - name='param_uid', - cif_name='param_uid', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = label - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class Aliases(Collection): - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[Alias]: - return Alias diff --git a/src/easydiffraction/analysis/collections/constraints.py b/src/easydiffraction/analysis/collections/constraints.py deleted file mode 100644 index c7f032f5..00000000 --- a/src/easydiffraction/analysis/collections/constraints.py +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class Constraint(Component): - @property - def category_key(self) -> str: - return 'constraint' - - @property - def cif_category_key(self) -> str: - return 'constraint' - - def __init__(self, lhs_alias: str, rhs_expr: str) -> None: - super().__init__() - - self.lhs_alias: Descriptor = Descriptor( - value=lhs_alias, - name='lhs_alias', - cif_name='lhs_alias', - ) - self.rhs_expr: Descriptor = Descriptor( - value=rhs_expr, - name='rhs_expr', - cif_name='rhs_expr', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = lhs_alias - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class Constraints(Collection): - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[Constraint]: - return Constraint diff --git a/src/easydiffraction/analysis/collections/joint_fit_experiments.py b/src/easydiffraction/analysis/collections/joint_fit_experiments.py deleted file mode 100644 index bec45433..00000000 --- a/src/easydiffraction/analysis/collections/joint_fit_experiments.py +++ /dev/null @@ -1,54 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class JointFitExperiment(Component): - @property - def category_key(self) -> str: - return 'joint_fit_experiment' - - @property - def cif_category_key(self) -> str: - return 'joint_fit_experiment' - - def __init__(self, id: str, weight: float) -> None: - super().__init__() - - self.id: Descriptor = Descriptor( - value=id, - name='id', - cif_name='id', - ) - self.weight: Descriptor = Descriptor( - value=weight, - name='weight', - cif_name='weight', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = id - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class JointFitExperiments(Collection): - """Collection manager for experiments that are fitted together in a - `joint` fit. - """ - - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[JointFitExperiment]: - return JointFitExperiment diff --git a/src/easydiffraction/analysis/fitting/__init__.py b/src/easydiffraction/analysis/fit_helpers/__init__.py similarity index 100% rename from src/easydiffraction/analysis/fitting/__init__.py rename to src/easydiffraction/analysis/fit_helpers/__init__.py diff --git a/src/easydiffraction/analysis/fitting/metrics.py b/src/easydiffraction/analysis/fit_helpers/metrics.py similarity index 97% rename from src/easydiffraction/analysis/fitting/metrics.py rename to src/easydiffraction/analysis/fit_helpers/metrics.py index 2ed11b5b..42a372e0 100644 --- a/src/easydiffraction/analysis/fitting/metrics.py +++ b/src/easydiffraction/analysis/fit_helpers/metrics.py @@ -6,7 +6,7 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase +from easydiffraction.analysis.calculators.base import CalculatorBase from easydiffraction.experiments.experiments import Experiments from easydiffraction.sample_models.sample_models import SampleModels @@ -141,7 +141,7 @@ def get_reliability_inputs( y_obs_all = [] y_calc_all = [] y_err_all = [] - for experiment in experiments._items.values(): + for experiment in experiments.values(): calculator.calculate_pattern(sample_models, experiment) y_calc = experiment.datastore.calc y_meas = experiment.datastore.meas diff --git a/src/easydiffraction/analysis/fitting/results.py b/src/easydiffraction/analysis/fit_helpers/reporting.py similarity index 63% rename from src/easydiffraction/analysis/fitting/results.py rename to src/easydiffraction/analysis/fit_helpers/reporting.py index 1ad0ae27..76e8c9f6 100644 --- a/src/easydiffraction/analysis/fitting/results.py +++ b/src/easydiffraction/analysis/fit_helpers/reporting.py @@ -6,14 +6,21 @@ from typing import Optional from easydiffraction import paragraph -from easydiffraction.analysis.fitting.metrics import calculate_r_factor -from easydiffraction.analysis.fitting.metrics import calculate_r_factor_squared -from easydiffraction.analysis.fitting.metrics import calculate_rb_factor -from easydiffraction.analysis.fitting.metrics import calculate_weighted_r_factor +from easydiffraction.analysis.fit_helpers.metrics import calculate_r_factor +from easydiffraction.analysis.fit_helpers.metrics import calculate_r_factor_squared +from easydiffraction.analysis.fit_helpers.metrics import calculate_rb_factor +from easydiffraction.analysis.fit_helpers.metrics import calculate_weighted_r_factor from easydiffraction.utils.utils import render_table class FitResults: + """Container for results of a single optimization run. + + Holds success flag, chi-square metrics, iteration counts, timing, + and parameter objects. Provides a printer to summarize key + indicators and a table of fitted parameters. + """ + def __init__( self, success: bool = False, @@ -27,6 +34,22 @@ def __init__( fitting_time: Optional[float] = None, **kwargs: Any, ) -> None: + """Initialize FitResults with the given parameters. + + Args: + success: Indicates if the fit was successful. + parameters: List of parameters used in the fit. + chi_square: Chi-square value of the fit. + reduced_chi_square: Reduced chi-square value of the fit. + message: Message related to the fit. + iterations: Number of iterations performed. + engine_result: Result from the fitting engine. + starting_parameters: Initial parameters for the fit. + fitting_time: Time taken for the fitting process. + **kwargs: Additional engine-specific fields. If ``redchi`` + is provided and ``reduced_chi_square`` is not set, it is + used as the reduced chi-square value. + """ self.success: bool = success self.parameters: List[Any] = parameters if parameters is not None else [] self.chi_square: Optional[float] = chi_square @@ -54,6 +77,15 @@ def display_results( f_obs: Optional[List[float]] = None, f_calc: Optional[List[float]] = None, ) -> None: + """Render a human-readable summary of the fit. + + Args: + y_obs: Observed intensities for pattern R-factor metrics. + y_calc: Calculated intensities for pattern R-factor metrics. + y_err: Standard deviations of observed intensities for wR. + f_obs: Observed structure-factor magnitudes for Bragg R. + f_calc: Calculated structure-factor magnitudes for Bragg R. + """ status_icon = '✅' if self.success else '❌' rf = rf2 = wr = br = None if y_obs is not None and y_calc is not None: @@ -103,30 +135,34 @@ def display_results( rows = [] for param in self.parameters: - datablock_id = getattr(param, 'datablock_id', 'N/A') - category_key = getattr(param, 'category_key', 'N/A') - collection_entry_id = getattr(param, 'collection_entry_id', 'N/A') + datablock_entry_name = ( + param._identity.datablock_entry_name + ) # getattr(param, 'datablock_name', 'N/A') + category_code = param._identity.category_code # getattr(param, 'category_key', 'N/A') + category_entry_name = ( + param._identity.category_entry_name + ) # getattr(param, 'category_entry_name', 'N/A') name = getattr(param, 'name', 'N/A') start = ( - f'{getattr(param, "start_value", "N/A"):.4f}' - if param.start_value is not None + f'{getattr(param, "_fit_start_value", "N/A"):.4f}' + if param._fit_start_value is not None else 'N/A' ) fitted = f'{param.value:.4f}' if param.value is not None else 'N/A' uncertainty = f'{param.uncertainty:.4f}' if param.uncertainty is not None else 'N/A' units = getattr(param, 'units', 'N/A') - if param.start_value and param.value: - change = ((param.value - param.start_value) / param.start_value) * 100 + if param._fit_start_value and param.value: + change = ((param.value - param._fit_start_value) / param._fit_start_value) * 100 arrow = '↑' if change > 0 else '↓' relative_change = f'{abs(change):.2f} % {arrow}' else: relative_change = 'N/A' rows.append([ - datablock_id, - category_key, - collection_entry_id, + datablock_entry_name, + category_code, + category_entry_name, name, start, fitted, diff --git a/src/easydiffraction/analysis/fitting/progress_tracker.py b/src/easydiffraction/analysis/fit_helpers/tracking.py similarity index 82% rename from src/easydiffraction/analysis/fitting/progress_tracker.py rename to src/easydiffraction/analysis/fit_helpers/tracking.py index a1b5a3f7..94629592 100644 --- a/src/easydiffraction/analysis/fitting/progress_tracker.py +++ b/src/easydiffraction/analysis/fit_helpers/tracking.py @@ -15,7 +15,7 @@ display = None clear_output = None -from easydiffraction.analysis.fitting.metrics import calculate_reduced_chi_square +from easydiffraction.analysis.fit_helpers.metrics import calculate_reduced_chi_square from easydiffraction.utils.utils import is_notebook from easydiffraction.utils.utils import render_table @@ -41,9 +41,12 @@ def format_cell( return cell_str -class FittingProgressTracker: - """Tracks and reports the reduced chi-square during the optimization - process. +class FitProgressTracker: + """Track and report reduced chi-square during optimization. + + The tracker keeps iteration counters, remembers the best observed + reduced chi-square and when it occurred, and can display progress as + a table in notebooks or a text UI in terminals. """ def __init__(self) -> None: @@ -59,6 +62,7 @@ def __init__(self) -> None: self._display_handle: Optional[DisplayHandle] = None def reset(self) -> None: + """Reset internal state before a new optimization run.""" self._iteration = 0 self._previous_chi2 = None self._last_chi2 = None @@ -72,15 +76,14 @@ def track( residuals: np.ndarray, parameters: List[float], ) -> np.ndarray: - """Track chi-square progress during the optimization process. + """Update progress with current residuals and parameters. - Parameters: - residuals (np.ndarray): Array of residuals between measured - and calculated data. - parameters (list): List of free parameters being fitted. + Args: + residuals: Residuals between measured and calculated data. + parameters: Current free parameters being fitted. Returns: - np.ndarray: Residuals unchanged, for optimizer consumption. + Residuals unchanged, for optimizer consumption. """ self._iteration += 1 @@ -133,28 +136,39 @@ def track( @property def best_chi2(self) -> Optional[float]: + """Best recorded reduced chi-square value or None.""" return self._best_chi2 @property def best_iteration(self) -> Optional[int]: + """Iteration index at which the best chi-square was observed.""" return self._best_iteration @property def iteration(self) -> int: + """Current iteration counter.""" return self._iteration @property def fitting_time(self) -> Optional[float]: + """Elapsed time of the last run in seconds, if available.""" return self._fitting_time def start_timer(self) -> None: + """Begin timing of a fit run.""" self._start_time = time.perf_counter() def stop_timer(self) -> None: + """Stop timing and store elapsed time for the run.""" self._end_time = time.perf_counter() self._fitting_time = self._end_time - self._start_time def start_tracking(self, minimizer_name: str) -> None: + """Initialize display and headers and announce the minimizer. + + Args: + minimizer_name: Name of the minimizer used for the run. + """ print(f"🚀 Starting fit process with '{minimizer_name}'...") print('📈 Goodness-of-fit (reduced χ²) change:') @@ -189,6 +203,11 @@ def start_tracking(self, minimizer_name: str) -> None: print('╞' + '╪'.join(['═' * FIXED_WIDTH for _ in DEFAULT_HEADERS]) + '╡') def add_tracking_info(self, row: List[str]) -> None: + """Append a formatted row to the progress display. + + Args: + row: Columns corresponding to DEFAULT_HEADERS. + """ if is_notebook() and display is not None: # Add row to DataFrame self._df_rows.append(row) @@ -214,6 +233,7 @@ def add_tracking_info(self, row: List[str]) -> None: print(formatted_row) def finish_tracking(self) -> None: + """Finalize progress display and print best result summary.""" # Add last iteration as last row row: List[str] = [ str(self._last_iteration), diff --git a/src/easydiffraction/analysis/minimization.py b/src/easydiffraction/analysis/fitting.py similarity index 87% rename from src/easydiffraction/analysis/minimization.py rename to src/easydiffraction/analysis/fitting.py index 3fc04cbf..f447a17f 100644 --- a/src/easydiffraction/analysis/minimization.py +++ b/src/easydiffraction/analysis/fitting.py @@ -9,18 +9,18 @@ import numpy as np -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase -from easydiffraction.analysis.fitting.metrics import get_reliability_inputs -from easydiffraction.analysis.minimizers.minimizer_factory import MinimizerFactory -from easydiffraction.core.objects import Parameter +from easydiffraction.analysis.calculators.base import CalculatorBase +from easydiffraction.analysis.fit_helpers.metrics import get_reliability_inputs +from easydiffraction.analysis.minimizers.factory import MinimizerFactory +from easydiffraction.core.parameters import Parameter from easydiffraction.experiments.experiments import Experiments from easydiffraction.sample_models.sample_models import SampleModels if TYPE_CHECKING: - from easydiffraction.analysis.fitting.results import FitResults + from easydiffraction.analysis.fit_helpers.reporting import FitResults -class DiffractionMinimizer: +class Fitter: """Handles the fitting workflow using a pluggable minimizer.""" def __init__(self, selection: str = 'lmfit (leastsq)') -> None: @@ -44,14 +44,14 @@ def fit( calculator: The calculator to use for pattern generation. weights: Optional weights for joint fitting. """ - params = sample_models.get_free_params() + experiments.get_free_params() + params = sample_models.free_parameters + experiments.free_parameters if not params: print('⚠️ No parameters selected for fitting.') return None for param in params: - param.start_value = param.value + param._fit_start_value = param.value def objective_function(engine_params: Dict[str, Any]) -> np.ndarray: return self._residual_function( @@ -114,9 +114,7 @@ def _collect_free_parameters( Returns: List of free parameters. """ - free_params: List[Parameter] = ( - sample_models.get_free_params() + experiments.get_free_params() - ) + free_params: List[Parameter] = sample_models.free_parameters + experiments.free_parameters return free_params def _residual_function( @@ -147,13 +145,13 @@ def _residual_function( self.minimizer._sync_result_to_parameters(parameters, engine_params) # Prepare weights for joint fitting - num_expts: int = len(experiments.ids) + num_expts: int = len(experiments.names) if weights is None: _weights = np.ones(num_expts) else: _weights_list: List[float] = [] - for id in experiments.ids: - _weight = weights._items[id].weight.value + for name in experiments.names: + _weight = weights[name].weight.value _weights_list.append(_weight) _weights = np.array(_weights_list, dtype=np.float64) @@ -165,7 +163,7 @@ def _residual_function( _weights *= num_expts / np.sum(_weights) residuals: List[float] = [] - for experiment, weight in zip(experiments._items.values(), _weights, strict=True): + for experiment, weight in zip(experiments.values(), _weights, strict=True): # Calculate the difference between measured and calculated # patterns calculator.calculate_pattern( diff --git a/src/easydiffraction/analysis/minimizers/minimizer_base.py b/src/easydiffraction/analysis/minimizers/base.py similarity index 66% rename from src/easydiffraction/analysis/minimizers/minimizer_base.py rename to src/easydiffraction/analysis/minimizers/base.py index 162c33ae..2f966d5c 100644 --- a/src/easydiffraction/analysis/minimizers/minimizer_base.py +++ b/src/easydiffraction/analysis/minimizers/base.py @@ -11,14 +11,19 @@ import numpy as np -from easydiffraction.analysis.fitting.progress_tracker import FittingProgressTracker -from easydiffraction.analysis.fitting.results import FitResults +from easydiffraction.analysis.fit_helpers.reporting import FitResults +from easydiffraction.analysis.fit_helpers.tracking import FitProgressTracker class MinimizerBase(ABC): - """Abstract base class for minimizer implementations. - - Provides shared logic and structure for concrete minimizers. + """Abstract base for concrete minimizers. + + Contract: + - Subclasses must implement ``_prepare_solver_args``, + ``_run_solver``, ``_sync_result_to_parameters`` and + ``_check_success``. + - The ``fit`` method orchestrates the full workflow and returns + :class:`FitResults`. """ def __init__( @@ -36,21 +41,32 @@ def __init__( self._best_chi2: Optional[float] = None self._best_iteration: Optional[int] = None self._fitting_time: Optional[float] = None - self.tracker: FittingProgressTracker = FittingProgressTracker() + self.tracker: FitProgressTracker = FitProgressTracker() def _start_tracking(self, minimizer_name: str) -> None: + """Initialize progress tracking and timer. + + Args: + minimizer_name: Human-readable name shown in progress. + """ self.tracker.reset() self.tracker.start_tracking(minimizer_name) self.tracker.start_timer() def _stop_tracking(self) -> None: + """Stop timer and finalize tracking.""" self.tracker.stop_timer() self.tracker.finish_tracking() @abstractmethod def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: - """Prepare the solver arguments directly from the list of free - parameters. + """Prepare keyword-arguments for the underlying solver. + + Args: + parameters: List of free parameters to be fitted. + + Returns: + Mapping of keyword arguments to pass into ``_run_solver``. """ pass @@ -60,6 +76,7 @@ def _run_solver( objective_function: Callable[..., Any], engine_parameters: Dict[str, Any], ) -> Any: + """Execute the concrete solver and return its raw result.""" pass @abstractmethod @@ -68,6 +85,9 @@ def _sync_result_to_parameters( raw_result: Any, parameters: List[Any], ) -> None: + """Copy values from ``raw_result`` back to ``parameters`` in- + place. + """ pass def _finalize_fit( @@ -75,6 +95,15 @@ def _finalize_fit( parameters: List[Any], raw_result: Any, ) -> FitResults: + """Build :class:`FitResults` and store it on ``self.result``. + + Args: + parameters: Parameters after the solver finished. + raw_result: Backend-specific solver output object. + + Returns: + FitResults: Aggregated outcome of the fit. + """ self._sync_result_to_parameters(parameters, raw_result) success = self._check_success(raw_result) self.result = FitResults( @@ -89,10 +118,7 @@ def _finalize_fit( @abstractmethod def _check_success(self, raw_result: Any) -> bool: - """Determine whether the fit was successful. - - This must be implemented by concrete minimizers. - """ + """Determine whether the fit was successful.""" pass def fit( @@ -100,6 +126,16 @@ def fit( parameters: List[Any], objective_function: Callable[..., Any], ) -> FitResults: + """Run the full minimization workflow. + + Args: + parameters: Free parameters to optimize. + objective_function: Callable returning residuals for a given + set of engine arguments. + + Returns: + FitResults with success flag, best chi2 and timing. + """ minimizer_name = self.name or 'Unnamed Minimizer' if self.method is not None: minimizer_name += f' ({self.method})' @@ -123,6 +159,7 @@ def _objective_function( experiments: Any, calculator: Any, ) -> np.ndarray: + """Default objective helper computing residuals array.""" return self._compute_residuals( engine_params, parameters, @@ -138,6 +175,7 @@ def _create_objective_function( experiments: Any, calculator: Any, ) -> Callable[[Dict[str, Any]], np.ndarray]: + """Return a closure capturing problem context for the solver.""" return lambda engine_params: self._objective_function( engine_params, parameters, diff --git a/src/easydiffraction/analysis/minimizers/minimizer_dfols.py b/src/easydiffraction/analysis/minimizers/dfols.py similarity index 91% rename from src/easydiffraction/analysis/minimizers/minimizer_dfols.py rename to src/easydiffraction/analysis/minimizers/dfols.py index 6ba2999d..c3b70740 100644 --- a/src/easydiffraction/analysis/minimizers/minimizer_dfols.py +++ b/src/easydiffraction/analysis/minimizers/dfols.py @@ -8,7 +8,7 @@ import numpy as np from dfols import solve -from easydiffraction.analysis.minimizers.minimizer_base import MinimizerBase +from easydiffraction.analysis.minimizers.base import MinimizerBase DEFAULT_MAX_ITERATIONS = 1000 @@ -34,8 +34,8 @@ def _prepare_solver_args(self, parameters: List[Any]) -> Dict[str, Any]: bounds_upper = [] for param in parameters: x0.append(param.value) - bounds_lower.append(param.min if param.min is not None else -np.inf) - bounds_upper.append(param.max if param.max is not None else np.inf) + bounds_lower.append(param.fit_min) + bounds_upper.append(param.fit_max) bounds = (np.array(bounds_lower), np.array(bounds_upper)) return {'x0': np.array(x0), 'bounds': bounds} diff --git a/src/easydiffraction/analysis/minimizers/minimizer_factory.py b/src/easydiffraction/analysis/minimizers/factory.py similarity index 94% rename from src/easydiffraction/analysis/minimizers/minimizer_factory.py rename to src/easydiffraction/analysis/minimizers/factory.py index 0f05ec7a..3d5d6aba 100644 --- a/src/easydiffraction/analysis/minimizers/minimizer_factory.py +++ b/src/easydiffraction/analysis/minimizers/factory.py @@ -7,9 +7,9 @@ from typing import Optional from typing import Type -from easydiffraction.analysis.minimizers.minimizer_base import MinimizerBase -from easydiffraction.analysis.minimizers.minimizer_dfols import DfolsMinimizer -from easydiffraction.analysis.minimizers.minimizer_lmfit import LmfitMinimizer +from easydiffraction.analysis.minimizers.base import MinimizerBase +from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer +from easydiffraction.analysis.minimizers.lmfit import LmfitMinimizer from easydiffraction.utils.formatting import paragraph from easydiffraction.utils.utils import render_table diff --git a/src/easydiffraction/analysis/minimizers/minimizer_lmfit.py b/src/easydiffraction/analysis/minimizers/lmfit.py similarity index 91% rename from src/easydiffraction/analysis/minimizers/minimizer_lmfit.py rename to src/easydiffraction/analysis/minimizers/lmfit.py index b61c18c2..acbe2f2f 100644 --- a/src/easydiffraction/analysis/minimizers/minimizer_lmfit.py +++ b/src/easydiffraction/analysis/minimizers/lmfit.py @@ -7,7 +7,7 @@ import lmfit -from easydiffraction.analysis.minimizers.minimizer_base import MinimizerBase +from easydiffraction.analysis.minimizers.base import MinimizerBase DEFAULT_METHOD = 'leastsq' DEFAULT_MAX_ITERATIONS = 1000 @@ -44,11 +44,11 @@ def _prepare_solver_args( engine_parameters = lmfit.Parameters() for param in parameters: engine_parameters.add( - name=param.minimizer_uid, + name=param._minimizer_uid, value=param.value, vary=param.free, - min=param.min, - max=param.max, + min=param.fit_min, + max=param.fit_max, ) return {'engine_parameters': engine_parameters} @@ -86,9 +86,9 @@ def _sync_result_to_parameters( param_values = raw_result.params if hasattr(raw_result, 'params') else raw_result for param in parameters: - param_result = param_values.get(param.minimizer_uid) + param_result = param_values.get(param._minimizer_uid) if param_result is not None: - param.value = param_result.value + param._value = param_result.value # Bypass ranges check param.uncertainty = getattr(param_result, 'stderr', None) def _check_success(self, raw_result: Any) -> bool: diff --git a/src/easydiffraction/core/category.py b/src/easydiffraction/core/category.py new file mode 100644 index 00000000..2c055170 --- /dev/null +++ b/src/easydiffraction/core/category.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from easydiffraction.core.collection import CollectionBase +from easydiffraction.core.guard import GuardedBase +from easydiffraction.core.parameters import GenericDescriptorBase +from easydiffraction.core.validation import checktype +from easydiffraction.io.cif.serialize import category_collection_to_cif +from easydiffraction.io.cif.serialize import category_item_to_cif + + +class CategoryItem(GuardedBase): + """Base class for items in a category collection.""" + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + params = ', '.join(f'{p.name}={p.value!r}' for p in self.parameters) + return f'<{name} ({params})>' + + @property + def unique_name(self): + parts = [ + self._identity.datablock_entry_name, + self._identity.category_code, + self._identity.category_entry_name, + ] + return '.'.join(filter(None, parts)) + + @property + def parameters(self): + return [v for v in vars(self).values() if isinstance(v, GenericDescriptorBase)] + + @property + def as_cif(self) -> str: + """Return CIF representation of this object.""" + return category_item_to_cif(self) + + +class CategoryCollection(CollectionBase): + """Handles loop-style category containers (e.g. AtomSites). + + Each item is a CategoryItem (component). + """ + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + size = len(self) + return f'<{name} collection ({size} items)>' + + @property + def unique_name(self): + return None + + @property + def parameters(self): + """All parameters from all items in this collection.""" + params = [] + for item in self._items: + params.extend(item.parameters) + return params + + @property + def as_cif(self) -> str: + """Return CIF representation of this object.""" + return category_collection_to_cif(self) + + @checktype + def add(self, item) -> None: + """Add an item to the collection.""" + self[item._identity.category_entry_name] = item + + @checktype + def add_from_args(self, *args, **kwargs) -> None: + """Create and add a new child instance from the provided + arguments. + """ + child_obj = self._item_type(*args, **kwargs) + self.add(child_obj) diff --git a/src/easydiffraction/core/collection.py b/src/easydiffraction/core/collection.py new file mode 100644 index 00000000..e572827d --- /dev/null +++ b/src/easydiffraction/core/collection.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Lightweight container for guarded items with name-based indexing. + +`CollectionBase` maintains an ordered list of items and a lazily rebuilt +index by the item's identity key. It supports dict-like access for get, +set and delete, along with iteration over the items. +""" + +from __future__ import annotations + +from easydiffraction.core.guard import GuardedBase + + +class CollectionBase(GuardedBase): + """A minimal collection with stable iteration and name indexing. + + Args: + item_type: Type of items accepted by the collection. Used for + validation and tooling; not enforced at runtime here. + """ + + def __init__(self, item_type) -> None: + super().__init__() + self._items: list = [] + self._index: dict = {} + self._item_type = item_type + + def __getitem__(self, name: str): + """Return an item by its identity key. + + Rebuilds the internal index on a cache miss to stay consistent + with recent mutations. + """ + try: + return self._index[name] + except KeyError: + self._rebuild_index() + return self._index[name] + + def __setitem__(self, name: str, item) -> None: + """Insert or replace an item under the given identity key.""" + # Check if item with same identity exists; if so, replace it + for i, existing_item in enumerate(self._items): + if existing_item._identity.category_entry_name == name: + self._items[i] = item + self._rebuild_index() + return + # Otherwise append new item + item._parent = self # Explicitly set the parent for the item + self._items.append(item) + self._rebuild_index() + + def __delitem__(self, name: str) -> None: + """Delete an item by key or raise ``KeyError`` if missing.""" + # Remove from _items by identity entry name + for i, item in enumerate(self._items): + if item._identity.category_entry_name == name: + object.__setattr__(item, '_parent', None) # Unlink the parent before removal + del self._items[i] + self._rebuild_index() + return + raise KeyError(name) + + def __iter__(self): + """Iterate over items in insertion order.""" + return iter(self._items) + + def __len__(self) -> int: + """Return the number of items in the collection.""" + return len(self._items) + + def _key_for(self, item): + """Return the identity key for ``item`` (category or + datablock). + """ + return item._identity.category_entry_name or item._identity.datablock_entry_name + + def _rebuild_index(self) -> None: + """Rebuild the name-to-item index from the ordered item list.""" + self._index.clear() + for item in self._items: + key = self._key_for(item) + if key: + self._index[key] = item + + def keys(self): + """Yield keys for all items in insertion order.""" + return (self._key_for(item) for item in self._items) + + def values(self): + """Yield items in insertion order.""" + return (item for item in self._items) + + def items(self): + """Yield ``(key, item)`` pairs in insertion order.""" + return ((self._key_for(item), item) for item in self._items) + + @property + def names(self): + """List of all item keys in the collection.""" + return list(self.keys()) diff --git a/src/easydiffraction/core/datablock.py b/src/easydiffraction/core/datablock.py new file mode 100644 index 00000000..bd6f5d9f --- /dev/null +++ b/src/easydiffraction/core/datablock.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typeguard import typechecked + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.collection import CollectionBase +from easydiffraction.core.guard import GuardedBase +from easydiffraction.core.parameters import Parameter +from easydiffraction.io.cif.serialize import datablock_collection_to_cif +from easydiffraction.io.cif.serialize import datablock_item_to_cif + + +class DatablockItem(GuardedBase): + """Base class for items in a datablock collection.""" + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + items = getattr(self, '_items', None) + return f'<{name} ({items})>' + + @property + def unique_name(self): + return self._identity.datablock_entry_name + + @property + def parameters(self): + """All parameters from all categories contained in this + datablock. + """ + params = [] + for v in vars(self).values(): + if isinstance(v, (CategoryItem, CategoryCollection)): + params.extend(v.parameters) + return params + + @property + def as_cif(self) -> str: + """Return CIF representation of this object.""" + return datablock_item_to_cif(self) + + +class DatablockCollection(CollectionBase): + """Handles top-level category collections (e.g. SampleModels, + Experiments). + + Each item is a DatablockItem. + """ + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + size = len(self) + return f'<{name} collection ({size} items)>' + + @property + def unique_name(self): + return None + + @property + def parameters(self): + """All parameters from all datablocks in this collection.""" + params = [] + for db in self._items: + params.extend(db.parameters) + return params + + # was in class AbstractDatablock(ABC): + @property + def fittable_parameters(self) -> list: + return [p for p in self.parameters if isinstance(p, Parameter) and not p.constrained] + + # was in class AbstractDatablock(ABC): + @property + def free_parameters(self) -> list: + return [p for p in self.fittable_parameters if p.free] + + @property + def as_cif(self) -> str: + """Return CIF representation of this object.""" + return datablock_collection_to_cif(self) + + @typechecked + def add(self, item) -> None: + """Add an item to the collection.""" + self[item._identity.datablock_entry_name] = item diff --git a/src/easydiffraction/core/diagnostic.py b/src/easydiffraction/core/diagnostic.py new file mode 100644 index 00000000..38da87a1 --- /dev/null +++ b/src/easydiffraction/core/diagnostic.py @@ -0,0 +1,217 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Diagnostics helpers for logging validation messages. + +This module centralizes human-friendly error and debug logs for +attribute validation and configuration checks. +""" + +import difflib + +from easydiffraction import log + + +class Diagnostics: + """Centralized logger for attribute errors and validation hints.""" + + # ============================================================== + # Configuration / definition diagnostics + # ============================================================== + + @staticmethod + def type_override_error(cls_name: str, expected, got): + """Report an invalid DataTypes override. + + Used when descriptor and AttributeSpec types conflict. + """ + expected_label = str(expected) + got_label = str(got) + msg = ( + f'Invalid type override in <{cls_name}>. ' + f'Descriptor enforces `{expected_label}`, ' + f'but AttributeSpec defines `{got_label}`.' + ) + Diagnostics._log_error(msg, exc_type=TypeError) + + # ============================================================== + # Attribute diagnostics + # ============================================================== + + @staticmethod + def readonly_error( + name: str, + key: str | None = None, + ): + """Log an attempt to change a read-only attribute.""" + Diagnostics._log_error( + f"Cannot modify read-only attribute '{key}' of <{name}>.", + exc_type=AttributeError, + ) + + @staticmethod + def attr_error( + name: str, + key: str, + allowed: set[str], + label='Allowed', + ): + """Log access to an unknown attribute and suggest closest + key. + """ + suggestion = Diagnostics._build_suggestion(key, allowed) + # Use consistent (label) logic for allowed + hint = suggestion or Diagnostics._build_allowed(allowed, label=label) + Diagnostics._log_error( + f"Unknown attribute '{key}' of <{name}>.{hint}", + exc_type=AttributeError, + ) + + # ============================================================== + # Validation diagnostics + # ============================================================== + + @staticmethod + def type_mismatch( + name: str, + value, + expected_type, + current=None, + default=None, + ): + """Log a type mismatch and keep current or default value.""" + got_type = type(value).__name__ + msg = ( + f'Type mismatch for <{name}>. ' + f'Expected `{expected_type}`, got `{got_type}` ({value!r}).' + ) + Diagnostics._log_error_with_fallback( + msg, current=current, default=default, exc_type=TypeError + ) + + @staticmethod + def range_mismatch( + name: str, + value, + ge, + le, + current=None, + default=None, + ): + """Log range violation for a numeric value.""" + msg = f'Value mismatch for <{name}>. Provided {value!r} outside [{ge}, {le}].' + Diagnostics._log_error_with_fallback( + msg, current=current, default=default, exc_type=TypeError + ) + + @staticmethod + def choice_mismatch( + name: str, + value, + allowed, + current=None, + default=None, + ): + """Log an invalid choice against allowed values.""" + msg = f'Value mismatch for <{name}>. Provided {value!r} is unknown.' + if allowed is not None: + msg += Diagnostics._build_allowed(allowed, label='Allowed values') + Diagnostics._log_error_with_fallback( + msg, current=current, default=default, exc_type=TypeError + ) + + @staticmethod + def regex_mismatch( + name: str, + value, + pattern, + current=None, + default=None, + ): + """Log a regex mismatch with the expected pattern.""" + msg = ( + f"Value mismatch for <{name}>. Provided {value!r} does not match pattern '{pattern}'." + ) + Diagnostics._log_error_with_fallback( + msg, current=current, default=default, exc_type=TypeError + ) + + @staticmethod + def no_value(name, default): + """Log that default will be used due to missing value.""" + Diagnostics._log_debug(f'No value provided for <{name}>. Using default {default!r}.') + + @staticmethod + def none_value(name): + """Log explicit None provided by a user.""" + Diagnostics._log_debug(f'Using `None` explicitly provided for <{name}>.') + + @staticmethod + def none_value_skip_range(name): + """Log that range validation is skipped due to None.""" + Diagnostics._log_debug( + f'Skipping range validation as `None` is explicitly provided for <{name}>.' + ) + + @staticmethod + def validated(name, value, stage: str | None = None): + """Log that a value passed a validation stage.""" + stage_info = f' {stage}' if stage else '' + Diagnostics._log_debug(f'Value {value!r} for <{name}> passed{stage_info} validation.') + + # ============================================================== + # Helper log methods + # ============================================================== + + @staticmethod + def _log_error(msg, exc_type=Exception): + """Emit an error-level message via shared logger.""" + log.error(message=msg, exc_type=exc_type) + + @staticmethod + def _log_error_with_fallback( + msg, + current=None, + default=None, + exc_type=Exception, + ): + """Emit an error message and mention kept or default value.""" + if current is not None: + msg += f' Keeping current {current!r}.' + else: + msg += f' Using default {default!r}.' + log.error(message=msg, exc_type=exc_type) + + @staticmethod + def _log_debug(msg): + """Emit a debug-level message via shared logger.""" + log.debug(message=msg) + + # ============================================================== + # Suggestion and allowed value helpers + # ============================================================== + + @staticmethod + def _suggest(key: str, allowed: set[str]): + """Suggest closest allowed key using string similarity.""" + if not allowed: + return None + # Return the allowed key with smallest Levenshtein distance + matches = difflib.get_close_matches(key, allowed, n=1) + return matches[0] if matches else None + + @staticmethod + def _build_suggestion(key: str, allowed: set[str]): + s = Diagnostics._suggest(key, allowed) + return f" Did you mean '{s}'?" if s else '' + + @staticmethod + def _build_allowed(allowed, label='Allowed attributes'): + # allowed may be a set, list, or other iterable + if allowed: + allowed_list = list(allowed) + if len(allowed_list) <= 10: + s = ', '.join(map(repr, sorted(allowed_list))) + return f' {label}: {s}.' + else: + return f' ({len(allowed_list)} {label.lower()} not listed here).' + return '' diff --git a/src/easydiffraction/core/factory.py b/src/easydiffraction/core/factory.py new file mode 100644 index 00000000..cc0ecd79 --- /dev/null +++ b/src/easydiffraction/core/factory.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from typing import Iterable +from typing import Mapping + + +class FactoryBase: + """Reusable argument validation mixin.""" + + @staticmethod + def _validate_args( + present: set[str], + allowed_specs: Iterable[Mapping[str, Iterable[str]]], + factory_name: str, + ) -> None: + """Validate provided arguments against allowed combinations.""" + for spec in allowed_specs: + required = set(spec.get('required', [])) + optional = set(spec.get('optional', [])) + if required.issubset(present) and present <= (required | optional): + return # valid combo + # build readable error message + combos = [] + for spec in allowed_specs: + req = ', '.join(spec.get('required', [])) + opt = ', '.join(spec.get('optional', [])) + if opt: + combos.append(f'({req}[, {opt}])') + else: + combos.append(f'({req})') + raise ValueError( + f'Invalid argument combination for {factory_name} creation.\n' + f'Provided: {sorted(present)}\n' + f'Allowed combinations:\n ' + '\n '.join(combos) + ) diff --git a/src/easydiffraction/core/guard.py b/src/easydiffraction/core/guard.py new file mode 100644 index 00000000..f5d81ec4 --- /dev/null +++ b/src/easydiffraction/core/guard.py @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from abc import ABC +from abc import abstractmethod + +from easydiffraction.core.diagnostic import Diagnostics +from easydiffraction.core.identity import Identity + + +class GuardedBase(ABC): + """Base class enforcing controlled attribute access and parent + linkage. + """ + + # 5b: Use class-level diagnoser + _diagnoser = Diagnostics() + + def __init__(self): + self._identity = Identity(owner=self) + + def __str__(self) -> str: + return f'<{self.unique_name}>' + + def __repr__(self) -> str: + return self.__str__() + + def __getattr__(self, key: str): + cls = type(self) + allowed = cls._public_attrs() + if key not in allowed: + type(self)._diagnoser.attr_error( + self.unique_name, + key, + allowed, + label='Allowed readable/writable', + ) + + def __setattr__(self, key: str, value): + # Always allow private or special attributes without diagnostics + if key.startswith('_'): + object.__setattr__(self, key, value) + # Also maintain parent linkage for nested objects + if key != '_parent' and isinstance(value, GuardedBase): + object.__setattr__(value, '_parent', self) + return + + # Handle public attributes with diagnostics + cls = type(self) + # Prevent modification of read-only attributes + if key in cls._public_readonly_attrs(): + cls._diagnoser.readonly_error( + self.unique_name, + key, + ) + return + # Prevent assignment to unknown attributes + # Show writable attributes only as allowed + if key not in cls._public_attrs(): + allowed = cls._public_writable_attrs() + cls._diagnoser.attr_error( + self.unique_name, + key, + allowed, + label='Allowed writable', + ) + return + + self._assign_attr(key, value) + + def _assign_attr(self, key, value): + """Low-level assignment with parent linkage.""" + object.__setattr__(self, key, value) + if key != '_parent' and isinstance(value, GuardedBase): + object.__setattr__(value, '_parent', self) + + @classmethod + def _iter_properties(cls): + """Iterate over all public properties defined in the class + hierarchy. + + Yields: + tuple[str, property]: Each (key, property) pair for public + attributes. + """ + for base in cls.mro(): + for key, attr in base.__dict__.items(): + if key.startswith('_') or not isinstance(attr, property): + continue + yield key, attr + + @classmethod + def _public_attrs(cls): + """All public properties (read-only + writable).""" + return {key for key, _ in cls._iter_properties()} + + @classmethod + def _public_readonly_attrs(cls): + """Public properties without a setter.""" + return {key for key, prop in cls._iter_properties() if prop.fset is None} + + @classmethod + def _public_writable_attrs(cls) -> set[str]: + """Public properties with a setter.""" + return {key for key, prop in cls._iter_properties() if prop.fset is not None} + + def _allowed_attrs(self, writable_only=False): + cls = type(self) + if writable_only: + return cls._public_writable_attrs() + return cls._public_attrs() + + @property + def _log_name(self): + return type(self).__name__ + + @property + def unique_name(self): + return type(self).__name__ + + # @property + # def identity(self): + # """Expose a limited read-only view of identity attributes.""" + # return SimpleNamespace( + # datablock_entry_name=self._identity.datablock_entry_name, + # category_code=self._identity.category_code, + # category_entry_name=self._identity.category_entry_name, + # ) + + @property + @abstractmethod + def parameters(self): + """Return a list of parameter objects (to be implemented by + subclasses). + """ + raise NotImplementedError + + @property + @abstractmethod + def as_cif(self) -> str: + """Return CIF representation of this object (to be implemented + by subclasses). + """ + raise NotImplementedError diff --git a/src/easydiffraction/core/identity.py b/src/easydiffraction/core/identity.py new file mode 100644 index 00000000..90c74231 --- /dev/null +++ b/src/easydiffraction/core/identity.py @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Identity helpers to build CIF-like hierarchical names. + +Used by containers and items to expose datablock/category/entry names +without tight coupling. +""" + +from typing import Callable + + +class Identity: + """Resolve datablock/category/entry relationships lazily.""" + + def __init__( + self, + *, + owner: object, + datablock_entry: Callable | None = None, + category_code: str | None = None, + category_entry: Callable | None = None, + ): + self._owner = owner + self._datablock_entry = datablock_entry + self._category_code = category_code + self._category_entry = category_entry + + def _resolve_up(self, attr: str, visited=None): + """Resolve attribute by walking up parent chain safely.""" + if visited is None: + visited = set() + if id(self) in visited: + return None + visited.add(id(self)) + + # Direct callable or value on self + value = getattr(self, f'_{attr}', None) + if callable(value): + return value() + if isinstance(value, str): + return value + + # Climb to parent if available + parent = getattr(self._owner, '__dict__', {}).get('_parent') + if parent and hasattr(parent, '_identity'): + return parent._identity._resolve_up(attr, visited) + return None + + @property + def datablock_entry_name(self): + """Datablock entry name or None if not set.""" + return self._resolve_up('datablock_entry') + + @datablock_entry_name.setter + def datablock_entry_name(self, func: callable): + """Set callable returning datablock entry name.""" + self._datablock_entry = func + + @property + def category_code(self): + """Category code like 'atom_site' or 'background'.""" + return self._resolve_up('category_code') + + @category_code.setter + def category_code(self, value: str): + """Set category code value.""" + self._category_code = value + + @property + def category_entry_name(self): + """Category entry name or None if not set.""" + return self._resolve_up('category_entry') + + @category_entry_name.setter + def category_entry_name(self, func: callable): + """Set callable returning category entry name.""" + self._category_entry = func diff --git a/src/easydiffraction/core/objects.py b/src/easydiffraction/core/objects.py deleted file mode 100644 index edf5e675..00000000 --- a/src/easydiffraction/core/objects.py +++ /dev/null @@ -1,538 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -import secrets -import string -from abc import ABC -from abc import abstractmethod -from typing import Any -from typing import Dict -from typing import Iterator -from typing import List -from typing import Optional -from typing import TypeVar -from typing import Union - -from easydiffraction.core.singletons import UidMapHandler -from easydiffraction.utils.decorators import enforce_type -from easydiffraction.utils.formatting import error -from easydiffraction.utils.formatting import warning - -T = TypeVar('T') - - -class Descriptor: - """Base class for descriptors (non-refinable attributes).""" - - def __init__( - self, - # Value of the parameter - value: Any, - # ED parameter name (to access it in the code) - name: str, - # CIF parameter name (to show it in the CIF) - cif_name: str, - # Pretty name (to show it in the table) - pretty_name: Optional[str] = None, - # Parent datablock name - datablock_id: Optional[str] = None, - # ED parent category name - category_key: Optional[str] = None, - # CIF parent category name - cif_category_key: Optional[str] = None, - # Parent collection entry id - collection_entry_id: Optional[str] = None, - # Units of the parameter - units: Optional[str] = None, - # Description of the parameter - description: Optional[str] = None, - # If false, the parameter can never be edited. It is calculated - # automatically - editable: bool = True, - ) -> None: - self._value = value - self.name: str = name - self.cif_name: str = cif_name - self.pretty_name: Optional[str] = pretty_name - self._datablock_id: Optional[str] = datablock_id - self.category_key: Optional[str] = category_key - self.cif_category_key: Optional[str] = cif_category_key - self._collection_entry_id: Optional[str] = collection_entry_id - self.units: Optional[str] = units - self._description: Optional[str] = description - self._editable: bool = editable - - self._human_uid = self._generate_human_readable_unique_id() - - UidMapHandler.get().add_to_uid_map(self) - - def __str__(self): - # Base value string - value_str = f'{self.__class__.__name__}: {self.uid} = {self.value}' - - # Append ± uncertainty if it exists and is nonzero - if hasattr(self, 'uncertainty') and self.uncertainty != 0.0: - value_str += f' ± {self.uncertainty}' - - # Append units if available - if self.units: - value_str += f' {self.units}' - - return value_str - - def __repr__(self): - return self.__str__() - - def _generate_random_unique_id(self) -> str: - # Derived class Parameter will use this unique id for the - # minimization process to identify the parameter. It will also - # be used to create the alias for the parameter in the - # constraint expression. - length = 16 - letters = [secrets.choice(string.ascii_lowercase) for _ in range(length)] - uid = ''.join(letters) - return uid - - def _generate_human_readable_unique_id(self): - # Instead of generating a random string, we can use the - # name of the parameter and the block name to create a unique - # id. - # E.g.: - # - "block-id.category-name.parameter-name": - # "lbco.cell.length_a" - # - "block-id.category-name.entry-id.parameter-name": - # "lbco.atom_site.Ba.fract_x" - # For the analysis, we can use the same format, but without the - # datablock id. E.g.: - # - "category-name.entry-id.parameter-name": - # "alias.occ_Ba.label" - # This need to be called after the parameter is created and all - # its attributes are set. - if self.datablock_id: - uid = f'{self.datablock_id}.{self.cif_category_key}' - else: - uid = f'{self.cif_category_key}' - if self.collection_entry_id: - uid += f'.{self.collection_entry_id}' - uid += f'.{self.cif_name}' - return uid - - @property - def datablock_id(self): - return self._datablock_id - - @datablock_id.setter - def datablock_id(self, new_id): - self._datablock_id = new_id - # Update the unique id, when datablock_id attribute is of - # the parameter is changed - self.uid = self._generate_human_readable_unique_id() - - @property - def collection_entry_id(self): - return self._collection_entry_id - - @collection_entry_id.setter - def collection_entry_id(self, new_id): - self._collection_entry_id = new_id - # Update the unique id, when datablock_id attribute is of - # the parameter is changed - self.uid = self._generate_human_readable_unique_id() - - @property - def uid(self): - return self._human_uid - - @uid.setter - def uid(self, new_uid): - # Update the unique id in the global uid map - old_uid = self._human_uid - self._human_uid = new_uid - UidMapHandler.get().replace_uid(old_uid, new_uid) - - @property - def minimizer_uid(self): - return self.uid.replace('.', '__') - - @property - def value(self) -> Any: - return self._value - - @value.setter - def value(self, new_value: Any) -> None: - if self._editable: - self._value = new_value - else: - print( - warning( - f"The parameter '{self.cif_name}' it is calculated " - f'automatically and cannot be changed manually.' - ) - ) - - @property - def description(self) -> Optional[str]: - return self._description - - @property - def editable(self) -> bool: - return self._editable - - -class Parameter(Descriptor): - """A parameter with a value, uncertainty, units, and CIF - representation. - """ - - def __init__( - self, - value: Any, - name: str, - cif_name: str, - pretty_name: Optional[str] = None, - datablock_id: Optional[str] = None, # Parent datablock name - category_key: Optional[str] = None, - cif_category_key: Optional[str] = None, - collection_entry_id: Optional[str] = None, - units: Optional[str] = None, - description: Optional[str] = None, - editable: bool = True, - uncertainty: float = 0.0, - free: bool = False, - constrained: bool = False, - min_value: Optional[float] = None, - max_value: Optional[float] = None, - ) -> None: - super().__init__( - value, - name, - cif_name, - pretty_name, - datablock_id, - category_key, - cif_category_key, - collection_entry_id, - units, - description, - editable, - ) - self.uncertainty: float = ( - uncertainty # Standard uncertainty or estimated standard deviation - ) - self.free: bool = free # If the parameter is free to be fitted during the optimization - self.constrained: bool = ( - constrained # If symmetry constrains the parameter during the optimization - ) - self.min: Optional[float] = min_value # Minimum physical value of the parameter - self.max: Optional[float] = max_value # Maximum physical value of the parameter - self.start_value: Optional[Any] = None # Starting value for optimization - - -class Component(ABC): - """Base class for standard components, like Cell, Peak, etc.""" - - @property - @abstractmethod - def category_key(self): - """Must be implemented in subclasses to return the ED category - name. - - Can differ from cif_category_key. - """ - pass - - @property - @abstractmethod - def cif_category_key(self): - """Must be implemented in subclasses to return the CIF category - name. - """ - pass - - def __init__(self): - self._locked = False # If adding new attributes is locked - - self._datablock_id = None # Parent datablock name to be set by the parent - self._entry_id = None # Parent collection entry id to be set by the parent - - # TODO: Currently, it is not used. Planned to be used for - # displaying the parameters in the specific order. - self._ordered_attrs: List[str] = [] - - def __getattr__(self, name: str) -> Any: - """If the attribute is a Parameter or Descriptor, return its - value by default. - """ - attr = self.__dict__.get(name, None) - if isinstance(attr, (Descriptor, Parameter)): - return attr.value - raise AttributeError(f'{name} not found in {self}') - - def __setattr__(self, name: str, value: Any) -> None: - """If an object is locked for adding new attributes, raise an - error. - - If the attribute 'name' does not exist, add it. If the attribute - 'name' exists and is a Parameter or Descriptor, set its value. - """ - if hasattr(self, '_locked') and self._locked and not hasattr(self, name): - print(error(f"Cannot add new parameter '{name}'")) - return - - # Try to get the attribute from the instance's dictionary - attr = self.__dict__.get(name, None) - - # If the attribute is not set, and it is a Parameter or - # Descriptor, set its category_key and cif_category_key to the - # current category_key and cif_category_key and add it to the - # component. Also add its name to the list of ordered attributes - if attr is None: - if isinstance(value, (Descriptor, Parameter)): - value.category_key = self.category_key - value.cif_category_key = self.cif_category_key - self._ordered_attrs.append(name) - super().__setattr__(name, value) - # If the attribute is already set and is a Parameter or - # Descriptor, update its value. Else, allow normal reassignment - else: - if isinstance(attr, (Descriptor, Parameter)): - attr.value = value - else: - super().__setattr__(name, value) - - @property - def datablock_id(self): - return self._datablock_id - - @datablock_id.setter - def datablock_id(self, new_id): - self._datablock_id = new_id - # For each parameter in this component, also update its - # datablock_id - for param in self.get_all_params(): - param.datablock_id = new_id - - @property - def entry_id(self): - return self._entry_id - - @entry_id.setter - def entry_id(self, new_id): - self._entry_id = new_id - # For each parameter in the component, set the entry_id - for param in self.get_all_params(): - param.collection_entry_id = new_id - - def get_all_params(self): - attr_objs = [] - for attr_name in dir(self): - attr_obj = getattr(self, attr_name) - if isinstance(attr_obj, (Descriptor, Parameter)): - attr_objs.append(attr_obj) - return attr_objs - - def as_dict(self) -> Dict[str, Any]: - d = {} - - for attr_name in dir(self): - if attr_name.startswith('_'): - continue - - attr_obj = getattr(self, attr_name) - if not isinstance(attr_obj, (Descriptor, Parameter)): - continue - - key = attr_obj.cif_name - value = attr_obj.value - d[key] = value - - return d - - def as_cif(self) -> str: - if not self.cif_category_key: - raise ValueError('cif_category_key must be defined in the derived class.') - - lines = [] - - for attr_name in dir(self): - if attr_name.startswith('_'): - continue - - attr_obj = getattr(self, attr_name) - if not isinstance(attr_obj, (Descriptor, Parameter)): - continue - - key = f'_{self.cif_category_key}.{attr_obj.cif_name}' - value = attr_obj.value - - if value is None: - continue - - if isinstance(value, str) and ' ' in value: - value = f'"{value}"' - - line = f'{key} {value}' - lines.append(line) - - return '\n'.join(lines) - - -class Collection(ABC): - """Base class for collections like AtomSites, LinkedPhases, - SampleModels, Experiments, etc. - """ - - @property - @abstractmethod - def _child_class(self): - return None - - def __init__(self, parent=None): - self._parent = parent # Parent datablock - self._datablock_id = None # Parent datablock name to be set by the parent - self._items = {} - - def __getitem__(self, key: str) -> Union[Component, 'Collection']: - return self._items[key] - - def __iter__(self) -> Iterator[Union[Component, 'Collection']]: - return iter(self._items.values()) - - @property - def datablock_id(self): - return self._datablock_id - - @datablock_id.setter - def datablock_id(self, new_id): - self._datablock_id = new_id - for param in self.get_all_params(): - param.datablock_id = new_id - - def add(self, *args, **kwargs): - """Add a new item to the collection. - - The item must be a subclass of Component. - """ - if self._child_class is None: - raise ValueError('Child class is not defined.') - child_obj = self._child_class(*args, **kwargs) - # Setting the datablock_id to update its child parameters - child_obj.datablock_id = self.datablock_id - # Forcing the entry_id to be reset to update its child - # parameters - child_obj.entry_id = child_obj.entry_id - self._items[child_obj._entry_id] = child_obj - - # Call on_item_added if it exists, i.e. defined in the derived - # class - if hasattr(self, 'on_item_added'): - self.on_item_added(child_obj) - - def get_all_params(self): - params = [] - for item in self._items.values(): - if isinstance(item, Datablock): - datablock = item - for datablock_item in datablock.items(): - if isinstance(datablock_item, Component): - component = datablock_item - for param in component.get_all_params(): - params.append(param) - elif isinstance(datablock_item, Collection): - collection = datablock_item - for component in collection: - for param in component.get_all_params(): - params.append(param) - elif isinstance(item, Component): - component = item - for param in component.get_all_params(): - params.append(param) - else: - raise TypeError(f'Expected a Component or Datablock, got {type(item)}') - return params - - def get_fittable_params(self) -> List[Parameter]: - all_params = self.get_all_params() - params = [] - for param in all_params: - if hasattr(param, 'free') and not param.constrained: - params.append(param) - return params - - def get_free_params(self) -> List[Parameter]: - fittable_params = self.get_fittable_params() - params = [] - for param in fittable_params: - if param.free: - params.append(param) - return params - - def as_cif(self) -> str: - lines = [] - if self._type == 'category': - for idx, item in enumerate(self._items.values()): - params = item.as_dict() - category_key = item.cif_category_key - # Keys - keys = [f'_{category_key}.{param_key}' for param_key in params] - # Values. If the value is a string and contains spaces, - # add quotes - values = [] - for value in params.values(): - value = f'{value}' - if ' ' in value: - value = f'"{value}"' - values.append(value) - # Header is added only for the first item - if idx == 0: - lines.append('loop_') - header = '\n'.join(keys) - lines.append(header) - line = ' '.join(values) - lines.append(line) - return '\n'.join(lines) - - -class Datablock: - """Base class for Sample Model and Experiment data blocks.""" - - # TODO: Consider unifying with class Component? - - def __init__(self): - self._name = None - - def __setattr__(self, name, value): - # TODO: compare with class Component - # If the value is a Component or Collection: - # - set its datablock_id to the current datablock name - # - add it to the datablock - if isinstance(value, (Component, Collection)): - value.datablock_id = self._name - super().__setattr__(name, value) - - def items(self): - """Returns a list of both components and collections in the data - block. - """ - attr_objs = [] - for attr_name in dir(self): - if attr_name.startswith('_'): - continue - attr_obj = getattr(self, attr_name) - if isinstance(attr_obj, (Component, Collection)): - attr_objs.append(attr_obj) - return attr_objs - - @property - def name(self): - return self._name - - @name.setter - @enforce_type - def name(self, new_name: str): - self._name = new_name - # For each component/collection in this datablock, - # also update its datablock_id if it has one - for item in getattr(self, '__dict__', {}).values(): - if isinstance(item, (Component, Collection)): - item.datablock_id = new_name diff --git a/src/easydiffraction/core/parameters.py b/src/easydiffraction/core/parameters.py new file mode 100644 index 00000000..5a1c3177 --- /dev/null +++ b/src/easydiffraction/core/parameters.py @@ -0,0 +1,365 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import secrets +import string +from typing import TYPE_CHECKING +from typing import Any + +import numpy as np + +from easydiffraction.core.diagnostic import Diagnostics +from easydiffraction.core.guard import GuardedBase +from easydiffraction.core.singletons import UidMapHandler +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.validation import TypeValidator +from easydiffraction.io.cif.serialize import param_to_cif + +if TYPE_CHECKING: + from easydiffraction.io.cif.handler import CifHandler + + +class GenericDescriptorBase(GuardedBase): + """Base class for all parameter-like descriptors. + + A descriptor encapsulates a typed value with validation, + human-readable name/description and a globally unique identifier + that is stable across the session. Concrete subclasses specialize + the expected data type and can extend the public API with + additional behavior (e.g. units). + + Attributes: + name: Local parameter name (e.g. 'a', 'b_iso'). + description: Optional human-readable description. + uid: Stable random identifier for external references. + """ + + _BOOL_SPEC_TEMPLATE = AttributeSpec( + type_=DataTypes.BOOL, + default=False, + ) + + def __init__( + self, + *, + value_spec: AttributeSpec, + name: str, + description: str = None, + ): + """Initialize the descriptor with validation and identity. + + Args: + value_spec: Validation specification for the value. + name: Local name of the descriptor within its category. + description: Optional human-readable description. + """ + super().__init__() + + expected_type = getattr(self, '_value_type', None) + + if expected_type: + user_type = ( + value_spec._type_validator.expected_type + if value_spec._type_validator is not None + else None + ) + if user_type and user_type is not expected_type: + Diagnostics.type_override_error( + type(self).__name__, + expected_type, + user_type, + ) + else: + # Enforce descriptor's own type if not already defined + value_spec._type_validator = TypeValidator(expected_type) + + self._value_spec = value_spec + self._name = name + self._description = description + self._uid: str = self._generate_uid() + UidMapHandler.get().add_to_uid_map(self) + + # Initial validated states + self._value = self._value_spec.validated( + value_spec.value, + name=self.unique_name, + ) + + def __str__(self) -> str: + return f'<{self.unique_name} = {self.value!r}>' + + @staticmethod + def _generate_uid(length: int = 16) -> str: + letters = string.ascii_lowercase + return ''.join(secrets.choice(letters) for _ in range(length)) + + @property + def uid(self): + """Stable random identifier for this descriptor.""" + return self._uid + + @property + def name(self) -> str: + """Local name of the descriptor (without category/datablock).""" + return self._name + + @property + def unique_name(self): + """Fully qualified name including datablock, category and entry + name. + """ + # 7c: Use filter(None, [...]) + parts = [ + self._identity.datablock_entry_name, + self._identity.category_code, + self._identity.category_entry_name, + self.name, + ] + return '.'.join(filter(None, parts)) + + @property + def value(self): + """Current validated value.""" + return self._value + + @value.setter + def value(self, v): + """Set a new value after validating against the spec.""" + self._value = self._value_spec.validated( + v, + name=self.unique_name, + current=self._value, + ) + + @property + def description(self): + """Optional human-readable description.""" + return self._description + + @property + def parameters(self): + """Return a flat list of parameters contained by this object. + + For a single descriptor, it returns a one-element list with + itself. Composite objects override this to flatten nested + structures. + """ + return [self] + + @property + def as_cif(self) -> str: + """Serialize this descriptor to a CIF-formatted string.""" + return param_to_cif(self) + + +class GenericStringDescriptor(GenericDescriptorBase): + _value_type = DataTypes.STRING + + def __init__( + self, + **kwargs: Any, + ) -> None: + super().__init__(**kwargs) + + +class GenericNumericDescriptor(GenericDescriptorBase): + _value_type = DataTypes.NUMERIC + + def __init__( + self, + *, + units: str = '', + **kwargs: Any, + ) -> None: + super().__init__(**kwargs) + self._units: str = units + + def __str__(self) -> str: + s: str = super().__str__() + s = s[1:-1] # strip <> + if self.units: + s += f' {self.units}' + return f'<{s}>' + + @property + def units(self) -> str: + """Units associated with the numeric value, if any.""" + return self._units + + +class GenericParameter(GenericNumericDescriptor): + """Numeric descriptor extended with fitting-related attributes. + + Adds standard attributes used by minimizers: "free" flag, + uncertainty, bounds and an optional starting value. Subclasses can + integrate with specific backends while preserving this interface. + """ + + def __init__( + self, + **kwargs: Any, + ): + super().__init__(**kwargs) + + # Initial validated states + self._free_spec = self._BOOL_SPEC_TEMPLATE + self._free = self._free_spec.default + self._uncertainty_spec = AttributeSpec( + type_=DataTypes.NUMERIC, + content_validator=RangeValidator(ge=0), + allow_none=True, + ) + self._uncertainty = self._uncertainty_spec.default + self._fit_min_spec = AttributeSpec(type_=DataTypes.NUMERIC, default=-np.inf) + self._fit_min = self._fit_min_spec.default + self._fit_max_spec = AttributeSpec(type_=DataTypes.NUMERIC, default=np.inf) + self._fit_max = self._fit_max_spec.default + self._start_value_spec = AttributeSpec(type_=DataTypes.NUMERIC, default=0.0) + self._start_value = self._start_value_spec.default + self._constrained_spec = self._BOOL_SPEC_TEMPLATE + self._constrained = self._constrained_spec.default + + def __str__(self) -> str: + s = GenericDescriptorBase.__str__(self) + s = s[1:-1] # strip <> + if self.uncertainty is not None: + s += f' ± {self.uncertainty}' + if self.units is not None: + s += f' {self.units}' + s += f' (free={self.free})' + return f'<{s}>' + + @property + def _minimizer_uid(self): + """Variant of uid that is safe for minimizer engines.""" + # return self.unique_name.replace('.', '__') + return self.uid + + @property + def name(self) -> str: + """Local name of the parameter (without category/datablock).""" + return self._name + + @property + def unique_name(self): + """Fully qualified parameter name including its context path.""" + parts = [ + self._identity.datablock_entry_name, + self._identity.category_code, + self._identity.category_entry_name, + self.name, + ] + return '.'.join(filter(None, parts)) + + @property + def constrained(self): + """Whether this parameter is part of a constraint expression.""" + return self._constrained + + @property + def free(self): + """Whether this parameter is currently varied during fitting.""" + return self._free + + @free.setter + def free(self, v): + """Set the "free" flag after validation.""" + self._free = self._free_spec.validated( + v, name=f'{self.unique_name}.free', current=self._free + ) + + @property + def uncertainty(self): + """Estimated standard uncertainty of the fitted value, if + available. + """ + return self._uncertainty + + @uncertainty.setter + def uncertainty(self, v): + """Set the uncertainty value (must be non-negative or None).""" + self._uncertainty = self._uncertainty_spec.validated( + v, name=f'{self.unique_name}.uncertainty', current=self._uncertainty + ) + + @property + def fit_min(self): + """Lower fitting bound.""" + return self._fit_min + + @fit_min.setter + def fit_min(self, v): + """Set the lower bound for the parameter value.""" + self._fit_min = self._fit_min_spec.validated( + v, name=f'{self.unique_name}.fit_min', current=self._fit_min + ) + + @property + def fit_max(self): + """Upper fitting bound.""" + return self._fit_max + + @fit_max.setter + def fit_max(self, v): + """Set the upper bound for the parameter value.""" + self._fit_max = self._fit_max_spec.validated( + v, name=f'{self.unique_name}.fit_max', current=self._fit_max + ) + + +class StringDescriptor(GenericStringDescriptor): + def __init__( + self, + *, + cif_handler: CifHandler, + **kwargs: Any, + ) -> None: + """String descriptor bound to a CIF handler. + + Args: + cif_handler: Object that tracks CIF identifiers. + **kwargs: Forwarded to GenericStringDescriptor. + """ + super().__init__(**kwargs) + self._cif_handler = cif_handler + self._cif_handler.attach(self) + + +class NumericDescriptor(GenericNumericDescriptor): + def __init__( + self, + *, + cif_handler: CifHandler, + **kwargs: Any, + ) -> None: + """Numeric descriptor bound to a CIF handler. + + Args: + cif_handler: Object that tracks CIF identifiers. + **kwargs: Forwarded to GenericNumericDescriptor. + """ + super().__init__(**kwargs) + self._cif_handler = cif_handler + self._cif_handler.attach(self) + + +class Parameter(GenericParameter): + def __init__( + self, + *, + cif_handler: CifHandler, + **kwargs: Any, + ) -> None: + """Fittable parameter bound to a CIF handler. + + Args: + cif_handler: Object that tracks CIF identifiers. + **kwargs: Forwarded to GenericParameter. + """ + super().__init__(**kwargs) + self._cif_handler = cif_handler + self._cif_handler.attach(self) diff --git a/src/easydiffraction/core/singletons.py b/src/easydiffraction/core/singletons.py index 4d03b78b..b699422e 100644 --- a/src/easydiffraction/core/singletons.py +++ b/src/easydiffraction/core/singletons.py @@ -10,10 +10,10 @@ from asteval import Interpreter -T = TypeVar('T', bound='BaseSingleton') +T = TypeVar('T', bound='SingletonBase') -class BaseSingleton: +class SingletonBase: """Base class to implement Singleton pattern. Ensures only one shared instance of a class is ever created. Useful @@ -30,7 +30,7 @@ def get(cls: Type[T]) -> T: return cls._instance -class UidMapHandler(BaseSingleton): +class UidMapHandler(SingletonBase): """Global handler to manage UID-to-Parameter object mapping.""" def __init__(self) -> None: @@ -42,7 +42,18 @@ def get_uid_map(self) -> Dict[str, Any]: return self._uid_map def add_to_uid_map(self, parameter): - """Adds a single Parameter object to the UID map.""" + """Adds a single Parameter or Descriptor object to the UID map. + + Only Descriptor or Parameter instances are allowed (not + Components or others). + """ + from easydiffraction.core.parameters import GenericDescriptorBase + + if not isinstance(parameter, GenericDescriptorBase): + raise TypeError( + f'Cannot add object of type {type(parameter).__name__} to UID map. ' + 'Only Descriptor or Parameter instances are allowed.' + ) self._uid_map[parameter.uid] = parameter def replace_uid(self, old_uid, new_uid): @@ -51,17 +62,18 @@ def replace_uid(self, old_uid, new_uid): Moves the associated parameter from old_uid to new_uid. Raises a KeyError if the old_uid doesn't exist. """ - if old_uid in self._uid_map: - self._uid_map[new_uid] = self._uid_map.pop(old_uid) - else: + if old_uid not in self._uid_map: + # Only raise if old_uid is not None and not empty + print('DEBUG: replace_uid failed', old_uid, 'current map:', list(self._uid_map.keys())) raise KeyError(f"UID '{old_uid}' not found in the UID map.") + self._uid_map[new_uid] = self._uid_map.pop(old_uid) # TODO: Implement removing from the UID map # TODO: Implement changing atrr '.constrained' back to False # when removing constraints -class ConstraintsHandler(BaseSingleton): +class ConstraintsHandler(SingletonBase): """Manages user-defined parameter constraints using aliases and expressions. @@ -88,7 +100,7 @@ def set_aliases(self, aliases): Called when user registers parameter aliases like: alias='biso_La', param=model.atom_sites['La'].b_iso """ - self._alias_to_param = aliases._items + self._alias_to_param = dict(aliases.items()) def set_constraints(self, constraints): """Sets the constraints and triggers parsing into internal @@ -107,7 +119,7 @@ def _parse_constraints(self) -> None: """ self._parsed_constraints = [] - for expr_obj in self._constraints.values(): + for expr_obj in self._constraints: lhs_alias = expr_obj.lhs_alias.value rhs_expr = expr_obj.rhs_expr.value @@ -152,8 +164,8 @@ def apply(self) -> None: param = uid_map[dependent_uid] # Update its value and mark it as constrained - param.value = rhs_value - param.constrained = True + param._value = rhs_value # To bypass ranges check + param._constrained = True # To bypass read-only check except Exception as error: print(f"Failed to apply constraint '{lhs_alias} = {rhs_expr}': {error}") diff --git a/src/easydiffraction/core/validation.py b/src/easydiffraction/core/validation.py new file mode 100644 index 00000000..6bb88eae --- /dev/null +++ b/src/easydiffraction/core/validation.py @@ -0,0 +1,345 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Lightweight runtime validation utilities. + +Provides DataTypes, type/content validators, and AttributeSpec used by +descriptors and parameters. Only documentation was added here. +""" + +import functools +import re +from abc import ABC +from abc import abstractmethod +from enum import Enum +from enum import auto + +import numpy as np +from typeguard import TypeCheckError +from typeguard import typechecked + +from easydiffraction.core.diagnostic import Diagnostics +from easydiffraction.utils.logging import log + +# ============================================================== +# Shared constants +# ============================================================== + + +class DataTypes(Enum): + NUMERIC = (int, float, np.integer, np.floating, np.number) + STRING = (str,) + BOOL = (bool,) + ANY = (object,) # fallback for unconstrained + + def __str__(self): + return self.name.lower() + + @property + def expected_type(self): + """Convenience alias for tuple of allowed Python types.""" + return self.value + + +# ============================================================== +# Runtime type checking decorator +# ============================================================== + +# Runtime type checking decorator for validating those methods +# annotated with type hints, which are writable for the user, and +# which are not covered by custom validators for Parameter attribute +# types and content, implemented below. + + +def checktype(func=None, *, context=None): + """Runtime type check decorator using typeguard. + + When a TypeCheckError occurs, the error is logged and None is + returned. If context is provided, it is added to the message. + """ + + def decorator(f): + checked_func = typechecked(f) + + @functools.wraps(f) + def wrapper(*args, **kwargs): + try: + return checked_func(*args, **kwargs) + except TypeCheckError as err: + msg = str(err) + if context: + msg = f'{context}: {msg}' + log.error(message=msg, exc_type=TypeError) + return None + + return wrapper + + if func is None: + return decorator + return decorator(func) + + +# ============================================================== +# Validation stages (enum/constant) +# ============================================================== + + +class ValidationStage(Enum): + """Phases of validation for diagnostic logging.""" + + TYPE = auto() + RANGE = auto() + MEMBERSHIP = auto() + REGEX = auto() + + def __str__(self): + return self.name.lower() + + +# ============================================================== +# Advanced runtime custom validators for Parameter types/content +# ============================================================== + + +class ValidatorBase(ABC): + """Abstract base class for all validators.""" + + @abstractmethod + def validated(self, value, name, default=None, current=None): + """Return a validated value or fallback. + + Subclasses must implement this method. + """ + raise NotImplementedError + + def _fallback( + self, + current=None, + default=None, + ): + """Return current if set, else default.""" + return current if current is not None else default + + +class TypeValidator(ValidatorBase): + """Ensure a value is of the expected Python type.""" + + def __init__(self, expected_type: DataTypes): + if isinstance(expected_type, DataTypes): + self.expected_type = expected_type + self.expected_label = str(expected_type) + else: + raise TypeError(f'TypeValidator expected a DataTypes member, got {expected_type!r}') + + def validated( + self, + value, + name, + default=None, + current=None, + allow_none=False, + ): + """Validate type and return value or fallback. + + If allow_none is True, None bypasses content checks. + """ + # Fresh initialization, use default + if current is None and value is None: + Diagnostics.no_value(name, default) + return default + + # Explicit None (allowed) + if value is None and allow_none: + Diagnostics.none_value(name) + return None + + # Normal type validation + if not isinstance(value, self.expected_type.value): + Diagnostics.type_mismatch( + name, + value, + expected_type=self.expected_label, + current=current, + default=default, + ) + return self._fallback(current, default) + + Diagnostics.validated( + name, + value, + stage=ValidationStage.TYPE, + ) + return value + + +class RangeValidator(ValidatorBase): + """Ensure a numeric value lies within [ge, le].""" + + def __init__( + self, + *, + ge=-np.inf, + le=np.inf, + ): + self.ge, self.le = ge, le + + def validated( + self, + value, + name, + default=None, + current=None, + ): + """Validate range and return value or fallback.""" + if not (self.ge <= value <= self.le): + Diagnostics.range_mismatch( + name, + value, + self.ge, + self.le, + current=current, + default=default, + ) + return self._fallback(current, default) + + Diagnostics.validated( + name, + value, + stage=ValidationStage.RANGE, + ) + return value + + +class MembershipValidator(ValidatorBase): + """Ensure that a value is among allowed choices. + + `allowed` may be an iterable or a callable returning a collection. + """ + + def __init__(self, allowed): + # Do not convert immediately to list — may be callable + self.allowed = allowed + + def validated( + self, + value, + name, + default=None, + current=None, + ): + """Validate membership and return value or fallback.""" + # Dynamically evaluate allowed if callable (e.g. lambda) + allowed_values = self.allowed() if callable(self.allowed) else self.allowed + + if value not in allowed_values: + Diagnostics.choice_mismatch( + name, + value, + allowed_values, + current=current, + default=default, + ) + return self._fallback(current, default) + + Diagnostics.validated( + name, + value, + stage=ValidationStage.MEMBERSHIP, + ) + return value + + +class RegexValidator(ValidatorBase): + """Ensure that a string matches a given regular expression.""" + + def __init__(self, pattern): + self.pattern = re.compile(pattern) + + def validated( + self, + value, + name, + default=None, + current=None, + ): + """Validate regex and return value or fallback.""" + if not self.pattern.fullmatch(value): + Diagnostics.regex_mismatch( + name, + value, + self.pattern.pattern, + current=current, + default=default, + ) + return self._fallback(current, default) + + Diagnostics.validated( + name, + value, + stage=ValidationStage.REGEX, + ) + return value + + +# ============================================================== +# Attribute specification holding metadata and validators +# ============================================================== + + +class AttributeSpec: + """Hold metadata and validators for a single attribute.""" + + def __init__( + self, + *, + value=None, + type_=None, + default=None, + content_validator=None, + allow_none: bool = False, + ): + self.value = value + self.default = default + self.allow_none = allow_none + self._type_validator = TypeValidator(type_) if type_ else None + self._content_validator = content_validator + + def validated( + self, + value, + name, + current=None, + ): + """Validate through type and content validators. + + Returns validated value, possibly default or current if errors + occur. None may short-circuit further checks when allowed. + """ + val = value + # Evaluate callable defaults dynamically + default = self.default() if callable(self.default) else self.default + + # Type validation + if self._type_validator: + val = self._type_validator.validated( + val, + name, + default=default, + current=current, + allow_none=self.allow_none, + ) + + # Skip further validation: Special case for None + if val is None and self.allow_none: + Diagnostics.none_value_skip_range(name) + return None + + # Content validation + if self._content_validator and val is not None: + val = self._content_validator.validated( + val, + name, + default=default, + current=current, + ) + + return val diff --git a/src/easydiffraction/crystallography/crystallography.py b/src/easydiffraction/crystallography/crystallography.py index 66cdc446..c5a7fc9f 100644 --- a/src/easydiffraction/crystallography/crystallography.py +++ b/src/easydiffraction/crystallography/crystallography.py @@ -46,32 +46,32 @@ def apply_cell_symmetry_constraints( a = cell['lattice_a'] cell['lattice_b'] = a cell['lattice_c'] = a - cell['angle_alpha'] = 90 - cell['angle_beta'] = 90 - cell['angle_gamma'] = 90 + cell['angle_alpha'] = 90.0 + cell['angle_beta'] = 90.0 + cell['angle_gamma'] = 90.0 elif crystal_system == 'tetragonal': a = cell['lattice_a'] cell['lattice_b'] = a - cell['angle_alpha'] = 90 - cell['angle_beta'] = 90 - cell['angle_gamma'] = 90 + cell['angle_alpha'] = 90.0 + cell['angle_beta'] = 90.0 + cell['angle_gamma'] = 90.0 elif crystal_system == 'orthorhombic': - cell['angle_alpha'] = 90 - cell['angle_beta'] = 90 - cell['angle_gamma'] = 90 + cell['angle_alpha'] = 90.0 + cell['angle_beta'] = 90.0 + cell['angle_gamma'] = 90.0 elif crystal_system in {'hexagonal', 'trigonal'}: a = cell['lattice_a'] cell['lattice_b'] = a - cell['angle_alpha'] = 90 - cell['angle_beta'] = 90 - cell['angle_gamma'] = 120 + cell['angle_alpha'] = 90.0 + cell['angle_beta'] = 90.0 + cell['angle_gamma'] = 120.0 elif crystal_system == 'monoclinic': - cell['angle_alpha'] = 90 - cell['angle_gamma'] = 90 + cell['angle_alpha'] = 90.0 + cell['angle_gamma'] = 90.0 elif crystal_system == 'triclinic': pass # No constraints to apply diff --git a/src/easydiffraction/crystallography/space_groups.py b/src/easydiffraction/crystallography/space_groups.py index d647a347..0ffa5c09 100644 --- a/src/easydiffraction/crystallography/space_groups.py +++ b/src/easydiffraction/crystallography/space_groups.py @@ -1,3 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Space group reference data. + +Loads a gzipped, packaged pickle with crystallographic space-group +information. The file is part of the distribution; user input is not +involved. +""" + import gzip import pickle # noqa: S403 - trusted internal pickle file (package data only) from pathlib import Path @@ -15,6 +24,7 @@ def _restricted_pickle_load(file_obj) -> Any: def _load(): + """Load space-group data from the packaged archive.""" path = Path(__file__).with_name('space_groups.pkl.gz') with gzip.open(path, 'rb') as f: return _restricted_pickle_load(f) diff --git a/src/easydiffraction/experiments/collections/__init__.py b/src/easydiffraction/experiments/categories/__init__.py similarity index 100% rename from src/easydiffraction/experiments/collections/__init__.py rename to src/easydiffraction/experiments/categories/__init__.py diff --git a/src/easydiffraction/experiments/components/__init__.py b/src/easydiffraction/experiments/categories/background/__init__.py similarity index 100% rename from src/easydiffraction/experiments/components/__init__.py rename to src/easydiffraction/experiments/categories/background/__init__.py diff --git a/src/easydiffraction/experiments/categories/background/base.py b/src/easydiffraction/experiments/categories/background/base.py new file mode 100644 index 00000000..48c4a7df --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/base.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from abc import abstractmethod +from typing import Any + +from easydiffraction.core.category import CategoryCollection + + +class BackgroundBase(CategoryCollection): + """Abstract base for background subcategories in experiments. + + Concrete implementations provide parameterized background models and + compute background intensities on the experiment grid. + """ + + @abstractmethod + def calculate(self, x_data: Any) -> Any: + """Compute background values for the provided x grid. + + Args: + x_data: X positions (e.g. 2θ, TOF) at which to evaluate. + + Returns: + Background intensity array aligned with ``x_data``. + """ + pass + + # TODO: Consider moving to CategoryCollection + @abstractmethod + def show(self) -> None: + """Print a human-readable view of background components.""" + pass diff --git a/src/easydiffraction/experiments/categories/background/chebyshev.py b/src/easydiffraction/experiments/categories/background/chebyshev.py new file mode 100644 index 00000000..8bdfb9ac --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/chebyshev.py @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Chebyshev polynomial background model. + +Provides a collection of polynomial terms and evaluation helpers. +""" + +from __future__ import annotations + +from typing import List +from typing import Union + +import numpy as np +from numpy.polynomial.chebyshev import chebval + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.experiments.categories.background.base import BackgroundBase +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import warning +from easydiffraction.utils.utils import render_table + + +class PolynomialTerm(CategoryItem): + """Chebyshev polynomial term. + + New public attribute names: ``order`` and ``coef`` replacing the + longer ``chebyshev_order`` / ``chebyshev_coef``. Backward-compatible + aliases are kept so existing serialized data / external code does + not break immediately. Tests should migrate to the short names. + """ + + def __init__(self, *, order: int, coef: float) -> None: + super().__init__() + + # Canonical descriptors + self._order = NumericDescriptor( + name='order', + description='Order used in a Chebyshev polynomial background term', + value_spec=AttributeSpec( + value=order, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_pd_background.Chebyshev_order']), + ) + self._coef = Parameter( + name='coef', + description='Coefficient used in a Chebyshev polynomial background term', + value_spec=AttributeSpec( + value=coef, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_pd_background.Chebyshev_coef']), + ) + + self._identity.category_code = 'background' + self._identity.category_entry_name = lambda: str(self.order.value) + + @property + def order(self): + return self._order + + @order.setter + def order(self, value): + self._order.value = value + + @property + def coef(self): + return self._coef + + @coef.setter + def coef(self, value): + self._coef.value = value + + +class ChebyshevPolynomialBackground(BackgroundBase): + _description: str = 'Chebyshev polynomial background' + + def __init__(self): + super().__init__(item_type=PolynomialTerm) + + def calculate(self, x_data): + """Evaluate polynomial background over x_data.""" + if not self._items: + print(warning('No background points found. Setting background to zero.')) + return np.zeros_like(x_data) + + u = (x_data - x_data.min()) / (x_data.max() - x_data.min()) * 2 - 1 + coefs = [term.coef.value for term in self._items] + y_data = chebval(u, coefs) + return y_data + + def show(self) -> None: + """Print a table of polynomial orders and coefficients.""" + columns_headers: List[str] = ['Order', 'Coefficient'] + columns_alignment = ['left', 'left'] + columns_data: List[List[Union[int, float]]] = [ + [t.order.value, t.coef.value] for t in self._items + ] + + print(paragraph('Chebyshev polynomial background terms')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) diff --git a/src/easydiffraction/experiments/categories/background/enums.py b/src/easydiffraction/experiments/categories/background/enums.py new file mode 100644 index 00000000..4d163c52 --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/enums.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Enumerations for background model types.""" + +from __future__ import annotations + +from enum import Enum + + +# TODO: Consider making EnumBase class with: default, description, ... +class BackgroundTypeEnum(str, Enum): + """Supported background model types.""" + + LINE_SEGMENT = 'line-segment' + CHEBYSHEV = 'chebyshev polynomial' + + @classmethod + def default(cls) -> 'BackgroundTypeEnum': + """Return a default background type.""" + return cls.LINE_SEGMENT + + def description(self) -> str: + """Human-friendly description for the enum value.""" + if self is BackgroundTypeEnum.LINE_SEGMENT: + return 'Linear interpolation between points' + elif self is BackgroundTypeEnum.CHEBYSHEV: + return 'Chebyshev polynomial background' diff --git a/src/easydiffraction/experiments/categories/background/factory.py b/src/easydiffraction/experiments/categories/background/factory.py new file mode 100644 index 00000000..2447f7a6 --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/factory.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Background collection entry point (public facade). + +End users should import Background classes from this module. Internals +live under the package +`easydiffraction.experiments.category_collections.background_types` +and are re-exported here for a stable and readable API. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Optional + +from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.background import BackgroundBase + + +class BackgroundFactory: + """Create background collections by type.""" + + BT = BackgroundTypeEnum + + @classmethod + def _supported_map(cls) -> dict: + """Return mapping of enum values to concrete background + classes. + """ + # Lazy import to avoid circulars + from easydiffraction.experiments.categories.background.chebyshev import ( + ChebyshevPolynomialBackground, + ) + from easydiffraction.experiments.categories.background.line_segment import ( + LineSegmentBackground, + ) + + return { + cls.BT.LINE_SEGMENT: LineSegmentBackground, + cls.BT.CHEBYSHEV: ChebyshevPolynomialBackground, + } + + @classmethod + def create( + cls, + background_type: Optional[BackgroundTypeEnum] = None, + ) -> BackgroundBase: + """Instantiate a background collection of requested type. + + If type is None, the default enum value is used. + """ + if background_type is None: + background_type = BackgroundTypeEnum.default() + + supported = cls._supported_map() + if background_type not in supported: + supported_types = list(supported.keys()) + + raise ValueError( + f"Unsupported background type: '{background_type}'.\n" + f' Supported background types: {[bt.value for bt in supported_types]}' + ) + + background_class = supported[background_type] + return background_class() diff --git a/src/easydiffraction/experiments/categories/background/line_segment.py b/src/easydiffraction/experiments/categories/background/line_segment.py new file mode 100644 index 00000000..0a946550 --- /dev/null +++ b/src/easydiffraction/experiments/categories/background/line_segment.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Line-segment background model. + +Interpolate user-specified points to form a background curve. +""" + +from __future__ import annotations + +from typing import List + +import numpy as np +from scipy.interpolate import interp1d + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.experiments.categories.background.base import BackgroundBase +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import warning +from easydiffraction.utils.utils import render_table + + +class LineSegment(CategoryItem): + """Single background control point for interpolation.""" + + def __init__(self, *, x: float, y: float): + super().__init__() + + self._x = NumericDescriptor( + name='x', + description=( + 'X-coordinates used to create many straight-line segments ' + 'representing the background in a calculated diffractogram.' + ), + value_spec=AttributeSpec( + value=x, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler(names=['_pd_background.line_segment_X']), + ) + self._y = Parameter( + name='y', # TODO: rename to intensity + description=( + 'Intensity used to create many straight-line segments ' + 'representing the background in a calculated diffractogram' + ), + value_spec=AttributeSpec( + value=y, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), # TODO: rename to intensity + cif_handler=CifHandler(names=['_pd_background.line_segment_intensity']), + ) + + self._identity.category_code = 'background' + self._identity.category_entry_name = lambda: str(self.x.value) + + @property + def x(self): + return self._x + + @x.setter + def x(self, value): + self._x.value = value + + @property + def y(self): + return self._y + + @y.setter + def y(self, value): + self._y.value = value + + +class LineSegmentBackground(BackgroundBase): + _description: str = 'Linear interpolation between points' + + def __init__(self): + super().__init__(item_type=LineSegment) + + def calculate(self, x_data): + """Interpolate background points over x_data.""" + if not self: + print(warning('No background points found. Setting background to zero.')) + return np.zeros_like(x_data) + + background_x = np.array([point.x.value for point in self.values()]) + background_y = np.array([point.y.value for point in self.values()]) + interp_func = interp1d( + background_x, + background_y, + kind='linear', + bounds_error=False, + fill_value=(background_y[0], background_y[-1]), + ) + y_data = interp_func(x_data) + return y_data + + def show(self) -> None: + """Print a table of control points (x, intensity).""" + columns_headers: List[str] = ['X', 'Intensity'] + columns_alignment = ['left', 'left'] + columns_data: List[List[float]] = [[p.x.value, p.y.value] for p in self._items] + + print(paragraph('Line-segment background points')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) diff --git a/src/easydiffraction/experiments/categories/excluded_regions.py b/src/easydiffraction/experiments/categories/excluded_regions.py new file mode 100644 index 00000000..afd760c2 --- /dev/null +++ b/src/easydiffraction/experiments/categories/excluded_regions.py @@ -0,0 +1,125 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Exclude ranges of x from fitting/plotting (masked regions).""" + +from typing import List + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import NumericDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.utils import render_table + + +class ExcludedRegion(CategoryItem): + """Closed interval [start, end] to be excluded.""" + + def __init__( + self, + *, + start: float, + end: float, + ): + super().__init__() + + self._start = NumericDescriptor( + name='start', + description='Start of the excluded region.', + value_spec=AttributeSpec( + value=start, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_excluded_region.start', + ] + ), + ) + self._end = NumericDescriptor( + name='end', + description='End of the excluded region.', + value_spec=AttributeSpec( + value=end, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_excluded_region.end', + ] + ), + ) + # self._category_entry_attr_name = f'{start}-{end}' + # self._category_entry_attr_name = self.start.name + # self.name = self.start.value + self._identity.category_code = 'excluded_regions' + self._identity.category_entry_name = lambda: self.start.value + + @property + def start(self) -> NumericDescriptor: + return self._start + + @start.setter + def start(self, value: float): + self._start.value = value + + @property + def end(self) -> NumericDescriptor: + return self._end + + @end.setter + def end(self, value: float): + self._end.value = value + + +class ExcludedRegions(CategoryCollection): + """Collection of ExcludedRegion instances.""" + + def __init__(self): + super().__init__(item_type=ExcludedRegion) + + def add(self, item: ExcludedRegion) -> None: + """Mark excluded points in the pattern when a region is + added. + """ + # 1. Call parent add first + + super().add(item) + + # 2. Now add extra behavior specific to ExcludedRegions + + datastore = self._parent.datastore + + # Boolean mask for points within the new excluded region + in_region = (datastore.full_x >= item.start.value) & (datastore.full_x <= item.end.value) + + # Update the exclusion mask + datastore.excluded[in_region] = True + + # Update the excluded points in the datastore + datastore.x = datastore.full_x[~datastore.excluded] + datastore.meas = datastore.full_meas[~datastore.excluded] + datastore.meas_su = datastore.full_meas_su[~datastore.excluded] + + def show(self) -> None: + """Print a table of excluded [start, end] intervals.""" + # TODO: Consider moving this to the base class + # to avoid code duplication with implementations in Background, + # etc. Consider using parameter names as column headers + columns_headers: List[str] = ['start', 'end'] + columns_alignment = ['left', 'left'] + columns_data: List[List[float]] = [[r.start.value, r.end.value] for r in self._items] + + print(paragraph('Excluded regions')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) diff --git a/src/easydiffraction/experiments/categories/experiment_type.py b/src/easydiffraction/experiments/categories/experiment_type.py new file mode 100644 index 00000000..8e94bd7e --- /dev/null +++ b/src/easydiffraction/experiments/categories/experiment_type.py @@ -0,0 +1,156 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Experiment type descriptor (form, beam, probe, scattering). + +This lightweight container stores the categorical attributes defining +an experiment configuration and handles CIF serialization via +``CifHandler``. +""" + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum +from easydiffraction.io.cif.handler import CifHandler + + +class ExperimentType(CategoryItem): + """Container of categorical attributes defining experiment flavor. + + Args: + sample_form: Powder or Single crystal. + beam_mode: Constant wavelength (CW) or time-of-flight (TOF). + radiation_probe: Neutrons or X-rays. + scattering_type: Bragg or Total. + """ + + def __init__( + self, + *, + sample_form: str, + beam_mode: str, + radiation_probe: str, + scattering_type: str, + ): + super().__init__() + + self._sample_form: StringDescriptor = StringDescriptor( + name='sample_form', + description='Specifies whether the diffraction data corresponds to ' + 'powder diffraction or single crystal diffraction', + value_spec=AttributeSpec( + value=sample_form, + type_=DataTypes.STRING, + default=SampleFormEnum.default(), + content_validator=MembershipValidator( + allowed=[member.value for member in SampleFormEnum] + ), + ), + cif_handler=CifHandler( + names=[ + '_expt_type.sample_form', + ] + ), + ) + + self._beam_mode: StringDescriptor = StringDescriptor( + name='beam_mode', + description='Defines whether the measurement is performed with a ' + 'constant wavelength (CW) or time-of-flight (TOF) method', + value_spec=AttributeSpec( + value=beam_mode, + type_=DataTypes.STRING, + default=BeamModeEnum.default(), + content_validator=MembershipValidator( + allowed=[member.value for member in BeamModeEnum] + ), + ), + cif_handler=CifHandler( + names=[ + '_expt_type.beam_mode', + ] + ), + ) + self._radiation_probe: StringDescriptor = StringDescriptor( + name='radiation_probe', + description='Specifies whether the measurement uses neutrons or X-rays', + value_spec=AttributeSpec( + value=radiation_probe, + type_=DataTypes.STRING, + default=RadiationProbeEnum.default(), + content_validator=MembershipValidator( + allowed=[member.value for member in RadiationProbeEnum] + ), + ), + cif_handler=CifHandler( + names=[ + '_expt_type.radiation_probe', + ] + ), + ) + self._scattering_type: StringDescriptor = StringDescriptor( + name='scattering_type', + description='Specifies whether the experiment uses Bragg scattering ' + '(for conventional structure refinement) or total scattering ' + '(for pair distribution function analysis - PDF)', + value_spec=AttributeSpec( + value=scattering_type, + type_=DataTypes.STRING, + default=ScatteringTypeEnum.default(), + content_validator=MembershipValidator( + allowed=[member.value for member in ScatteringTypeEnum] + ), + ), + cif_handler=CifHandler( + names=[ + '_expt_type.scattering_type', + ] + ), + ) + + self._identity.category_code = 'expt_type' + + @property + def sample_form(self): + """Sample form descriptor (powder/single crystal).""" + return self._sample_form + + @sample_form.setter + def sample_form(self, value): + """Set sample form value.""" + self._sample_form.value = value + + @property + def beam_mode(self): + """Beam mode descriptor (CW/TOF).""" + return self._beam_mode + + @beam_mode.setter + def beam_mode(self, value): + """Set beam mode value.""" + self._beam_mode.value = value + + @property + def radiation_probe(self): + """Radiation probe descriptor (neutrons/X-rays).""" + return self._radiation_probe + + @radiation_probe.setter + def radiation_probe(self, value): + """Set radiation probe value.""" + self._radiation_probe.value = value + + @property + def scattering_type(self): + """Scattering type descriptor (Bragg/Total).""" + return self._scattering_type + + @scattering_type.setter + def scattering_type(self, value): + """Set scattering type value.""" + self._scattering_type.value = value diff --git a/src/easydiffraction/sample_models/collections/__init__.py b/src/easydiffraction/experiments/categories/instrument/__init__.py similarity index 100% rename from src/easydiffraction/sample_models/collections/__init__.py rename to src/easydiffraction/experiments/categories/instrument/__init__.py diff --git a/src/easydiffraction/experiments/categories/instrument/base.py b/src/easydiffraction/experiments/categories/instrument/base.py new file mode 100644 index 00000000..429829d9 --- /dev/null +++ b/src/easydiffraction/experiments/categories/instrument/base.py @@ -0,0 +1,24 @@ +"""Instrument category base definitions for CWL/TOF instruments. + +This module provides the shared parent used by concrete instrument +implementations under the instrument category. +""" +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from easydiffraction.core.category import CategoryItem + + +class InstrumentBase(CategoryItem): + """Base class for instrument category items. + + This class sets the common ``category_code`` and is used as a base + for concrete CWL/TOF instrument definitions. + """ + + def __init__(self) -> None: + """Initialize instrument base and set category code.""" + super().__init__() + self._identity.category_code = 'instrument' diff --git a/src/easydiffraction/experiments/categories/instrument/cwl.py b/src/easydiffraction/experiments/categories/instrument/cwl.py new file mode 100644 index 00000000..e7d0c9cb --- /dev/null +++ b/src/easydiffraction/experiments/categories/instrument/cwl.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.experiments.categories.instrument.base import InstrumentBase +from easydiffraction.io.cif.handler import CifHandler + + +class CwlInstrument(InstrumentBase): + def __init__( + self, + *, + setup_wavelength=None, + calib_twotheta_offset=None, + ) -> None: + super().__init__() + + self._setup_wavelength: Parameter = Parameter( + name='wavelength', + description='Incident neutron or X-ray wavelength', + value_spec=AttributeSpec( + value=setup_wavelength, + type_=DataTypes.NUMERIC, + default=1.5406, + content_validator=RangeValidator(), + ), + units='Å', + cif_handler=CifHandler( + names=[ + '_instr.wavelength', + ] + ), + ) + self._calib_twotheta_offset: Parameter = Parameter( + name='twotheta_offset', + description='Instrument misalignment offset', + value_spec=AttributeSpec( + value=calib_twotheta_offset, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_instr.2theta_offset', + ] + ), + ) + + @property + def setup_wavelength(self): + """Incident wavelength parameter (Å).""" + return self._setup_wavelength + + @setup_wavelength.setter + def setup_wavelength(self, value): + """Set incident wavelength value (Å).""" + self._setup_wavelength.value = value + + @property + def calib_twotheta_offset(self): + """Instrument misalignment two-theta offset (deg).""" + return self._calib_twotheta_offset + + @calib_twotheta_offset.setter + def calib_twotheta_offset(self, value): + """Set two-theta offset value (deg).""" + self._calib_twotheta_offset.value = value diff --git a/src/easydiffraction/experiments/categories/instrument/factory.py b/src/easydiffraction/experiments/categories/instrument/factory.py new file mode 100644 index 00000000..db1c72ff --- /dev/null +++ b/src/easydiffraction/experiments/categories/instrument/factory.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for instrument category items. + +Provides a stable entry point for creating instrument objects from the +experiment's scattering type and beam mode. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Optional +from typing import Type + +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.instrument.base import InstrumentBase + + +class InstrumentFactory: + """Create instrument instances for supported modes. + + The factory hides implementation details and lazy-loads concrete + instrument classes to avoid circular imports. + """ + + ST = ScatteringTypeEnum + BM = BeamModeEnum + + @classmethod + def _supported_map(cls) -> dict: + # Lazy import to avoid circulars + from easydiffraction.experiments.categories.instrument.cwl import CwlInstrument + from easydiffraction.experiments.categories.instrument.tof import TofInstrument + + return { + cls.ST.BRAGG: { + cls.BM.CONSTANT_WAVELENGTH: CwlInstrument, + cls.BM.TIME_OF_FLIGHT: TofInstrument, + } + } + + @classmethod + def create( + cls, + scattering_type: Optional[ScatteringTypeEnum] = None, + beam_mode: Optional[BeamModeEnum] = None, + ) -> InstrumentBase: + if beam_mode is None: + beam_mode = BeamModeEnum.default() + if scattering_type is None: + scattering_type = ScatteringTypeEnum.default() + + supported = cls._supported_map() + + supported_scattering_types = list(supported.keys()) + if scattering_type not in supported_scattering_types: + raise ValueError( + f"Unsupported scattering type: '{scattering_type}'.\n " + f'Supported scattering types: {supported_scattering_types}' + ) + + supported_beam_modes = list(supported[scattering_type].keys()) + if beam_mode not in supported_beam_modes: + raise ValueError( + f"Unsupported beam mode: '{beam_mode}' for scattering type: " + f"'{scattering_type}'.\n " + f'Supported beam modes: {supported_beam_modes}' + ) + + instrument_class: Type[InstrumentBase] = supported[scattering_type][beam_mode] + return instrument_class() diff --git a/src/easydiffraction/experiments/categories/instrument/tof.py b/src/easydiffraction/experiments/categories/instrument/tof.py new file mode 100644 index 00000000..981a57e3 --- /dev/null +++ b/src/easydiffraction/experiments/categories/instrument/tof.py @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.experiments.categories.instrument.base import InstrumentBase +from easydiffraction.io.cif.handler import CifHandler + + +class TofInstrument(InstrumentBase): + def __init__( + self, + *, + setup_twotheta_bank=None, + calib_d_to_tof_offset=None, + calib_d_to_tof_linear=None, + calib_d_to_tof_quad=None, + calib_d_to_tof_recip=None, + ) -> None: + super().__init__() + + self._setup_twotheta_bank: Parameter = Parameter( + name='twotheta_bank', + description='Detector bank position', + value_spec=AttributeSpec( + value=setup_twotheta_bank, + type_=DataTypes.NUMERIC, + default=150.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_instr.2theta_bank', + ] + ), + ) + self._calib_d_to_tof_offset: Parameter = Parameter( + name='d_to_tof_offset', + description='TOF offset', + value_spec=AttributeSpec( + value=calib_d_to_tof_offset, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs', + cif_handler=CifHandler( + names=[ + '_instr.d_to_tof_offset', + ] + ), + ) + self._calib_d_to_tof_linear: Parameter = Parameter( + name='d_to_tof_linear', + description='TOF linear conversion', + value_spec=AttributeSpec( + value=calib_d_to_tof_linear, + type_=DataTypes.NUMERIC, + default=10000.0, + content_validator=RangeValidator(), + ), + units='µs/Å', + cif_handler=CifHandler( + names=[ + '_instr.d_to_tof_linear', + ] + ), + ) + self._calib_d_to_tof_quad: Parameter = Parameter( + name='d_to_tof_quad', + description='TOF quadratic correction', + value_spec=AttributeSpec( + value=calib_d_to_tof_quad, + type_=DataTypes.NUMERIC, + default=-0.00001, + content_validator=RangeValidator(), + ), + units='µs/Ų', + cif_handler=CifHandler( + names=[ + '_instr.d_to_tof_quad', + ] + ), + ) + self._calib_d_to_tof_recip: Parameter = Parameter( + name='d_to_tof_recip', + description='TOF reciprocal velocity correction', + value_spec=AttributeSpec( + value=calib_d_to_tof_recip, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs·Å', + cif_handler=CifHandler( + names=[ + '_instr.d_to_tof_recip', + ] + ), + ) + + @property + def setup_twotheta_bank(self): + """Detector bank two-theta position (deg).""" + return self._setup_twotheta_bank + + @setup_twotheta_bank.setter + def setup_twotheta_bank(self, value): + """Set detector bank two-theta position (deg).""" + self._setup_twotheta_bank.value = value + + @property + def calib_d_to_tof_offset(self): + """TOF offset calibration parameter (µs).""" + return self._calib_d_to_tof_offset + + @calib_d_to_tof_offset.setter + def calib_d_to_tof_offset(self, value): + """Set TOF offset (µs).""" + self._calib_d_to_tof_offset.value = value + + @property + def calib_d_to_tof_linear(self): + """Linear d to TOF conversion coefficient (µs/Å).""" + return self._calib_d_to_tof_linear + + @calib_d_to_tof_linear.setter + def calib_d_to_tof_linear(self, value): + """Set linear d to TOF coefficient (µs/Å).""" + self._calib_d_to_tof_linear.value = value + + @property + def calib_d_to_tof_quad(self): + """Quadratic d to TOF correction coefficient (µs/Ų).""" + return self._calib_d_to_tof_quad + + @calib_d_to_tof_quad.setter + def calib_d_to_tof_quad(self, value): + """Set quadratic d to TOF correction (µs/Ų).""" + self._calib_d_to_tof_quad.value = value + + @property + def calib_d_to_tof_recip(self): + """Reciprocal-velocity d to TOF correction (µs·Å).""" + return self._calib_d_to_tof_recip + + @calib_d_to_tof_recip.setter + def calib_d_to_tof_recip(self, value): + """Set reciprocal-velocity d to TOF correction (µs·Å).""" + self._calib_d_to_tof_recip.value = value diff --git a/src/easydiffraction/experiments/categories/linked_phases.py b/src/easydiffraction/experiments/categories/linked_phases.py new file mode 100644 index 00000000..9ee953a7 --- /dev/null +++ b/src/easydiffraction/experiments/categories/linked_phases.py @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Linked phases allow combining phases with scale factors.""" + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +class LinkedPhase(CategoryItem): + """Link to a phase by id with a scale factor.""" + + def __init__( + self, + *, + id: str, # TODO: need new name instead of id + scale: float, + ): + super().__init__() + + self._id = StringDescriptor( + name='id', + description='Identifier of the linked phase.', + value_spec=AttributeSpec( + value=id, + type_=DataTypes.STRING, + default='Si', + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_pd_phase_block.id', + ] + ), + ) + self._scale = Parameter( + name='scale', + description='Scale factor of the linked phase.', + value_spec=AttributeSpec( + value=scale, + type_=DataTypes.NUMERIC, + default=1.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_pd_phase_block.scale', + ] + ), + ) + self._identity.category_code = 'linked_phases' + self._identity.category_entry_name = lambda: self.id.value + + @property + def id(self) -> StringDescriptor: + """Identifier of the linked phase.""" + return self._id + + @id.setter + def id(self, value: str): + """Set the linked phase identifier.""" + self._id.value = value + + @property + def scale(self) -> Parameter: + """Scale factor parameter.""" + return self._scale + + @scale.setter + def scale(self, value: float): + """Set scale factor value.""" + self._scale.value = value + + +class LinkedPhases(CategoryCollection): + """Collection of LinkedPhase instances.""" + + def __init__(self): + """Create an empty collection of linked phases.""" + super().__init__(item_type=LinkedPhase) diff --git a/src/easydiffraction/sample_models/components/__init__.py b/src/easydiffraction/experiments/categories/peak/__init__.py similarity index 100% rename from src/easydiffraction/sample_models/components/__init__.py rename to src/easydiffraction/experiments/categories/peak/__init__.py diff --git a/src/easydiffraction/experiments/categories/peak/base.py b/src/easydiffraction/experiments/categories/peak/base.py new file mode 100644 index 00000000..ce8030b9 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/base.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Base class for peak profile categories.""" + +from easydiffraction.core.category import CategoryItem + + +class PeakBase(CategoryItem): + """Base class for peak profile categories.""" + + def __init__(self) -> None: + super().__init__() + self._identity.category_code = 'peak' diff --git a/src/easydiffraction/experiments/categories/peak/cwl.py b/src/easydiffraction/experiments/categories/peak/cwl.py new file mode 100644 index 00000000..9170c1dc --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/cwl.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Constant-wavelength peak profile classes.""" + +from easydiffraction.experiments.categories.peak.base import PeakBase +from easydiffraction.experiments.categories.peak.cwl_mixins import CwlBroadeningMixin +from easydiffraction.experiments.categories.peak.cwl_mixins import EmpiricalAsymmetryMixin +from easydiffraction.experiments.categories.peak.cwl_mixins import FcjAsymmetryMixin + + +class CwlPseudoVoigt( + PeakBase, + CwlBroadeningMixin, +): + """Constant-wavelength pseudo-Voigt peak shape.""" + + def __init__(self) -> None: + super().__init__() + self._add_constant_wavelength_broadening() + + +class CwlSplitPseudoVoigt( + PeakBase, + CwlBroadeningMixin, + EmpiricalAsymmetryMixin, +): + """Split pseudo-Voigt (empirical asymmetry) for CWL mode.""" + + def __init__(self) -> None: + super().__init__() + self._add_constant_wavelength_broadening() + self._add_empirical_asymmetry() + + +class CwlThompsonCoxHastings( + PeakBase, + CwlBroadeningMixin, + FcjAsymmetryMixin, +): + """Thompson–Cox–Hastings with FCJ asymmetry for CWL mode.""" + + def __init__(self) -> None: + super().__init__() + self._add_constant_wavelength_broadening() + self._add_fcj_asymmetry() diff --git a/src/easydiffraction/experiments/categories/peak/cwl_mixins.py b/src/easydiffraction/experiments/categories/peak/cwl_mixins.py new file mode 100644 index 00000000..d37f98d4 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/cwl_mixins.py @@ -0,0 +1,331 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Constant-wavelength (CWL) peak-profile mixins. + +This module provides mixins that add broadening and asymmetry parameters +for constant-wavelength powder diffraction peak profiles. They are +composed into concrete peak classes elsewhere. +""" + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler + + +class CwlBroadeningMixin: + """Mixin that adds CWL Gaussian and Lorentz broadening + parameters. + """ + + def _add_constant_wavelength_broadening(self) -> None: + """Create CWL broadening parameters and attach them to the + class. + + Defines Gaussian (U, V, W) and Lorentz (X, Y) terms + often used in the TCH formulation. Values are stored as + ``Parameter`` objects. + """ + self._broad_gauss_u: Parameter = Parameter( + name='broad_gauss_u', + description='Gaussian broadening coefficient (dependent on ' + 'sample size and instrument resolution)', + value_spec=AttributeSpec( + value=0.01, + type_=DataTypes.NUMERIC, + default=0.01, + content_validator=RangeValidator(), + ), + units='deg²', + cif_handler=CifHandler( + names=[ + '_peak.broad_gauss_u', + ] + ), + ) + self._broad_gauss_v: Parameter = Parameter( + name='broad_gauss_v', + description='Gaussian broadening coefficient (instrumental broadening contribution)', + value_spec=AttributeSpec( + value=-0.01, + type_=DataTypes.NUMERIC, + default=-0.01, + content_validator=RangeValidator(), + ), + units='deg²', + cif_handler=CifHandler( + names=[ + '_peak.broad_gauss_v', + ] + ), + ) + self._broad_gauss_w: Parameter = Parameter( + name='broad_gauss_w', + description='Gaussian broadening coefficient (instrumental broadening contribution)', + value_spec=AttributeSpec( + value=0.02, + type_=DataTypes.NUMERIC, + default=0.02, + content_validator=RangeValidator(), + ), + units='deg²', + cif_handler=CifHandler( + names=[ + '_peak.broad_gauss_w', + ] + ), + ) + self._broad_lorentz_x: Parameter = Parameter( + name='broad_lorentz_x', + description='Lorentzian broadening coefficient (dependent on sample strain effects)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_peak.broad_lorentz_x', + ] + ), + ) + self._broad_lorentz_y: Parameter = Parameter( + name='broad_lorentz_y', + description='Lorentzian broadening coefficient (dependent on ' + 'microstructural defects and strain)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_peak.broad_lorentz_y', + ] + ), + ) + + @property + def broad_gauss_u(self) -> Parameter: + """Get Gaussian U broadening parameter.""" + return self._broad_gauss_u + + @broad_gauss_u.setter + def broad_gauss_u(self, value: float) -> None: + """Set Gaussian U broadening parameter.""" + self._broad_gauss_u.value = value + + @property + def broad_gauss_v(self) -> Parameter: + """Get Gaussian V broadening parameter.""" + return self._broad_gauss_v + + @broad_gauss_v.setter + def broad_gauss_v(self, value: float) -> None: + """Set Gaussian V broadening parameter.""" + self._broad_gauss_v.value = value + + @property + def broad_gauss_w(self) -> Parameter: + """Get Gaussian W broadening parameter.""" + return self._broad_gauss_w + + @broad_gauss_w.setter + def broad_gauss_w(self, value: float) -> None: + """Set Gaussian W broadening parameter.""" + self._broad_gauss_w.value = value + + @property + def broad_lorentz_x(self) -> Parameter: + """Get Lorentz X broadening parameter.""" + return self._broad_lorentz_x + + @broad_lorentz_x.setter + def broad_lorentz_x(self, value: float) -> None: + """Set Lorentz X broadening parameter.""" + self._broad_lorentz_x.value = value + + @property + def broad_lorentz_y(self) -> Parameter: + """Get Lorentz Y broadening parameter.""" + return self._broad_lorentz_y + + @broad_lorentz_y.setter + def broad_lorentz_y(self, value: float) -> None: + """Set Lorentz Y broadening parameter.""" + self._broad_lorentz_y.value = value + + +class EmpiricalAsymmetryMixin: + """Mixin that adds empirical CWL peak asymmetry parameters.""" + + def _add_empirical_asymmetry(self) -> None: + """Create empirical asymmetry parameters p1..p4.""" + self._asym_empir_1: Parameter = Parameter( + name='asym_empir_1', + description='Empirical asymmetry coefficient p1', + value_spec=AttributeSpec( + value=0.1, + type_=DataTypes.NUMERIC, + default=0.1, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_empir_1', + ] + ), + ) + self._asym_empir_2: Parameter = Parameter( + name='asym_empir_2', + description='Empirical asymmetry coefficient p2', + value_spec=AttributeSpec( + value=0.2, + type_=DataTypes.NUMERIC, + default=0.2, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_empir_2', + ] + ), + ) + self._asym_empir_3: Parameter = Parameter( + name='asym_empir_3', + description='Empirical asymmetry coefficient p3', + value_spec=AttributeSpec( + value=0.3, + type_=DataTypes.NUMERIC, + default=0.3, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_empir_3', + ] + ), + ) + self._asym_empir_4: Parameter = Parameter( + name='asym_empir_4', + description='Empirical asymmetry coefficient p4', + value_spec=AttributeSpec( + value=0.4, + type_=DataTypes.NUMERIC, + default=0.4, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_empir_4', + ] + ), + ) + + @property + def asym_empir_1(self) -> Parameter: + """Get empirical asymmetry coefficient p1.""" + return self._asym_empir_1 + + @asym_empir_1.setter + def asym_empir_1(self, value: float) -> None: + """Set empirical asymmetry coefficient p1.""" + self._asym_empir_1.value = value + + @property + def asym_empir_2(self) -> Parameter: + """Get empirical asymmetry coefficient p2.""" + return self._asym_empir_2 + + @asym_empir_2.setter + def asym_empir_2(self, value: float) -> None: + """Set empirical asymmetry coefficient p2.""" + self._asym_empir_2.value = value + + @property + def asym_empir_3(self) -> Parameter: + """Get empirical asymmetry coefficient p3.""" + return self._asym_empir_3 + + @asym_empir_3.setter + def asym_empir_3(self, value: float) -> None: + """Set empirical asymmetry coefficient p3.""" + self._asym_empir_3.value = value + + @property + def asym_empir_4(self) -> Parameter: + """Get empirical asymmetry coefficient p4.""" + return self._asym_empir_4 + + @asym_empir_4.setter + def asym_empir_4(self, value: float) -> None: + """Set empirical asymmetry coefficient p4.""" + self._asym_empir_4.value = value + + +class FcjAsymmetryMixin: + """Mixin that adds Finger–Cox–Jephcoat (FCJ) asymmetry params.""" + + def _add_fcj_asymmetry(self) -> None: + """Create FCJ asymmetry parameters.""" + self._asym_fcj_1: Parameter = Parameter( + name='asym_fcj_1', + description='Finger-Cox-Jephcoat asymmetry parameter 1', + value_spec=AttributeSpec( + value=0.01, + type_=DataTypes.NUMERIC, + default=0.01, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_fcj_1', + ] + ), + ) + self._asym_fcj_2: Parameter = Parameter( + name='asym_fcj_2', + description='Finger-Cox-Jephcoat asymmetry parameter 2', + value_spec=AttributeSpec( + value=0.02, + type_=DataTypes.NUMERIC, + default=0.02, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_fcj_2', + ] + ), + ) + + @property + def asym_fcj_1(self) -> Parameter: + """Get FCJ asymmetry parameter 1.""" + return self._asym_fcj_1 + + @asym_fcj_1.setter + def asym_fcj_1(self, value: float) -> None: + """Set FCJ asymmetry parameter 1.""" + self._asym_fcj_1.value = value + + @property + def asym_fcj_2(self) -> Parameter: + """Get FCJ asymmetry parameter 2.""" + return self._asym_fcj_2 + + @asym_fcj_2.setter + def asym_fcj_2(self, value: float) -> None: + """Set FCJ asymmetry parameter 2.""" + self._asym_fcj_2.value = value diff --git a/src/easydiffraction/experiments/categories/peak/factory.py b/src/easydiffraction/experiments/categories/peak/factory.py new file mode 100644 index 00000000..d96092e9 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/factory.py @@ -0,0 +1,130 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from typing import Optional + +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import PeakProfileTypeEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + +# TODO: Consider inheriting from FactoryBase +class PeakFactory: + """Factory for creating peak profile objects. + + Lazily imports implementations to avoid circular dependencies and + selects the appropriate class based on scattering type, beam mode + and requested profile type. + """ + + ST = ScatteringTypeEnum + BM = BeamModeEnum + PPT = PeakProfileTypeEnum + _supported = None # type: ignore[var-annotated] + + @classmethod + def _supported_map(cls): + """Return nested mapping of supported profile classes. + + Structure: + ``{ScatteringType: {BeamMode: {ProfileType: Class}}}``. + """ + # Lazy import to avoid circular imports between + # base and cw/tof/pdf modules + if cls._supported is None: + from easydiffraction.experiments.categories.peak.cwl import CwlPseudoVoigt as CwPv + from easydiffraction.experiments.categories.peak.cwl import ( + CwlSplitPseudoVoigt as CwSpv, + ) + from easydiffraction.experiments.categories.peak.cwl import ( + CwlThompsonCoxHastings as CwTch, + ) + from easydiffraction.experiments.categories.peak.tof import TofPseudoVoigt as TofPv + from easydiffraction.experiments.categories.peak.tof import ( + TofPseudoVoigtBackToBack as TofBtb, + ) + from easydiffraction.experiments.categories.peak.tof import ( + TofPseudoVoigtIkedaCarpenter as TofIc, + ) + from easydiffraction.experiments.categories.peak.total import ( + TotalGaussianDampedSinc as PdfGds, + ) + + cls._supported = { + cls.ST.BRAGG: { + cls.BM.CONSTANT_WAVELENGTH: { + cls.PPT.PSEUDO_VOIGT: CwPv, + cls.PPT.SPLIT_PSEUDO_VOIGT: CwSpv, + cls.PPT.THOMPSON_COX_HASTINGS: CwTch, + }, + cls.BM.TIME_OF_FLIGHT: { + cls.PPT.PSEUDO_VOIGT: TofPv, + cls.PPT.PSEUDO_VOIGT_IKEDA_CARPENTER: TofIc, + cls.PPT.PSEUDO_VOIGT_BACK_TO_BACK: TofBtb, + }, + }, + cls.ST.TOTAL: { + cls.BM.CONSTANT_WAVELENGTH: { + cls.PPT.GAUSSIAN_DAMPED_SINC: PdfGds, + }, + cls.BM.TIME_OF_FLIGHT: { + cls.PPT.GAUSSIAN_DAMPED_SINC: PdfGds, + }, + }, + } + return cls._supported + + @classmethod + def create( + cls, + scattering_type: Optional[ScatteringTypeEnum] = None, + beam_mode: Optional[BeamModeEnum] = None, + profile_type: Optional[PeakProfileTypeEnum] = None, + ): + """Instantiate a peak profile for the given configuration. + + Args: + scattering_type: Bragg or Total. Defaults to library + default. + beam_mode: CW or TOF. Defaults to library default. + profile_type: Concrete profile within the mode. If omitted, + a sensible default is chosen based on the other args. + + Returns: + A newly created peak profile object. + + Raises: + ValueError: If a requested option is not supported. + """ + if beam_mode is None: + beam_mode = BeamModeEnum.default() + if scattering_type is None: + scattering_type = ScatteringTypeEnum.default() + if profile_type is None: + profile_type = PeakProfileTypeEnum.default(scattering_type, beam_mode) + supported = cls._supported_map() + supported_scattering_types = list(supported.keys()) + if scattering_type not in supported_scattering_types: + raise ValueError( + f"Unsupported scattering type: '{scattering_type}'.\n" + f'Supported scattering types: {supported_scattering_types}' + ) + + supported_beam_modes = list(supported[scattering_type].keys()) + if beam_mode not in supported_beam_modes: + raise ValueError( + f"Unsupported beam mode: '{beam_mode}' for scattering type: " + f"'{scattering_type}'.\n Supported beam modes: '{supported_beam_modes}'" + ) + + supported_profile_types = list(supported[scattering_type][beam_mode].keys()) + if profile_type not in supported_profile_types: + raise ValueError( + f"Unsupported profile type '{profile_type}' for beam mode '{beam_mode}'.\n" + f'Supported profile types: {supported_profile_types}' + ) + + peak_class = supported[scattering_type][beam_mode][profile_type] + peak_obj = peak_class() + + return peak_obj diff --git a/src/easydiffraction/experiments/categories/peak/tof.py b/src/easydiffraction/experiments/categories/peak/tof.py new file mode 100644 index 00000000..c6a44512 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/tof.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Time-of-flight peak profile classes.""" + +from easydiffraction.experiments.categories.peak.base import PeakBase +from easydiffraction.experiments.categories.peak.tof_mixins import IkedaCarpenterAsymmetryMixin +from easydiffraction.experiments.categories.peak.tof_mixins import TofBroadeningMixin + + +class TofPseudoVoigt( + PeakBase, + TofBroadeningMixin, +): + """Time-of-flight pseudo-Voigt peak shape.""" + + def __init__(self) -> None: + super().__init__() + self._add_time_of_flight_broadening() + + +class TofPseudoVoigtIkedaCarpenter( + PeakBase, + TofBroadeningMixin, + IkedaCarpenterAsymmetryMixin, +): + """TOF pseudo-Voigt with Ikeda–Carpenter asymmetry.""" + + def __init__(self) -> None: + super().__init__() + self._add_time_of_flight_broadening() + self._add_ikeda_carpenter_asymmetry() + + +class TofPseudoVoigtBackToBack( + PeakBase, + TofBroadeningMixin, + IkedaCarpenterAsymmetryMixin, +): + """TOF back-to-back pseudo-Voigt with asymmetry.""" + + def __init__(self) -> None: + super().__init__() + self._add_time_of_flight_broadening() + self._add_ikeda_carpenter_asymmetry() diff --git a/src/easydiffraction/experiments/categories/peak/tof_mixins.py b/src/easydiffraction/experiments/categories/peak/tof_mixins.py new file mode 100644 index 00000000..47fd4e41 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/tof_mixins.py @@ -0,0 +1,293 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Time-of-flight (TOF) peak-profile mixins. + +Defines mixins that add Gaussian/Lorentz broadening, mixing, and +Ikeda–Carpenter asymmetry parameters used by TOF peak shapes. +""" + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler + + +class TofBroadeningMixin: + """Mixin that adds TOF Gaussian/Lorentz broadening and mixing + terms. + """ + + def _add_time_of_flight_broadening(self) -> None: + """Create TOF broadening and mixing parameters.""" + self._broad_gauss_sigma_0: Parameter = Parameter( + name='gauss_sigma_0', + description='Gaussian broadening coefficient (instrumental resolution)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs²', + cif_handler=CifHandler( + names=[ + '_peak.gauss_sigma_0', + ] + ), + ) + self._broad_gauss_sigma_1: Parameter = Parameter( + name='gauss_sigma_1', + description='Gaussian broadening coefficient (dependent on d-spacing)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs/Å', + cif_handler=CifHandler( + names=[ + '_peak.gauss_sigma_1', + ] + ), + ) + self._broad_gauss_sigma_2: Parameter = Parameter( + name='gauss_sigma_2', + description='Gaussian broadening coefficient (instrument-dependent term)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs²/Ų', + cif_handler=CifHandler( + names=[ + '_peak.gauss_sigma_2', + ] + ), + ) + self._broad_lorentz_gamma_0: Parameter = Parameter( + name='lorentz_gamma_0', + description='Lorentzian broadening coefficient (dependent on microstrain effects)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs', + cif_handler=CifHandler( + names=[ + '_peak.lorentz_gamma_0', + ] + ), + ) + self._broad_lorentz_gamma_1: Parameter = Parameter( + name='lorentz_gamma_1', + description='Lorentzian broadening coefficient (dependent on d-spacing)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs/Å', + cif_handler=CifHandler( + names=[ + '_peak.lorentz_gamma_1', + ] + ), + ) + self._broad_lorentz_gamma_2: Parameter = Parameter( + name='lorentz_gamma_2', + description='Lorentzian broadening coefficient (instrument-dependent term)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='µs²/Ų', + cif_handler=CifHandler( + names=[ + '_peak.lorentz_gamma_2', + ] + ), + ) + self._broad_mix_beta_0: Parameter = Parameter( + name='mix_beta_0', + description='Mixing parameter. Defines the ratio of Gaussian ' + 'to Lorentzian contributions in TOF profiles', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_peak.mix_beta_0', + ] + ), + ) + self._broad_mix_beta_1: Parameter = Parameter( + name='mix_beta_1', + description='Mixing parameter. Defines the ratio of Gaussian ' + 'to Lorentzian contributions in TOF profiles', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='deg', + cif_handler=CifHandler( + names=[ + '_peak.mix_beta_1', + ] + ), + ) + + @property + def broad_gauss_sigma_0(self) -> Parameter: + """Get Gaussian sigma_0 parameter.""" + return self._broad_gauss_sigma_0 + + @broad_gauss_sigma_0.setter + def broad_gauss_sigma_0(self, value: float) -> None: + """Set Gaussian sigma_0 parameter.""" + self._broad_gauss_sigma_0.value = value + + @property + def broad_gauss_sigma_1(self) -> Parameter: + """Get Gaussian sigma_1 parameter.""" + return self._broad_gauss_sigma_1 + + @broad_gauss_sigma_1.setter + def broad_gauss_sigma_1(self, value: float) -> None: + """Set Gaussian sigma_1 parameter.""" + self._broad_gauss_sigma_1.value = value + + @property + def broad_gauss_sigma_2(self) -> Parameter: + """Get Gaussian sigma_2 parameter.""" + return self._broad_gauss_sigma_2 + + @broad_gauss_sigma_2.setter + def broad_gauss_sigma_2(self, value: float) -> None: + """Set Gaussian sigma_2 parameter.""" + self._broad_gauss_sigma_2.value = value + + @property + def broad_lorentz_gamma_0(self) -> Parameter: + """Get Lorentz gamma_0 parameter.""" + return self._broad_lorentz_gamma_0 + + @broad_lorentz_gamma_0.setter + def broad_lorentz_gamma_0(self, value: float) -> None: + """Set Lorentz gamma_0 parameter.""" + self._broad_lorentz_gamma_0.value = value + + @property + def broad_lorentz_gamma_1(self) -> Parameter: + """Get Lorentz gamma_1 parameter.""" + return self._broad_lorentz_gamma_1 + + @broad_lorentz_gamma_1.setter + def broad_lorentz_gamma_1(self, value: float) -> None: + """Set Lorentz gamma_1 parameter.""" + self._broad_lorentz_gamma_1.value = value + + @property + def broad_lorentz_gamma_2(self) -> Parameter: + """Get Lorentz gamma_2 parameter.""" + return self._broad_lorentz_gamma_2 + + @broad_lorentz_gamma_2.setter + def broad_lorentz_gamma_2(self, value: float) -> None: + """Set Lorentz gamma_2 parameter.""" + self._broad_lorentz_gamma_2.value = value + + @property + def broad_mix_beta_0(self) -> Parameter: + """Get mixing parameter beta_0.""" + return self._broad_mix_beta_0 + + @broad_mix_beta_0.setter + def broad_mix_beta_0(self, value: float) -> None: + """Set mixing parameter beta_0.""" + self._broad_mix_beta_0.value = value + + @property + def broad_mix_beta_1(self) -> Parameter: + """Get mixing parameter beta_1.""" + return self._broad_mix_beta_1 + + @broad_mix_beta_1.setter + def broad_mix_beta_1(self, value: float) -> None: + """Set mixing parameter beta_1.""" + self._broad_mix_beta_1.value = value + + +class IkedaCarpenterAsymmetryMixin: + """Mixin that adds Ikeda–Carpenter asymmetry parameters.""" + + def _add_ikeda_carpenter_asymmetry(self) -> None: + """Create Ikeda–Carpenter asymmetry parameters alpha_0 and + alpha_1. + """ + self._asym_alpha_0: Parameter = Parameter( + name='asym_alpha_0', + description='Ikeda-Carpenter asymmetry parameter α₀', + value_spec=AttributeSpec( + value=0.01, + type_=DataTypes.NUMERIC, + default=0.01, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_alpha_0', + ] + ), + ) + self._asym_alpha_1: Parameter = Parameter( + name='asym_alpha_1', + description='Ikeda-Carpenter asymmetry parameter α₁', + value_spec=AttributeSpec( + value=0.02, + type_=DataTypes.NUMERIC, + default=0.02, + content_validator=RangeValidator(), + ), + units='', + cif_handler=CifHandler( + names=[ + '_peak.asym_alpha_1', + ] + ), + ) + + @property + def asym_alpha_0(self) -> Parameter: + """Get Ikeda–Carpenter asymmetry alpha_0.""" + return self._asym_alpha_0 + + @asym_alpha_0.setter + def asym_alpha_0(self, value: float) -> None: + """Set Ikeda–Carpenter asymmetry alpha_0.""" + self._asym_alpha_0.value = value + + @property + def asym_alpha_1(self) -> Parameter: + """Get Ikeda–Carpenter asymmetry alpha_1.""" + return self._asym_alpha_1 + + @asym_alpha_1.setter + def asym_alpha_1(self, value: float) -> None: + """Set Ikeda–Carpenter asymmetry alpha_1.""" + self._asym_alpha_1.value = value diff --git a/src/easydiffraction/experiments/categories/peak/total.py b/src/easydiffraction/experiments/categories/peak/total.py new file mode 100644 index 00000000..3b843c2e --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/total.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Total-scattering (PDF) peak profile classes.""" + +from easydiffraction.experiments.categories.peak.base import PeakBase +from easydiffraction.experiments.categories.peak.total_mixins import TotalBroadeningMixin + + +class TotalGaussianDampedSinc( + PeakBase, + TotalBroadeningMixin, +): + """Gaussian-damped sinc peak for total scattering (PDF).""" + + def __init__(self) -> None: + super().__init__() + self._add_pair_distribution_function_broadening() diff --git a/src/easydiffraction/experiments/categories/peak/total_mixins.py b/src/easydiffraction/experiments/categories/peak/total_mixins.py new file mode 100644 index 00000000..4d9b5877 --- /dev/null +++ b/src/easydiffraction/experiments/categories/peak/total_mixins.py @@ -0,0 +1,181 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Total scattering/PDF peak-profile mixins. + +Adds damping, broadening, sharpening and envelope parameters used in +pair distribution function (PDF) modeling. +""" + +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler + + +class TotalBroadeningMixin: + """Mixin adding PDF broadening/damping/sharpening parameters.""" + + def _add_pair_distribution_function_broadening(self): + """Create PDF parameters: damp_q, broad_q, cutoff_q, + sharp deltas, and particle diameter envelope. + """ + self._damp_q: Parameter = Parameter( + name='damp_q', + description='Instrumental Q-resolution damping factor ' + '(affects high-r PDF peak amplitude)', + value_spec=AttributeSpec( + value=0.05, + type_=DataTypes.NUMERIC, + default=0.05, + content_validator=RangeValidator(), + ), + units='Å⁻¹', + cif_handler=CifHandler( + names=[ + '_peak.damp_q', + ] + ), + ) + self._broad_q: Parameter = Parameter( + name='broad_q', + description='Quadratic PDF peak broadening coefficient ' + '(thermal and model uncertainty contribution)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Å⁻²', + cif_handler=CifHandler( + names=[ + '_peak.broad_q', + ] + ), + ) + self._cutoff_q: Parameter = Parameter( + name='cutoff_q', + description='Q-value cutoff applied to model PDF for Fourier ' + 'transform (controls real-space resolution)', + value_spec=AttributeSpec( + value=25.0, + type_=DataTypes.NUMERIC, + default=25.0, + content_validator=RangeValidator(), + ), + units='Å⁻¹', + cif_handler=CifHandler( + names=[ + '_peak.cutoff_q', + ] + ), + ) + self._sharp_delta_1: Parameter = Parameter( + name='sharp_delta_1', + description='PDF peak sharpening coefficient (1/r dependence)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Å', + cif_handler=CifHandler( + names=[ + '_peak.sharp_delta_1', + ] + ), + ) + self._sharp_delta_2: Parameter = Parameter( + name='sharp_delta_2', + description='PDF peak sharpening coefficient (1/r² dependence)', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Ų', + cif_handler=CifHandler( + names=[ + '_peak.sharp_delta_2', + ] + ), + ) + self._damp_particle_diameter: Parameter = Parameter( + name='damp_particle_diameter', + description='Particle diameter for spherical envelope damping correction in PDF', + value_spec=AttributeSpec( + value=0.0, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Å', + cif_handler=CifHandler( + names=[ + '_peak.damp_particle_diameter', + ] + ), + ) + + @property + def damp_q(self) -> Parameter: + """Get Q-resolution damping factor.""" + return self._damp_q + + @damp_q.setter + def damp_q(self, value: float) -> None: + """Set Q-resolution damping factor.""" + self._damp_q.value = value + + @property + def broad_q(self) -> Parameter: + """Get quadratic PDF broadening coefficient.""" + return self._broad_q + + @broad_q.setter + def broad_q(self, value: float) -> None: + """Set quadratic PDF broadening coefficient.""" + self._broad_q.value = value + + @property + def cutoff_q(self) -> Parameter: + """Get Q cutoff used for Fourier transform.""" + return self._cutoff_q + + @cutoff_q.setter + def cutoff_q(self, value: float) -> None: + """Set Q cutoff used for Fourier transform.""" + self._cutoff_q.value = value + + @property + def sharp_delta_1(self) -> Parameter: + """Get sharpening coefficient with 1/r dependence.""" + return self._sharp_delta_1 + + @sharp_delta_1.setter + def sharp_delta_1(self, value: float) -> None: + """Set sharpening coefficient with 1/r dependence.""" + self._sharp_delta_1.value = value + + @property + def sharp_delta_2(self) -> Parameter: + """Get sharpening coefficient with 1/r^2 dependence.""" + return self._sharp_delta_2 + + @sharp_delta_2.setter + def sharp_delta_2(self, value: float) -> None: + """Set sharpening coefficient with 1/r^2 dependence.""" + self._sharp_delta_2.value = value + + @property + def damp_particle_diameter(self) -> Parameter: + """Get particle diameter for spherical envelope damping.""" + return self._damp_particle_diameter + + @damp_particle_diameter.setter + def damp_particle_diameter(self, value: float) -> None: + """Set particle diameter for spherical envelope damping.""" + self._damp_particle_diameter.value = value diff --git a/src/easydiffraction/experiments/collections/background.py b/src/easydiffraction/experiments/collections/background.py deleted file mode 100644 index 56c42298..00000000 --- a/src/easydiffraction/experiments/collections/background.py +++ /dev/null @@ -1,241 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from abc import abstractmethod -from enum import Enum -from typing import Dict -from typing import List -from typing import Optional -from typing import Type -from typing import Union - -import numpy as np -from numpy.polynomial.chebyshev import chebval -from scipy.interpolate import interp1d - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter -from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.formatting import warning -from easydiffraction.utils.utils import render_table - - -# TODO: rename to LineSegment -class Point(Component): - @property - def category_key(self) -> str: - return 'background' - - @property - def cif_category_key(self) -> str: - return 'pd_background' - - def __init__( - self, - x: float, - y: float, - ): - super().__init__() - - self.x = Descriptor( - value=x, - name='x', - cif_name='line_segment_X', - description='X-coordinates used to create many straight-line segments ' - 'representing the background in a calculated diffractogram.', - ) - self.y = Parameter( - value=y, # TODO: rename to intensity - name='y', # TODO: rename to intensity - cif_name='line_segment_intensity', - description='Intensity used to create many straight-line segments ' - 'representing the background in a calculated diffractogram', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = str(x) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class PolynomialTerm(Component): - # TODO: make consistency in where to place the following properties: - # before or after the __init__ method - @property - def category_key(self) -> str: - return 'background' - - @property - def cif_category_key(self): - return 'pd_background' - - def __init__( - self, - order: int, - coef: float, - ) -> None: - super().__init__() - - self.order = Descriptor( - value=order, - name='chebyshev_order', - cif_name='Chebyshev_order', - description='The value of an order used in a Chebyshev polynomial ' - 'equation representing the background in a calculated diffractogram', - ) - self.coef = Parameter( - value=coef, - name='chebyshev_coef', - cif_name='Chebyshev_coef', - description='The value of a coefficient used in a Chebyshev polynomial ' - 'equation representing the background in a calculated diffractogram', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = str(order) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class BackgroundBase(Collection): - @property - def _type(self) -> str: - return 'category' # datablock or category - - @abstractmethod - def calculate(self, x_data: np.ndarray) -> np.ndarray: - pass - - @abstractmethod - def show(self) -> None: - pass - - -class LineSegmentBackground(BackgroundBase): - _description: str = 'Linear interpolation between points' - - @property - def _child_class(self) -> Type[Point]: - return Point - - def calculate(self, x_data: np.ndarray) -> np.ndarray: - """Interpolate background points over x_data.""" - if not self._items: - print(warning('No background points found. Setting background to zero.')) - return np.zeros_like(x_data) - - background_x = np.array([point.x.value for point in self._items.values()]) - background_y = np.array([point.y.value for point in self._items.values()]) - interp_func = interp1d( - background_x, - background_y, - kind='linear', - bounds_error=False, - fill_value=( - background_y[0], - background_y[-1], - ), - ) - y_data = interp_func(x_data) - return y_data - - def show(self) -> None: - columns_headers: List[str] = ['X', 'Intensity'] - columns_alignment = ['left', 'left'] - columns_data: List[List[float]] = [] - for point in self._items.values(): - x = point.x.value - y = point.y.value - columns_data.append([x, y]) - - print(paragraph('Line-segment background points')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) - - -class ChebyshevPolynomialBackground(BackgroundBase): - _description: str = 'Chebyshev polynomial background' - - @property - def _child_class(self) -> Type[PolynomialTerm]: - return PolynomialTerm - - def calculate(self, x_data: np.ndarray) -> np.ndarray: - """Evaluate polynomial background over x_data.""" - if not self._items: - print(warning('No background points found. Setting background to zero.')) - return np.zeros_like(x_data) - - u = (x_data - x_data.min()) / (x_data.max() - x_data.min()) * 2 - 1 # scale to [-1, 1] - coefs = [term.coef.value for term in self._items.values()] - y_data = chebval(u, coefs) - return y_data - - def show(self) -> None: - columns_headers: List[str] = ['Order', 'Coefficient'] - columns_alignment = ['left', 'left'] - columns_data: List[List[Union[int, float]]] = [] - for term in self._items.values(): - order = term.order.value - coef = term.coef.value - columns_data.append([order, coef]) - - print(paragraph('Chebyshev polynomial background terms')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) - - -class BackgroundTypeEnum(str, Enum): - LINE_SEGMENT = 'line-segment' - CHEBYSHEV = 'chebyshev polynomial' - - @classmethod - def default(cls) -> 'BackgroundTypeEnum': - return cls.LINE_SEGMENT - - def description(self) -> str: - if self is BackgroundTypeEnum.LINE_SEGMENT: - return 'Linear interpolation between points' - elif self is BackgroundTypeEnum.CHEBYSHEV: - return 'Chebyshev polynomial background' - - -class BackgroundFactory: - BT = BackgroundTypeEnum - _supported: Dict[BT, Type[BackgroundBase]] = { - BT.LINE_SEGMENT: LineSegmentBackground, - BT.CHEBYSHEV: ChebyshevPolynomialBackground, - } - - @classmethod - def create( - cls, - background_type: Optional[BackgroundTypeEnum] = None, - ) -> BackgroundBase: - if background_type is None: - background_type = BackgroundTypeEnum.default() - - if background_type not in cls._supported: - supported_types = list(cls._supported.keys()) - - raise ValueError( - f"Unsupported background type: '{background_type}'.\n" - f' Supported background types: {[bt.value for bt in supported_types]}' - ) - - background_class = cls._supported[background_type] - return background_class() diff --git a/src/easydiffraction/experiments/collections/excluded_regions.py b/src/easydiffraction/experiments/collections/excluded_regions.py deleted file mode 100644 index 68057e0a..00000000 --- a/src/easydiffraction/experiments/collections/excluded_regions.py +++ /dev/null @@ -1,98 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import List -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter -from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.utils import render_table - - -class ExcludedRegion(Component): - @property - def category_key(self) -> str: - return 'excluded_regions' - - @property - def cif_category_key(self) -> str: - return 'excluded_region' - - def __init__( - self, - start: float, - end: float, - ): - super().__init__() - - self.start = Descriptor( - value=start, - name='start', - cif_name='start', - description='Start of the excluded region.', - ) - self.end = Parameter( - value=end, - name='end', - cif_name='end', - description='End of the excluded region.', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = f'{start}-{end}' - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class ExcludedRegions(Collection): - """Collection of ExcludedRegion instances.""" - - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[ExcludedRegion]: - return ExcludedRegion - - def on_item_added(self, item: ExcludedRegion) -> None: - """Mark excluded points in the experiment pattern when a new - region is added. - """ - datastore = self._parent.datastore - - # Boolean mask for points within the new excluded region - in_region = (datastore.full_x >= item.start.value) & (datastore.full_x <= item.end.value) - - # Update the exclusion mask - datastore.excluded[in_region] = True - - # Update the excluded points in the datastore - datastore.x = datastore.full_x[~datastore.excluded] - datastore.meas = datastore.full_meas[~datastore.excluded] - datastore.meas_su = datastore.full_meas_su[~datastore.excluded] - - def show(self) -> None: - # TODO: Consider moving this to the base class - # to avoid code duplication with implementations in Background, - # etc. Consider using parameter names as column headers - columns_headers: List[str] = ['start', 'end'] - columns_alignment = ['left', 'left'] - columns_data: List[List[float]] = [] - for region in self._items.values(): - start = region.start.value - end = region.end.value - columns_data.append([start, end]) - - print(paragraph('Excluded regions')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) diff --git a/src/easydiffraction/experiments/collections/linked_phases.py b/src/easydiffraction/experiments/collections/linked_phases.py deleted file mode 100644 index ad29f203..00000000 --- a/src/easydiffraction/experiments/collections/linked_phases.py +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Type - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter - - -class LinkedPhase(Component): - @property - def category_key(self) -> str: - return 'linked_phases' - - @property - def cif_category_key(self) -> str: - return 'pd_phase_block' - - def __init__( - self, - id: str, - scale: float, - ): - super().__init__() - - self.id = Descriptor( - value=id, - name='id', - cif_name='id', - description='Identifier of the linked phase.', - ) - self.scale = Parameter( - value=scale, - name='scale', - cif_name='scale', - description='Scale factor of the linked phase.', - ) - - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = id - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class LinkedPhases(Collection): - """Collection of LinkedPhase instances.""" - - @property - def _type(self) -> str: - return 'category' # datablock or category - - @property - def _child_class(self) -> Type[LinkedPhase]: - return LinkedPhase diff --git a/src/easydiffraction/experiments/components/experiment_type.py b/src/easydiffraction/experiments/components/experiment_type.py deleted file mode 100644 index a467a43b..00000000 --- a/src/easydiffraction/experiments/components/experiment_type.py +++ /dev/null @@ -1,119 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from enum import Enum - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class SampleFormEnum(str, Enum): - POWDER = 'powder' - SINGLE_CRYSTAL = 'single crystal' - - @classmethod - def default(cls) -> 'SampleFormEnum': - return cls.POWDER - - def description(self) -> str: - if self is SampleFormEnum.POWDER: - return 'Powdered or polycrystalline sample.' - elif self is SampleFormEnum.SINGLE_CRYSTAL: - return 'Single crystal sample.' - - -class ScatteringTypeEnum(str, Enum): - BRAGG = 'bragg' - TOTAL = 'total' - - @classmethod - def default(cls) -> 'ScatteringTypeEnum': - return cls.BRAGG - - def description(self) -> str: - if self is ScatteringTypeEnum.BRAGG: - return 'Bragg diffraction for conventional structure refinement.' - elif self is ScatteringTypeEnum.TOTAL: - return 'Total scattering for pair distribution function analysis (PDF).' - - -class RadiationProbeEnum(str, Enum): - NEUTRON = 'neutron' - XRAY = 'xray' - - @classmethod - def default(cls) -> 'RadiationProbeEnum': - return cls.NEUTRON - - def description(self) -> str: - if self is RadiationProbeEnum.NEUTRON: - return 'Neutron diffraction.' - elif self is RadiationProbeEnum.XRAY: - return 'X-ray diffraction.' - - -class BeamModeEnum(str, Enum): - CONSTANT_WAVELENGTH = 'constant wavelength' - TIME_OF_FLIGHT = 'time-of-flight' - - @classmethod - def default(cls) -> 'BeamModeEnum': - return cls.CONSTANT_WAVELENGTH - - def description(self) -> str: - if self is BeamModeEnum.CONSTANT_WAVELENGTH: - return 'Constant wavelength (CW) diffraction.' - elif self is BeamModeEnum.TIME_OF_FLIGHT: - return 'Time-of-flight (TOF) diffraction.' - - -class ExperimentType(Component): - @property - def cif_category_key(self) -> str: - return 'expt_type' - - @property - def category_key(self) -> str: - return 'expt_type' - - def __init__( - self, - sample_form: str, - beam_mode: str, - radiation_probe: str, - scattering_type: str, - ): - super().__init__() - - self.sample_form: Descriptor = Descriptor( - value=sample_form, - name='sample_form', - cif_name='sample_form', - description='Specifies whether the diffraction data corresponds to ' - 'powder diffraction or single crystal diffraction', - ) - self.beam_mode: Descriptor = Descriptor( - value=beam_mode, - name='beam_mode', - cif_name='beam_mode', - description='Defines whether the measurement is performed with a ' - 'constant wavelength (CW) or time-of-flight (TOF) method', - ) - self.radiation_probe: Descriptor = Descriptor( - value=radiation_probe, - name='radiation_probe', - cif_name='radiation_probe', - description='Specifies whether the measurement uses neutrons or X-rays', - ) - self.scattering_type: Descriptor = Descriptor( - value=scattering_type, - name='scattering_type', - cif_name='scattering_type', - description='Specifies whether the experiment uses Bragg scattering ' - '(for conventional structure refinement) or total scattering ' - '(for pair distribution function analysis - PDF)', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True diff --git a/src/easydiffraction/experiments/components/instrument.py b/src/easydiffraction/experiments/components/instrument.py deleted file mode 100644 index 235fffae..00000000 --- a/src/easydiffraction/experiments/components/instrument.py +++ /dev/null @@ -1,141 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Optional - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Parameter -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum - - -class InstrumentBase(Component): - @property - def category_key(self) -> str: - return 'instrument' - - @property - def cif_category_key(self) -> str: - return 'instr' - - -class ConstantWavelengthInstrument(InstrumentBase): - def __init__( - self, - setup_wavelength: float = 1.5406, - calib_twotheta_offset: float = 0.0, - ) -> None: - super().__init__() - - self.setup_wavelength: Parameter = Parameter( - value=setup_wavelength, - name='wavelength', - cif_name='wavelength', - units='Å', - description='Incident neutron or X-ray wavelength', - ) - self.calib_twotheta_offset: Parameter = Parameter( - value=calib_twotheta_offset, - name='twotheta_offset', - cif_name='2theta_offset', - units='deg', - description='Instrument misalignment offset', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class TimeOfFlightInstrument(InstrumentBase): - def __init__( - self, - setup_twotheta_bank: float = 150.0, - calib_d_to_tof_offset: float = 0.0, - calib_d_to_tof_linear: float = 10000.0, - calib_d_to_tof_quad: float = -0.00001, - calib_d_to_tof_recip: float = 0.0, - ) -> None: - super().__init__() - - self.setup_twotheta_bank: Parameter = Parameter( - value=setup_twotheta_bank, - name='twotheta_bank', - cif_name='2theta_bank', - units='deg', - description='Detector bank position', - ) - self.calib_d_to_tof_offset: Parameter = Parameter( - value=calib_d_to_tof_offset, - name='d_to_tof_offset', - cif_name='d_to_tof_offset', - units='µs', - description='TOF offset', - ) - self.calib_d_to_tof_linear: Parameter = Parameter( - value=calib_d_to_tof_linear, - name='d_to_tof_linear', - cif_name='d_to_tof_linear', - units='µs/Å', - description='TOF linear conversion', - ) - self.calib_d_to_tof_quad: Parameter = Parameter( - value=calib_d_to_tof_quad, - name='d_to_tof_quad', - cif_name='d_to_tof_quad', - units='µs/Ų', - description='TOF quadratic correction', - ) - self.calib_d_to_tof_recip: Parameter = Parameter( - value=calib_d_to_tof_recip, - name='d_to_tof_recip', - cif_name='d_to_tof_recip', - units='µs·Å', - description='TOF reciprocal velocity correction', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class InstrumentFactory: - ST = ScatteringTypeEnum - BM = BeamModeEnum - _supported = { - ST.BRAGG: { - BM.CONSTANT_WAVELENGTH: ConstantWavelengthInstrument, - BM.TIME_OF_FLIGHT: TimeOfFlightInstrument, - } - } - - @classmethod - def create( - cls, - scattering_type: Optional[ScatteringTypeEnum] = None, - beam_mode: Optional[BeamModeEnum] = None, - ): - if beam_mode is None: - beam_mode = BeamModeEnum.default() - if scattering_type is None: - scattering_type = ScatteringTypeEnum.default() - - supported_scattering_types = list(cls._supported.keys()) - if scattering_type not in supported_scattering_types: - raise ValueError( - f"Unsupported scattering type: '{scattering_type}'.\n " - f'Supported scattering types: {supported_scattering_types}' - ) - - supported_beam_modes = list(cls._supported[scattering_type].keys()) - if beam_mode not in supported_beam_modes: - raise ValueError( - f"Unsupported beam mode: '{beam_mode}' for scattering type: " - f"'{scattering_type}'.\n " - f'Supported beam modes: {supported_beam_modes}' - ) - - instrument_class = cls._supported[scattering_type][beam_mode] - instrument_obj = instrument_class() - - return instrument_obj diff --git a/src/easydiffraction/experiments/components/peak.py b/src/easydiffraction/experiments/components/peak.py deleted file mode 100644 index 34c29b89..00000000 --- a/src/easydiffraction/experiments/components/peak.py +++ /dev/null @@ -1,466 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause -from enum import Enum -from typing import Optional - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Parameter -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum - - -class PeakProfileTypeEnum(str, Enum): - PSEUDO_VOIGT = 'pseudo-voigt' - SPLIT_PSEUDO_VOIGT = 'split pseudo-voigt' - THOMPSON_COX_HASTINGS = 'thompson-cox-hastings' - PSEUDO_VOIGT_IKEDA_CARPENTER = 'pseudo-voigt * ikeda-carpenter' - PSEUDO_VOIGT_BACK_TO_BACK = 'pseudo-voigt * back-to-back' - GAUSSIAN_DAMPED_SINC = 'gaussian-damped-sinc' - - @classmethod - def default( - cls, - scattering_type: ScatteringTypeEnum | None = None, - beam_mode: BeamModeEnum | None = None, - ) -> 'PeakProfileTypeEnum': - if scattering_type is None: - scattering_type = ScatteringTypeEnum.default() - if beam_mode is None: - beam_mode = BeamModeEnum.default() - - return { - ( - ScatteringTypeEnum.BRAGG, - BeamModeEnum.CONSTANT_WAVELENGTH, - ): cls.PSEUDO_VOIGT, - ( - ScatteringTypeEnum.BRAGG, - BeamModeEnum.TIME_OF_FLIGHT, - ): cls.PSEUDO_VOIGT_IKEDA_CARPENTER, - ( - ScatteringTypeEnum.TOTAL, - BeamModeEnum.CONSTANT_WAVELENGTH, - ): cls.GAUSSIAN_DAMPED_SINC, - ( - ScatteringTypeEnum.TOTAL, - BeamModeEnum.TIME_OF_FLIGHT, - ): cls.GAUSSIAN_DAMPED_SINC, - }[(scattering_type, beam_mode)] - - def description(self) -> str: - if self is PeakProfileTypeEnum.PSEUDO_VOIGT: - return 'Pseudo-Voigt profile' - elif self is PeakProfileTypeEnum.SPLIT_PSEUDO_VOIGT: - return 'Split pseudo-Voigt profile with empirical asymmetry correction.' - elif self is PeakProfileTypeEnum.THOMPSON_COX_HASTINGS: - return 'Thompson-Cox-Hastings profile with FCJ asymmetry correction.' - elif self is PeakProfileTypeEnum.PSEUDO_VOIGT_IKEDA_CARPENTER: - return 'Pseudo-Voigt profile with Ikeda-Carpenter asymmetry correction.' - elif self is PeakProfileTypeEnum.PSEUDO_VOIGT_BACK_TO_BACK: - return 'Pseudo-Voigt profile with Back-to-Back Exponential asymmetry correction.' - elif self is PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC: - return 'Gaussian-damped sinc profile for pair distribution function (PDF) analysis.' - - -# --- Mixins --- -class ConstantWavelengthBroadeningMixin: - def _add_constant_wavelength_broadening(self) -> None: - self.broad_gauss_u: Parameter = Parameter( - value=0.01, - name='broad_gauss_u', - cif_name='broad_gauss_u', - units='deg²', - description='Gaussian broadening coefficient (dependent on ' - 'sample size and instrument resolution)', - ) - self.broad_gauss_v: Parameter = Parameter( - value=-0.01, - name='broad_gauss_v', - cif_name='broad_gauss_v', - units='deg²', - description='Gaussian broadening coefficient (instrumental broadening contribution)', - ) - self.broad_gauss_w: Parameter = Parameter( - value=0.02, - name='broad_gauss_w', - cif_name='broad_gauss_w', - units='deg²', - description='Gaussian broadening coefficient (instrumental broadening contribution)', - ) - self.broad_lorentz_x: Parameter = Parameter( - value=0.0, - name='broad_lorentz_x', - cif_name='broad_lorentz_x', - units='deg', - description='Lorentzian broadening coefficient (dependent on sample strain effects)', - ) - self.broad_lorentz_y: Parameter = Parameter( - value=0.0, - name='broad_lorentz_y', - cif_name='broad_lorentz_y', - units='deg', - description='Lorentzian broadening coefficient (dependent on ' - 'microstructural defects and strain)', - ) - - -class TimeOfFlightBroadeningMixin: - def _add_time_of_flight_broadening(self) -> None: - self.broad_gauss_sigma_0: Parameter = Parameter( - value=0.0, - name='gauss_sigma_0', - cif_name='gauss_sigma_0', - units='µs²', - description='Gaussian broadening coefficient (instrumental resolution)', - ) - self.broad_gauss_sigma_1: Parameter = Parameter( - value=0.0, - name='gauss_sigma_1', - cif_name='gauss_sigma_1', - units='µs/Å', - description='Gaussian broadening coefficient (dependent on d-spacing)', - ) - self.broad_gauss_sigma_2: Parameter = Parameter( - value=0.0, - name='gauss_sigma_2', - cif_name='gauss_sigma_2', - units='µs²/Ų', - description='Gaussian broadening coefficient (instrument-dependent term)', - ) - self.broad_lorentz_gamma_0: Parameter = Parameter( - value=0.0, - name='lorentz_gamma_0', - cif_name='lorentz_gamma_0', - units='µs', - description='Lorentzian broadening coefficient (dependent on microstrain effects)', - ) - self.broad_lorentz_gamma_1: Parameter = Parameter( - value=0.0, - name='lorentz_gamma_1', - cif_name='lorentz_gamma_1', - units='µs/Å', - description='Lorentzian broadening coefficient (dependent on d-spacing)', - ) - self.broad_lorentz_gamma_2: Parameter = Parameter( - value=0.0, - name='lorentz_gamma_2', - cif_name='lorentz_gamma_2', - units='µs²/Ų', - description='Lorentzian broadening coefficient (instrumental-dependent term)', - ) - self.broad_mix_beta_0: Parameter = Parameter( - value=0.0, - name='mix_beta_0', - cif_name='mix_beta_0', - units='deg', - description='Mixing parameter. Defines the ratio of Gaussian ' - 'to Lorentzian contributions in TOF profiles', - ) - self.broad_mix_beta_1: Parameter = Parameter( - value=0.0, - name='mix_beta_1', - cif_name='mix_beta_1', - units='deg', - description='Mixing parameter. Defines the ratio of Gaussian ' - 'to Lorentzian contributions in TOF profiles', - ) - - -class EmpiricalAsymmetryMixin: - def _add_empirical_asymmetry(self) -> None: - self.asym_empir_1: Parameter = Parameter( - value=0.1, - name='asym_empir_1', - cif_name='asym_empir_1', - units='', - description='Empirical asymmetry coefficient p1', - ) - self.asym_empir_2: Parameter = Parameter( - value=0.2, - name='asym_empir_2', - cif_name='asym_empir_2', - units='', - description='Empirical asymmetry coefficient p2', - ) - self.asym_empir_3: Parameter = Parameter( - value=0.3, - name='asym_empir_3', - cif_name='asym_empir_3', - units='', - description='Empirical asymmetry coefficient p3', - ) - self.asym_empir_4: Parameter = Parameter( - value=0.4, - name='asym_empir_4', - cif_name='asym_empir_4', - units='', - description='Empirical asymmetry coefficient p4', - ) - - -class FcjAsymmetryMixin: - def _add_fcj_asymmetry(self) -> None: - self.asym_fcj_1: Parameter = Parameter( - value=0.01, - name='asym_fcj_1', - cif_name='asym_fcj_1', - units='', - description='FCJ asymmetry coefficient 1', - ) - self.asym_fcj_2: Parameter = Parameter( - value=0.02, - name='asym_fcj_2', - cif_name='asym_fcj_2', - units='', - description='FCJ asymmetry coefficient 2', - ) - - -class IkedaCarpenterAsymmetryMixin: - def _add_ikeda_carpenter_asymmetry(self) -> None: - self.asym_alpha_0: Parameter = Parameter( - value=0.01, - name='asym_alpha_0', - cif_name='asym_alpha_0', - units='', - description='Ikeda-Carpenter asymmetry parameter α₀', - ) - self.asym_alpha_1: Parameter = Parameter( - value=0.02, - name='asym_alpha_1', - cif_name='asym_alpha_1', - units='', - description='Ikeda-Carpenter asymmetry parameter α₁', - ) - - -class PairDistributionFunctionBroadeningMixin: - def _add_pair_distribution_function_broadening(self): - self.damp_q = Parameter( - value=0.05, - name='damp_q', - cif_name='damp_q', - units='Å⁻¹', - description='Instrumental Q-resolution damping factor ' - '(affects high-r PDF peak amplitude)', - ) - self.broad_q = Parameter( - value=0.0, - name='broad_q', - cif_name='broad_q', - units='Å⁻²', - description='Quadratic PDF peak broadening coefficient ' - '(thermal and model uncertainty contribution)', - ) - self.cutoff_q = Parameter( - value=25.0, - name='cutoff_q', - cif_name='cutoff_q', - units='Å⁻¹', - description='Q-value cutoff applied to model PDF for Fourier ' - 'transform (controls real-space resolution)', - ) - self.sharp_delta_1 = Parameter( - value=0.0, - name='sharp_delta_1', - cif_name='sharp_delta_1', - units='Å', - description='PDF peak sharpening coefficient (1/r dependence)', - ) - self.sharp_delta_2 = Parameter( - value=0.0, - name='sharp_delta_2', - cif_name='sharp_delta_2', - units='Ų', - description='PDF peak sharpening coefficient (1/r² dependence)', - ) - self.damp_particle_diameter = Parameter( - value=0.0, - name='damp_particle_diameter', - cif_name='damp_particle_diameter', - units='Å', - description='Particle diameter for spherical envelope damping correction in PDF', - ) - - -# --- Base peak class --- -class PeakBase(Component): - @property - def category_key(self) -> str: - return 'peak' - - @property - def cif_category_key(self) -> str: - return 'peak' - - -# --- Derived peak classes --- -class ConstantWavelengthPseudoVoigt( - PeakBase, - ConstantWavelengthBroadeningMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_constant_wavelength_broadening() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class ConstantWavelengthSplitPseudoVoigt( - PeakBase, - ConstantWavelengthBroadeningMixin, - EmpiricalAsymmetryMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_constant_wavelength_broadening() - self._add_empirical_asymmetry() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class ConstantWavelengthThompsonCoxHastings( - PeakBase, - ConstantWavelengthBroadeningMixin, - FcjAsymmetryMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_constant_wavelength_broadening() - self._add_fcj_asymmetry() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class TimeOfFlightPseudoVoigt( - PeakBase, - TimeOfFlightBroadeningMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_time_of_flight_broadening() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class TimeOfFlightPseudoVoigtIkedaCarpenter( - PeakBase, - TimeOfFlightBroadeningMixin, - IkedaCarpenterAsymmetryMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_time_of_flight_broadening() - self._add_ikeda_carpenter_asymmetry() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class TimeOfFlightPseudoVoigtBackToBack( - PeakBase, - TimeOfFlightBroadeningMixin, - IkedaCarpenterAsymmetryMixin, -): - def __init__(self) -> None: - super().__init__() - - self._add_time_of_flight_broadening() - self._add_ikeda_carpenter_asymmetry() - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True - - -class PairDistributionFunctionGaussianDampedSinc( - PeakBase, - PairDistributionFunctionBroadeningMixin, -): - def __init__(self): - super().__init__() - self._add_pair_distribution_function_broadening() - self._locked = True # Lock further attribute additions - - -# --- Peak factory --- -class PeakFactory: - ST = ScatteringTypeEnum - BM = BeamModeEnum - PPT = PeakProfileTypeEnum - _supported = { - ST.BRAGG: { - BM.CONSTANT_WAVELENGTH: { - PPT.PSEUDO_VOIGT: ConstantWavelengthPseudoVoigt, - PPT.SPLIT_PSEUDO_VOIGT: ConstantWavelengthSplitPseudoVoigt, - PPT.THOMPSON_COX_HASTINGS: ConstantWavelengthThompsonCoxHastings, - }, - BM.TIME_OF_FLIGHT: { - PPT.PSEUDO_VOIGT: TimeOfFlightPseudoVoigt, - PPT.PSEUDO_VOIGT_IKEDA_CARPENTER: TimeOfFlightPseudoVoigtIkedaCarpenter, - PPT.PSEUDO_VOIGT_BACK_TO_BACK: TimeOfFlightPseudoVoigtBackToBack, - }, - }, - ST.TOTAL: { - BM.CONSTANT_WAVELENGTH: { - PPT.GAUSSIAN_DAMPED_SINC: PairDistributionFunctionGaussianDampedSinc, - }, - BM.TIME_OF_FLIGHT: { - PPT.GAUSSIAN_DAMPED_SINC: PairDistributionFunctionGaussianDampedSinc, - }, - }, - } - - @classmethod - def create( - cls, - scattering_type: Optional[ScatteringTypeEnum] = None, - beam_mode: Optional[BeamModeEnum] = None, - profile_type: Optional[PeakProfileTypeEnum] = None, - ): - if beam_mode is None: - beam_mode = BeamModeEnum.default() - if scattering_type is None: - scattering_type = ScatteringTypeEnum.default() - if profile_type is None: - profile_type = PeakProfileTypeEnum.default(scattering_type, beam_mode) - - supported_scattering_types = list(cls._supported.keys()) - if scattering_type not in supported_scattering_types: - raise ValueError( - f"Unsupported scattering type: '{scattering_type}'.\n" - f'Supported scattering types: {supported_scattering_types}' - ) - - supported_beam_modes = list(cls._supported[scattering_type].keys()) - if beam_mode not in supported_beam_modes: - raise ValueError( - f"Unsupported beam mode: '{beam_mode}' for scattering type: " - f"'{scattering_type}'.\n Supported beam modes: '{supported_beam_modes}'" - ) - - supported_profile_types = list(cls._supported[scattering_type][beam_mode].keys()) - if profile_type not in supported_profile_types: - raise ValueError( - f"Unsupported profile type '{profile_type}' for beam mode '{beam_mode}'.\n" - f'Supported profile types: {supported_profile_types}' - ) - - peak_class = cls._supported[scattering_type][beam_mode][profile_type] - peak_obj = peak_class() - - return peak_obj diff --git a/src/easydiffraction/experiments/datastore.py b/src/easydiffraction/experiments/datastore.py deleted file mode 100644 index e957b122..00000000 --- a/src/easydiffraction/experiments/datastore.py +++ /dev/null @@ -1,254 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from __future__ import annotations - -from abc import abstractmethod -from typing import Optional - -import numpy as np - -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import SampleFormEnum -from easydiffraction.utils.decorators import enforce_type - - -class BaseDatastore: - """Base class for all data stores. - - Attributes: - meas (Optional[np.ndarray]): Measured intensities. - meas_su (Optional[np.ndarray]): Standard uncertainties of - measured intensities. - excluded (Optional[np.ndarray]): Flags for excluded points. - _calc (Optional[np.ndarray]): Stored calculated intensities. - """ - - def __init__(self) -> None: - self.meas: Optional[np.ndarray] = None - self.meas_su: Optional[np.ndarray] = None - self.excluded: Optional[np.ndarray] = None - self._calc: Optional[np.ndarray] = None - - @property - def calc(self) -> Optional[np.ndarray]: - """Access calculated intensities. Should be updated via external - calculation. - - Returns: - Optional[np.ndarray]: Calculated intensities array or None - if not set. - """ - return self._calc - - @calc.setter - @enforce_type - def calc(self, values: np.ndarray) -> None: - """Set calculated intensities (from - Analysis.calculate_pattern()). - - Args: - values (np.ndarray): Array of calculated intensities. - """ - self._calc = values - - @abstractmethod - def _cif_mapping(self) -> dict[str, str]: - """Must be implemented in subclasses to return a mapping from - attribute names to CIF tags. - - Returns: - dict[str, str]: Mapping from attribute names to CIF tags. - """ - pass - - def as_cif(self, max_points: Optional[int] = None) -> str: - """Generate a CIF-formatted string representing the datastore - data. - - Args: - max_points (Optional[int]): Maximum number of points to - include from start and end. If the total points exceed - twice this number, data in the middle is truncated with - '...'. - - Returns: - str: CIF-formatted string of the data. Empty string if no - data available. - """ - cif_lines = ['loop_'] - - # Add CIF tags from mapping - mapping = self._cif_mapping() - for cif_key in mapping.values(): - cif_lines.append(cif_key) - - # Collect data arrays according to mapping keys - data_arrays = [] - for attr_name in mapping: - attr_array = getattr(self, attr_name, None) - if attr_array is None: - data_arrays.append(np.array([])) - else: - data_arrays.append(attr_array) - - # Return empty string if no data - if not data_arrays or not data_arrays[0].size: - return '' - - # Determine number of points in the first data array - n_points = len(data_arrays[0]) - - # Function to format a single row of data - def _format_row(i: int) -> str: - return ' '.join(str(data_arrays[j][i]) for j in range(len(data_arrays))) - - # Add data lines, applying max_points truncation if needed - if max_points is not None and n_points > 2 * max_points: - for i in range(max_points): - cif_lines.append(_format_row(i)) - cif_lines.append('...') - for i in range(-max_points, 0): - cif_lines.append(_format_row(i)) - else: - for i in range(n_points): - cif_lines.append(_format_row(i)) - - cif_str = '\n'.join(cif_lines) - - return cif_str - - -class PowderDatastore(BaseDatastore): - """Class for powder diffraction data. - - Attributes: - x (Optional[np.ndarray]): Scan variable (e.g. 2θ or - time-of-flight values). - d (Optional[np.ndarray]): d-spacing values. - bkg (Optional[np.ndarray]): Background values. - """ - - def __init__(self, beam_mode: Optional[BeamModeEnum] = None) -> None: - """Initialize PowderDatastore. - - Args: - beam_mode (str): Beam mode, e.g. 'time-of-flight' or - 'constant wavelength'. - """ - super().__init__() - - if beam_mode is None: - beam_mode = BeamModeEnum.default() - - self.beam_mode = beam_mode - self.x: Optional[np.ndarray] = None - self.d: Optional[np.ndarray] = None - self.bkg: Optional[np.ndarray] = None - - def _cif_mapping(self) -> dict[str, str]: - """Return mapping from attribute names to CIF tags based on beam - mode. - - Returns: - dict[str, str]: Mapping dictionary. - """ - # TODO: Decide where to have validation for beam_mode, - # here or in Experiment class or somewhere else. - return { - 'time-of-flight': { - 'x': '_pd_meas.time_of_flight', - 'meas': '_pd_meas.intensity_total', - 'meas_su': '_pd_meas.intensity_total_su', - }, - 'constant wavelength': { - 'x': '_pd_meas.2theta_scan', - 'meas': '_pd_meas.intensity_total', - 'meas_su': '_pd_meas.intensity_total_su', - }, - }[self.beam_mode] - - -class SingleCrystalDatastore(BaseDatastore): - """Class for single crystal diffraction data. - - Attributes: - sin_theta_over_lambda (Optional[np.ndarray]): sin(θ)/λ values. - index_h (Optional[np.ndarray]): Miller index h. - index_k (Optional[np.ndarray]): Miller index k. - index_l (Optional[np.ndarray]): Miller index l. - """ - - def __init__(self) -> None: - """Initialize SingleCrystalDatastore.""" - super().__init__() - self.sin_theta_over_lambda: Optional[np.ndarray] = None - self.index_h: Optional[np.ndarray] = None - self.index_k: Optional[np.ndarray] = None - self.index_l: Optional[np.ndarray] = None - - def _cif_mapping(self) -> dict[str, str]: - """Return mapping from attribute names to CIF tags for single - crystal data. - - Returns: - dict[str, str]: Mapping dictionary. - """ - return { - 'index_h': '_refln.index_h', - 'index_k': '_refln.index_k', - 'index_l': '_refln.index_l', - 'meas': '_refln.intensity_meas', - 'meas_su': '_refln.intensity_meas_su', - } - - -class DatastoreFactory: - _supported = { - 'powder': PowderDatastore, - 'single crystal': SingleCrystalDatastore, - } - - @classmethod - def create( - cls, - sample_form: str = SampleFormEnum.default(), - beam_mode: str = BeamModeEnum.default(), - ) -> BaseDatastore: - """Create and return a datastore object for the given sample - form. - - Args: - sample_form (str): Sample form type, e.g. 'powder' or - 'single crystal'. - beam_mode (str): Beam mode for powder sample form. - - Returns: - BaseDatastore: Instance of a datastore class corresponding - to sample form. - - Raises: - ValueError: If the sample_form or beam_mode is not - supported. - """ - supported_sample_forms = list(cls._supported.keys()) - if sample_form not in supported_sample_forms: - raise ValueError( - f"Unsupported sample form: '{sample_form}'.\n" - f'Supported sample forms: {supported_sample_forms}' - ) - - supported_beam_modes = ['time-of-flight', 'constant wavelength'] - if beam_mode not in supported_beam_modes: - raise ValueError( - f"Unsupported beam mode: '{beam_mode}'.\n" - f'Supported beam modes: {supported_beam_modes}' - ) - - datastore_class = cls._supported[sample_form] - if sample_form == 'powder': - datastore_obj = datastore_class(beam_mode=beam_mode) - else: - datastore_obj = datastore_class() - - return datastore_obj diff --git a/src/easydiffraction/experiments/datastore/__init__.py b/src/easydiffraction/experiments/datastore/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/experiments/datastore/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/experiments/datastore/base.py b/src/easydiffraction/experiments/datastore/base.py new file mode 100644 index 00000000..43d8a46b --- /dev/null +++ b/src/easydiffraction/experiments/datastore/base.py @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING +from typing import Optional + +from typeguard import typechecked + +from easydiffraction.io.cif.serialize import datastore_to_cif + +if TYPE_CHECKING: + import numpy as np + + +class DatastoreBase: + """Base class for all data stores. + + Attributes: + meas (Optional[np.ndarray]): Measured intensities. + meas_su (Optional[np.ndarray]): Standard uncertainties of + measured intensities. + excluded (Optional[np.ndarray]): Flags for excluded points. + _calc (Optional[np.ndarray]): Stored calculated intensities. + """ + + def __init__(self) -> None: + self.meas: Optional[np.ndarray] = None + self.meas_su: Optional[np.ndarray] = None + self.excluded: Optional[np.ndarray] = None + self._calc: Optional[np.ndarray] = None + + @property + def calc(self) -> Optional[np.ndarray]: + """Access calculated intensities. Should be updated via external + calculation. + + Returns: + Optional[np.ndarray]: Calculated intensities array or None + if not set. + """ + return self._calc + + @calc.setter + @typechecked + def calc(self, values: np.ndarray) -> None: + """Set calculated intensities (from + Analysis.calculate_pattern()). + + Args: + values (np.ndarray): Array of calculated intensities. + """ + self._calc = values + + @abstractmethod + def _cif_mapping(self) -> dict[str, str]: + """Must be implemented in subclasses to return a mapping from + attribute names to CIF tags. + + Returns: + dict[str, str]: Mapping from attribute names to CIF tags. + """ + pass + + @property + def as_cif(self) -> str: + """Generate a CIF-formatted string representing the datastore + data. + """ + return datastore_to_cif(self) + + @property + def as_truncated_cif(self) -> str: + """Generate a CIF-formatted string representing the datastore + data. + """ + return datastore_to_cif(self, max_points=5) diff --git a/src/easydiffraction/experiments/datastore/factory.py b/src/easydiffraction/experiments/datastore/factory.py new file mode 100644 index 00000000..bf9842aa --- /dev/null +++ b/src/easydiffraction/experiments/datastore/factory.py @@ -0,0 +1,70 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for experiment datastores based on sample form and beam +mode. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from easydiffraction.experiments.datastore.pd import PdDatastore +from easydiffraction.experiments.datastore.sc import ScDatastore +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum + +if TYPE_CHECKING: + from easydiffraction.experiments.datastore.base import DatastoreBase + + +class DatastoreFactory: + """Create PD or SC datastores depending on sample form.""" + + _supported = { + 'powder': PdDatastore, + 'single crystal': ScDatastore, + } + + @classmethod + def create( + cls, + sample_form: str = SampleFormEnum.default(), + beam_mode: str = BeamModeEnum.default(), + ) -> DatastoreBase: + """Create and return a datastore object for the given sample + form. + + Args: + sample_form (str): Sample form type, e.g. 'powder' or + 'single crystal'. + beam_mode (str): Beam mode for powder sample form. + + Returns: + DatastoreBase: Instance of a datastore class corresponding + to sample form. + + Raises: + ValueError: If the sample_form or beam_mode is not + supported. + """ + supported_sample_forms = list(cls._supported.keys()) + if sample_form not in supported_sample_forms: + raise ValueError( + f"Unsupported sample form: '{sample_form}'.\n" + f'Supported sample forms: {supported_sample_forms}' + ) + + supported_beam_modes = ['time-of-flight', 'constant wavelength'] + if beam_mode not in supported_beam_modes: + raise ValueError( + f"Unsupported beam mode: '{beam_mode}'.\n" + f'Supported beam modes: {supported_beam_modes}' + ) + + datastore_class = cls._supported[sample_form] + if sample_form == 'powder': + datastore_obj = datastore_class(beam_mode=beam_mode) + else: + datastore_obj = datastore_class() + + return datastore_obj diff --git a/src/easydiffraction/experiments/datastore/pd.py b/src/easydiffraction/experiments/datastore/pd.py new file mode 100644 index 00000000..1939f70a --- /dev/null +++ b/src/easydiffraction/experiments/datastore/pd.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Optional + +from easydiffraction.experiments.datastore.base import DatastoreBase +from easydiffraction.experiments.experiment.enums import BeamModeEnum + +if TYPE_CHECKING: + import numpy as np + + +class PdDatastore(DatastoreBase): + """Class for powder diffraction data. + + Attributes: + x (Optional[np.ndarray]): Scan variable (e.g. 2θ or + time-of-flight values). + d (Optional[np.ndarray]): d-spacing values. + bkg (Optional[np.ndarray]): Background values. + """ + + def __init__(self, beam_mode: Optional[BeamModeEnum] = None) -> None: + """Initialize PowderDatastore. + + Args: + beam_mode (str): Beam mode, e.g. 'time-of-flight' or + 'constant wavelength'. + """ + super().__init__() + + if beam_mode is None: + beam_mode = BeamModeEnum.default() + + self.beam_mode = beam_mode + self.x: Optional[np.ndarray] = None + self.d: Optional[np.ndarray] = None + self.bkg: Optional[np.ndarray] = None + + def _cif_mapping(self) -> dict[str, str]: + """Return mapping from attribute names to CIF tags based on beam + mode. + + Returns: + dict[str, str]: Mapping dictionary. + """ + # TODO: Decide where to have validation for beam_mode, + # here or in Experiment class or somewhere else. + return { + 'time-of-flight': { + 'x': '_pd_meas.time_of_flight', + 'meas': '_pd_meas.intensity_total', + 'meas_su': '_pd_meas.intensity_total_su', + }, + 'constant wavelength': { + 'x': '_pd_meas.2theta_scan', + 'meas': '_pd_meas.intensity_total', + 'meas_su': '_pd_meas.intensity_total_su', + }, + }[self.beam_mode] diff --git a/src/easydiffraction/experiments/datastore/sc.py b/src/easydiffraction/experiments/datastore/sc.py new file mode 100644 index 00000000..7182782b --- /dev/null +++ b/src/easydiffraction/experiments/datastore/sc.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Optional + +from easydiffraction.experiments.datastore.base import DatastoreBase + +if TYPE_CHECKING: + import numpy as np + + +class ScDatastore(DatastoreBase): + """Class for single crystal diffraction data. + + Attributes: + sin_theta_over_lambda (Optional[np.ndarray]): sin(θ)/λ values. + index_h (Optional[np.ndarray]): Miller index h. + index_k (Optional[np.ndarray]): Miller index k. + index_l (Optional[np.ndarray]): Miller index l. + """ + + def __init__(self) -> None: + """Initialize SingleCrystalDatastore.""" + super().__init__() + self.sin_theta_over_lambda: Optional[np.ndarray] = None + self.index_h: Optional[np.ndarray] = None + self.index_k: Optional[np.ndarray] = None + self.index_l: Optional[np.ndarray] = None + + def _cif_mapping(self) -> dict[str, str]: + """Return mapping from attribute names to CIF tags for single + crystal data. + + Returns: + dict[str, str]: Mapping dictionary. + """ + return { + 'index_h': '_refln.index_h', + 'index_k': '_refln.index_k', + 'index_l': '_refln.index_l', + 'meas': '_refln.intensity_meas', + 'meas_su': '_refln.intensity_meas_su', + } diff --git a/src/easydiffraction/experiments/experiment.py b/src/easydiffraction/experiments/experiment.py deleted file mode 100644 index 8abb80f7..00000000 --- a/src/easydiffraction/experiments/experiment.py +++ /dev/null @@ -1,572 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from abc import abstractmethod -from typing import List -from typing import Optional - -import numpy as np - -from easydiffraction.core.objects import Datablock -from easydiffraction.experiments.collections.background import BackgroundFactory -from easydiffraction.experiments.collections.background import BackgroundTypeEnum -from easydiffraction.experiments.collections.excluded_regions import ExcludedRegions -from easydiffraction.experiments.collections.linked_phases import LinkedPhases -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ExperimentType -from easydiffraction.experiments.components.experiment_type import RadiationProbeEnum -from easydiffraction.experiments.components.experiment_type import SampleFormEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum -from easydiffraction.experiments.components.instrument import InstrumentBase -from easydiffraction.experiments.components.instrument import InstrumentFactory -from easydiffraction.experiments.components.peak import PeakFactory -from easydiffraction.experiments.components.peak import PeakProfileTypeEnum -from easydiffraction.experiments.datastore import DatastoreFactory -from easydiffraction.utils.decorators import enforce_type -from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.formatting import warning -from easydiffraction.utils.utils import render_cif -from easydiffraction.utils.utils import render_table - - -class InstrumentMixin: - def __init__(self, *args, **kwargs): - expt_type = kwargs.get('type') - super().__init__(*args, **kwargs) - self._instrument = InstrumentFactory.create( - scattering_type=expt_type.scattering_type.value, - beam_mode=expt_type.beam_mode.value, - ) - - @property - def instrument(self): - return self._instrument - - @instrument.setter - @enforce_type - def instrument(self, new_instrument: InstrumentBase): - self._instrument = new_instrument - - -class BaseExperiment(Datablock): - """Base class for all experiments with only core attributes. - - Wraps experiment type, instrument and datastore. - """ - - # TODO: Find better name for the attribute 'type'. - # 1. It shadows the built-in type() function. - # 2. It is not very clear what it refers to. - def __init__(self, name: str, type: ExperimentType): - self.name = name - self.type = type - self.datastore = DatastoreFactory.create( - sample_form=self.type.sample_form.value, - beam_mode=self.type.beam_mode.value, - ) - - # --------------- - # Experiment type - # --------------- - - @property - def type(self): - return self._type - - @type.setter - @enforce_type - def type(self, new_experiment_type: ExperimentType): - self._type = new_experiment_type - - # ---------------- - # Misc. Need to be sorted - # ---------------- - - def as_cif( - self, - max_points: Optional[int] = None, - ) -> str: - """Export the sample model to CIF format. - - Returns: - str: CIF string representation of the experiment. - """ - # Data block header - cif_lines: List[str] = [f'data_{self.name}'] - - # Experiment type - cif_lines += ['', self.type.as_cif()] - - # Instrument setup and calibration - if hasattr(self, 'instrument'): - cif_lines += ['', self.instrument.as_cif()] - - # Peak profile, broadening and asymmetry - if hasattr(self, 'peak'): - cif_lines += ['', self.peak.as_cif()] - - # Phase scale factors for powder experiments - if hasattr(self, 'linked_phases') and self.linked_phases._items: - cif_lines += ['', self.linked_phases.as_cif()] - - # Crystal scale factor for single crystal experiments - if hasattr(self, 'linked_crystal'): - cif_lines += ['', self.linked_crystal.as_cif()] - - # Background points - if hasattr(self, 'background') and self.background._items: - cif_lines += ['', self.background.as_cif()] - - # Excluded regions - if hasattr(self, 'excluded_regions') and self.excluded_regions._items: - cif_lines += ['', self.excluded_regions.as_cif()] - - # Measured data - if hasattr(self, 'datastore'): - cif_lines += ['', self.datastore.as_cif(max_points=max_points)] - - return '\n'.join(cif_lines) - - def show_as_cif(self) -> None: - cif_text: str = self.as_cif(max_points=5) - paragraph_title: str = paragraph(f"Experiment 🔬 '{self.name}' as cif") - render_cif(cif_text, paragraph_title) - - @abstractmethod - def _load_ascii_data_to_experiment(self, data_path: str) -> None: - pass - - -class BasePowderExperiment(BaseExperiment): - """Base class for all powder experiments.""" - - def __init__( - self, - name: str, - type: ExperimentType, - ) -> None: - super().__init__(name=name, type=type) - - self._peak_profile_type: str = PeakProfileTypeEnum.default( - self.type.scattering_type.value, - self.type.beam_mode.value, - ).value - self.peak = PeakFactory.create( - scattering_type=self.type.scattering_type.value, - beam_mode=self.type.beam_mode.value, - profile_type=self._peak_profile_type, - ) - - self.linked_phases: LinkedPhases = LinkedPhases() - self.excluded_regions: ExcludedRegions = ExcludedRegions(parent=self) - - @abstractmethod - def _load_ascii_data_to_experiment(self, data_path: str) -> None: - pass - - @property - def peak_profile_type(self): - return self._peak_profile_type - - @peak_profile_type.setter - def peak_profile_type(self, new_type: str): - if ( - new_type - not in PeakFactory._supported[self.type.scattering_type.value][ - self.type.beam_mode.value - ] - ): - supported_types = list( - PeakFactory._supported[self.type.scattering_type.value][ - self.type.beam_mode.value - ].keys() - ) - print(warning(f"Unsupported peak profile '{new_type}'")) - print(f'Supported peak profiles: {supported_types}') - print("For more information, use 'show_supported_peak_profile_types()'") - return - self.peak = PeakFactory.create( - scattering_type=self.type.scattering_type.value, - beam_mode=self.type.beam_mode.value, - profile_type=new_type, - ) - self._peak_profile_type = new_type - print(paragraph(f"Peak profile type for experiment '{self.name}' changed to")) - print(new_type) - - def show_supported_peak_profile_types(self): - columns_headers = ['Peak profile type', 'Description'] - columns_alignment = ['left', 'left'] - columns_data = [] - - scattering_type = self.type.scattering_type.value - beam_mode = self.type.beam_mode.value - - for profile_type in PeakFactory._supported[scattering_type][beam_mode]: - columns_data.append([profile_type.value, profile_type.description()]) - - print(paragraph('Supported peak profile types')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) - - def show_current_peak_profile_type(self): - print(paragraph('Current peak profile type')) - print(self.peak_profile_type) - - -class PowderExperiment( - InstrumentMixin, - BasePowderExperiment, -): - """Powder experiment class with specific attributes. - - Wraps background, peak profile, and linked phases. - """ - - def __init__( - self, - name: str, - type: ExperimentType, - ) -> None: - super().__init__(name=name, type=type) - - self._background_type: BackgroundTypeEnum = BackgroundTypeEnum.default() - self.background = BackgroundFactory.create(background_type=self.background_type) - - # ------------- - # Measured data - # ------------- - - def _load_ascii_data_to_experiment(self, data_path: str) -> None: - """Loads x, y, sy values from an ASCII data file into the - experiment. - - The file must be structured as: - x y sy - """ - try: - data = np.loadtxt(data_path) - except Exception as e: - raise IOError(f'Failed to read data from {data_path}: {e}') from e - - if data.shape[1] < 2: - raise ValueError('Data file must have at least two columns: x and y.') - - if data.shape[1] < 3: - print('Warning: No uncertainty (sy) column provided. Defaulting to sqrt(y).') - - # Extract x, y data - x: np.ndarray = data[:, 0] - y: np.ndarray = data[:, 1] - - # Round x to 4 decimal places - # TODO: This is needed for CrysPy, as otherwise it fails to - # match the size of the data arrays. - x = np.round(x, 4) - - # Determine sy from column 3 if available, otherwise use sqrt(y) - sy: np.ndarray = data[:, 2] if data.shape[1] > 2 else np.sqrt(y) - - # Replace values smaller than 0.0001 with 1.0 - # TODO: This is needed for minimization algorithms that fail - # with very small or zero uncertainties. - sy = np.where(sy < 0.0001, 1.0, sy) - - # Attach the data to the experiment's datastore - - # The full pattern data - self.datastore.full_x = x - self.datastore.full_meas = y - self.datastore.full_meas_su = sy - - # The pattern data used for fitting (without excluded points) - # This is the same as full_x, full_meas, full_meas_su by default - self.datastore.x = x - self.datastore.meas = y - self.datastore.meas_su = sy - - # Excluded mask - # No excluded points by default - self.datastore.excluded = np.full(x.shape, fill_value=False, dtype=bool) - - print(paragraph('Data loaded successfully')) - print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") - - @property - def background_type(self): - return self._background_type - - @background_type.setter - def background_type(self, new_type): - if new_type not in BackgroundFactory._supported: - supported_types = list(BackgroundFactory._supported.keys()) - print(warning(f"Unknown background type '{new_type}'")) - print(f'Supported background types: {supported_types}') - print("For more information, use 'show_supported_background_types()'") - return - self.background = BackgroundFactory.create(new_type) - self._background_type = new_type - print(paragraph(f"Background type for experiment '{self.name}' changed to")) - print(new_type) - - def show_supported_background_types(self): - columns_headers = ['Background type', 'Description'] - columns_alignment = ['left', 'left'] - columns_data = [] - for bt in BackgroundFactory._supported: - columns_data.append([bt.value, bt.description()]) - - print(paragraph('Supported background types')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=columns_data, - ) - - def show_current_background_type(self): - print(paragraph('Current background type')) - print(self.background_type) - - -# TODO: Refactor this class to reuse PowderExperiment -# TODO: This is not a specific experiment, but rather processed data -# from PowderExperiment. So, we should think of a better design. -class PairDistributionFunctionExperiment(BasePowderExperiment): - """PDF experiment class with specific attributes.""" - - def __init__( - self, - name: str, - type: ExperimentType, - ): - super().__init__(name=name, type=type) - - def _load_ascii_data_to_experiment(self, data_path): - """Loads x, y, sy values from an ASCII data file into the - experiment. - - The file must be structured as: - x y sy - """ - try: - from diffpy.utils.parsers.loaddata import loadData - except ImportError: - raise ImportError('diffpy module not found.') from None - try: - data = loadData(data_path) - except Exception as e: - raise IOError(f'Failed to read data from {data_path}: {e}') from e - - if data.shape[1] < 2: - raise ValueError('Data file must have at least two columns: x and y.') - - default_sy = 0.03 - if data.shape[1] < 3: - print(f'Warning: No uncertainty (sy) column provided. Defaulting to {default_sy}.') - - # Extract x, y, and sy data - x = data[:, 0] - # We should also add sx = data[:, 2] to capture the e.s.d. of x. - # It might be useful in future. - y = data[:, 1] - # Using sqrt isn’t appropriate here, as the y-scale isn’t raw - # counts and includes both positive and negative values. For - # now, set the e.s.d. to a fixed value of 0.03 if it’s not - # included in the measured data file. We should improve this - # later. - # sy = data[:, 3] if data.shape[1] > 2 else np.sqrt(y) - sy = data[:, 2] if data.shape[1] > 2 else np.full_like(y, fill_value=default_sy) - - # Attach the data to the experiment's datastore - self.datastore.x = x - self.datastore.meas = y - self.datastore.meas_su = sy - - print(paragraph('Data loaded successfully')) - print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") - - -class SingleCrystalExperiment(BaseExperiment): - """Single crystal experiment class with specific attributes.""" - - def __init__( - self, - name: str, - type: ExperimentType, - ) -> None: - super().__init__(name=name, type=type) - self.linked_crystal = None - - def show_meas_chart(self) -> None: - print('Showing measured data chart is not implemented yet.') - - -class ExperimentFactory: - """Creates Experiment instances with only relevant attributes.""" - - _valid_arg_sets = [ - { - 'required': ['cif_path'], - 'optional': [], - }, - { - 'required': ['cif_str'], - 'optional': [], - }, - { - 'required': [ - 'name', - 'data_path', - ], - 'optional': [ - 'sample_form', - 'beam_mode', - 'radiation_probe', - 'scattering_type', - ], - }, - { - 'required': ['name'], - 'optional': [ - 'sample_form', - 'beam_mode', - 'radiation_probe', - 'scattering_type', - ], - }, - ] - - _supported = { - ScatteringTypeEnum.BRAGG: { - SampleFormEnum.POWDER: PowderExperiment, - SampleFormEnum.SINGLE_CRYSTAL: SingleCrystalExperiment, - }, - ScatteringTypeEnum.TOTAL: { - SampleFormEnum.POWDER: PairDistributionFunctionExperiment, - }, - } - - @classmethod - def create(cls, **kwargs): - """Main factory method for creating an experiment instance. - - Validates argument combinations and dispatches to the - appropriate creation method. Raises ValueError if arguments are - invalid or no valid dispatch is found. - """ - # Check for valid argument combinations - user_args = [k for k, v in kwargs.items() if v is not None] - if not cls.is_valid_args(user_args): - raise ValueError(f'Invalid argument combination: {user_args}') - - # Validate enum arguments if provided - if 'sample_form' in kwargs: - SampleFormEnum(kwargs['sample_form']) - if 'beam_mode' in kwargs: - BeamModeEnum(kwargs['beam_mode']) - if 'radiation_probe' in kwargs: - RadiationProbeEnum(kwargs['radiation_probe']) - if 'scattering_type' in kwargs: - ScatteringTypeEnum(kwargs['scattering_type']) - - # Dispatch to the appropriate creation method - if 'cif_path' in kwargs: - return cls._create_from_cif_path(kwargs) - elif 'cif_str' in kwargs: - return cls._create_from_cif_str(kwargs) - elif 'data_path' in kwargs: - return cls._create_from_data_path(kwargs) - elif 'name' in kwargs: - return cls._create_without_data(kwargs) - - @staticmethod - def _create_from_cif_path(cif_path): - """Create an experiment from a CIF file path. - - Not yet implemented. - """ - # TODO: Implement CIF file loading logic - raise NotImplementedError('CIF file loading not implemented yet.') - - @staticmethod - def _create_from_cif_str(cif_str): - """Create an experiment from a CIF string. - - Not yet implemented. - """ - # TODO: Implement CIF string loading logic - raise NotImplementedError('CIF string loading not implemented yet.') - - @classmethod - def _create_from_data_path(cls, kwargs): - """Create an experiment from a raw data ASCII file. - - Loads the experiment and attaches measured data from the - specified file. - """ - expt_type = cls._make_experiment_type(kwargs) - scattering_type = expt_type.scattering_type.value - sample_form = expt_type.sample_form.value - expt_class = cls._supported[scattering_type][sample_form] - expt_name = kwargs['name'] - expt_obj = expt_class(name=expt_name, type=expt_type) - data_path = kwargs['data_path'] - expt_obj._load_ascii_data_to_experiment(data_path) - return expt_obj - - @classmethod - def _create_without_data(cls, kwargs): - """Create an experiment without measured data. - - Returns an experiment instance with only metadata and - configuration. - """ - expt_type = cls._make_experiment_type(kwargs) - scattering_type = expt_type.scattering_type.value - sample_form = expt_type.sample_form.value - expt_class = cls._supported[scattering_type][sample_form] - expt_name = kwargs['name'] - expt_obj = expt_class(name=expt_name, type=expt_type) - return expt_obj - - @classmethod - def _make_experiment_type(cls, kwargs): - """Helper to construct an ExperimentType from keyword arguments, - using defaults as needed. - """ - return ExperimentType( - sample_form=kwargs.get('sample_form', SampleFormEnum.default()), - beam_mode=kwargs.get('beam_mode', BeamModeEnum.default()), - radiation_probe=kwargs.get('radiation_probe', RadiationProbeEnum.default()), - scattering_type=kwargs.get('scattering_type', ScatteringTypeEnum.default()), - ) - - @staticmethod - def is_valid_args(user_args): - """Validate user argument set against allowed combinations. - - Returns True if the argument set matches any valid combination, - else False. - """ - user_arg_set = set(user_args) - for arg_set in ExperimentFactory._valid_arg_sets: - required = set(arg_set['required']) - optional = set(arg_set['optional']) - # Must have all required, and only required+optional - if required.issubset(user_arg_set) and user_arg_set <= (required | optional): - return True - return False - - -class Experiment: - """User-facing API for creating an experiment. - - Accepts keyword arguments and delegates validation and creation to - ExperimentFactory. - """ - - def __new__(cls, **kwargs): - return ExperimentFactory.create(**kwargs) diff --git a/src/easydiffraction/experiments/experiment/__init__.py b/src/easydiffraction/experiments/experiment/__init__.py new file mode 100644 index 00000000..23232375 --- /dev/null +++ b/src/easydiffraction/experiments/experiment/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.experiments.experiment.base import PdExperimentBase +from easydiffraction.experiments.experiment.bragg_pd import BraggPdExperiment +from easydiffraction.experiments.experiment.bragg_sc import BraggScExperiment +from easydiffraction.experiments.experiment.total_pd import TotalPdExperiment + +__all__ = [ + 'ExperimentBase', + 'PdExperimentBase', + 'BraggPdExperiment', + 'TotalPdExperiment', + 'BraggScExperiment', +] diff --git a/src/easydiffraction/experiments/experiment/base.py b/src/easydiffraction/experiments/experiment/base.py new file mode 100644 index 00000000..ae8d383f --- /dev/null +++ b/src/easydiffraction/experiments/experiment/base.py @@ -0,0 +1,217 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING + +from easydiffraction.core.datablock import DatablockItem +from easydiffraction.experiments.categories.excluded_regions import ExcludedRegions +from easydiffraction.experiments.categories.linked_phases import LinkedPhases +from easydiffraction.experiments.categories.peak.factory import PeakFactory +from easydiffraction.experiments.categories.peak.factory import PeakProfileTypeEnum +from easydiffraction.experiments.datastore.factory import DatastoreFactory +from easydiffraction.io.cif.serialize import experiment_to_cif +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import warning +from easydiffraction.utils.utils import render_cif +from easydiffraction.utils.utils import render_table + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.experiment_type import ExperimentType + + +class ExperimentBase(DatablockItem): + """Base class for all experiments with only core attributes. + + Wraps experiment type, instrument and datastore. + """ + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ): + super().__init__() + self._name = name + self._type = type + self._datastore = DatastoreFactory.create( + sample_form=self.type.sample_form.value, + beam_mode=self.type.beam_mode.value, + ) + self._identity.datablock_entry_name = lambda: self.name + + @property + def name(self) -> str: + """Human-readable name of the experiment.""" + return self._name + + @name.setter + def name(self, new: str) -> None: + """Rename the experiment. + + Args: + new: New name for this experiment. + """ + self._name = new + + @property + def type(self): # TODO: Consider another name + """Experiment type descriptor (sample form, probe, beam + mode). + """ + return self._type + + @property + def datastore(self): + """Data container with x, y, error, calc and background + arrays. + """ + return self._datastore + + @property + def as_cif(self) -> str: + """Serialize this experiment to a CIF fragment.""" + return experiment_to_cif(self) + + def show_as_cif(self) -> None: + """Pretty-print the experiment and datastore as CIF text.""" + experiment_cif = super().as_cif + datastore_cif = self.datastore.as_truncated_cif + cif_text: str = f'{experiment_cif}\n\n{datastore_cif}' + paragraph_title: str = paragraph(f"Experiment 🔬 '{self.name}' as cif") + render_cif(cif_text, paragraph_title) + + @abstractmethod + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """Load ASCII data from file into the experiment datastore. + + Args: + data_path: Path to the ASCII file to load. + """ + raise NotImplementedError() + + +class PdExperimentBase(ExperimentBase): + """Base class for all powder experiments.""" + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ) -> None: + super().__init__(name=name, type=type) + + self._peak_profile_type: PeakProfileTypeEnum = PeakProfileTypeEnum.default( + self.type.scattering_type.value, + self.type.beam_mode.value, + ) + self._peak = PeakFactory.create( + scattering_type=self.type.scattering_type.value, + beam_mode=self.type.beam_mode.value, + profile_type=self._peak_profile_type, + ) + + self._linked_phases: LinkedPhases = LinkedPhases() + self._excluded_regions: ExcludedRegions = ExcludedRegions() + + @abstractmethod + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """Load powder diffraction data from an ASCII file. + + Args: + data_path: Path to data file with columns compatible with + the beam mode (e.g. 2θ/I/σ for CWL, TOF/I/σ for TOF). + """ + pass + + @property + def peak(self) -> str: + """Peak category object with profile parameters and mixins.""" + return self._peak + + @peak.setter + def peak(self, value): + """Replace the peak model used for this powder experiment. + + Args: + value: New peak object created by the `PeakFactory`. + """ + self._peak = value + + @property + def linked_phases(self) -> str: + """Collection of phases linked to this experiment.""" + return self._linked_phases + + @property + def excluded_regions(self) -> str: + """Collection of excluded regions for the x-grid.""" + return self._excluded_regions + + @property + def peak_profile_type(self): + """Currently selected peak profile type enum.""" + return self._peak_profile_type + + @peak_profile_type.setter + def peak_profile_type(self, new_type: str | PeakProfileTypeEnum): + """Change the active peak profile type, if supported. + + Args: + new_type: New profile type as enum or its string value. + """ + if isinstance(new_type, str): + try: + new_type = PeakProfileTypeEnum(new_type) + except ValueError: + print(warning(f"Unknown peak profile type '{new_type}'")) + return + + supported_types = list( + PeakFactory._supported[self.type.scattering_type.value][ + self.type.beam_mode.value + ].keys() + ) + + if new_type not in supported_types: + print(warning(f"Unsupported peak profile '{new_type.value}'")) + print(f'Supported peak profiles: {supported_types}') + print("For more information, use 'show_supported_peak_profile_types()'") + return + + self._peak = PeakFactory.create( + scattering_type=self.type.scattering_type.value, + beam_mode=self.type.beam_mode.value, + profile_type=new_type, + ) + self._peak_profile_type = new_type + print(paragraph(f"Peak profile type for experiment '{self.name}' changed to")) + print(new_type.value) + + def show_supported_peak_profile_types(self): + """Print available peak profile types for this experiment.""" + columns_headers = ['Peak profile type', 'Description'] + columns_alignment = ['left', 'left'] + columns_data = [] + + scattering_type = self.type.scattering_type.value + beam_mode = self.type.beam_mode.value + + for profile_type in PeakFactory._supported[scattering_type][beam_mode]: + columns_data.append([profile_type.value, profile_type.description()]) + + print(paragraph('Supported peak profile types')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) + + def show_current_peak_profile_type(self): + """Print the currently selected peak profile type.""" + print(paragraph('Current peak profile type')) + print(self.peak_profile_type) diff --git a/src/easydiffraction/experiments/experiment/bragg_pd.py b/src/easydiffraction/experiments/experiment/bragg_pd.py new file mode 100644 index 00000000..f69e1fbc --- /dev/null +++ b/src/easydiffraction/experiments/experiment/bragg_pd.py @@ -0,0 +1,135 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np + +from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum +from easydiffraction.experiments.categories.background.factory import BackgroundFactory +from easydiffraction.experiments.experiment.base import PdExperimentBase +from easydiffraction.experiments.experiment.instrument_mixin import InstrumentMixin +from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.formatting import warning +from easydiffraction.utils.utils import render_table + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.experiment_type import ExperimentType + + +class BraggPdExperiment(InstrumentMixin, PdExperimentBase): + """Powder diffraction experiment. + + Wraps background model, peak profile and linked phases for Bragg PD. + """ + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ) -> None: + super().__init__(name=name, type=type) + + self._background_type: BackgroundTypeEnum = BackgroundTypeEnum.default() + self._background = BackgroundFactory.create(background_type=self.background_type) + + @property + def background(self): + return self._background + + @background.setter + def background(self, value): + self._background = value + + # ------------- + # Measured data + # ------------- + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + """Load (x, y, sy) data from an ASCII file into the datastore. + + The file format is space/column separated with 2 or 3 columns: + ``x y [sy]``. If ``sy`` is missing, it is approximated as + ``sqrt(y)`` with small values clamped to ``1.0``. + """ + try: + data = np.loadtxt(data_path) + except Exception as e: + raise IOError(f'Failed to read data from {data_path}: {e}') from e + + if data.shape[1] < 2: + raise ValueError('Data file must have at least two columns: x and y.') + + if data.shape[1] < 3: + print('Warning: No uncertainty (sy) column provided. Defaulting to sqrt(y).') + + # Extract x, y data + x: np.ndarray = data[:, 0] + y: np.ndarray = data[:, 1] + + # Round x to 4 decimal places + x = np.round(x, 4) + + # Determine sy from column 3 if available, otherwise use sqrt(y) + sy: np.ndarray = data[:, 2] if data.shape[1] > 2 else np.sqrt(y) + + # Replace values smaller than 0.0001 with 1.0 + sy = np.where(sy < 0.0001, 1.0, sy) + + # Attach the data to the experiment's datastore + self.datastore.full_x = x + self.datastore.full_meas = y + self.datastore.full_meas_su = sy + self.datastore.x = x + self.datastore.meas = y + self.datastore.meas_su = sy + self.datastore.excluded = np.full(x.shape, fill_value=False, dtype=bool) + + print(paragraph('Data loaded successfully')) + print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") + + @property + def background_type(self): + """Current background type enum value.""" + return self._background_type + + @background_type.setter + def background_type(self, new_type): + """Set and apply a new background type. + + Falls back to printing supported types if the new value is not + supported. + """ + if new_type not in BackgroundFactory._supported_map(): + supported_types = list(BackgroundFactory._supported_map().keys()) + print(warning(f"Unknown background type '{new_type}'")) + print(f'Supported background types: {supported_types}') + print("For more information, use 'show_supported_background_types()'") + return + self.background = BackgroundFactory.create(new_type) + self._background_type = new_type + print(paragraph(f"Background type for experiment '{self.name}' changed to")) + print(new_type) + + def show_supported_background_types(self): + """Print a table of supported background types.""" + columns_headers = ['Background type', 'Description'] + columns_alignment = ['left', 'left'] + columns_data = [] + for bt in BackgroundFactory._supported_map(): + columns_data.append([bt.value, bt.description()]) + + print(paragraph('Supported background types')) + render_table( + columns_headers=columns_headers, + columns_alignment=columns_alignment, + columns_data=columns_data, + ) + + def show_current_background_type(self): + """Print the currently used background type.""" + print(paragraph('Current background type')) + print(self.background_type) diff --git a/src/easydiffraction/experiments/experiment/bragg_sc.py b/src/easydiffraction/experiments/experiment/bragg_sc.py new file mode 100644 index 00000000..d5e069da --- /dev/null +++ b/src/easydiffraction/experiments/experiment/bragg_sc.py @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Single crystal experiment types and helpers.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from easydiffraction.experiments.experiment.base import ExperimentBase + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.experiment_type import ExperimentType + + +class BraggScExperiment(ExperimentBase): + """Single crystal experiment class with specific attributes.""" + + def __init__( + self, + *, + name: str, + type: ExperimentType, + ) -> None: + super().__init__(name=name, type=type) + self.linked_crystal = None + + def show_meas_chart(self) -> None: + print('Showing measured data chart is not implemented yet.') diff --git a/src/easydiffraction/experiments/experiment/enums.py b/src/easydiffraction/experiments/experiment/enums.py new file mode 100644 index 00000000..ca622c3b --- /dev/null +++ b/src/easydiffraction/experiments/experiment/enums.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Enumerations for experiment configuration (forms, modes, types).""" + +from enum import Enum + + +class SampleFormEnum(str, Enum): + """Physical sample form supported by experiments.""" + + POWDER = 'powder' + SINGLE_CRYSTAL = 'single crystal' + + @classmethod + def default(cls) -> 'SampleFormEnum': + return cls.POWDER + + def description(self) -> str: + if self is SampleFormEnum.POWDER: + return 'Powdered or polycrystalline sample.' + elif self is SampleFormEnum.SINGLE_CRYSTAL: + return 'Single crystal sample.' + + +class ScatteringTypeEnum(str, Enum): + """Type of scattering modeled in an experiment.""" + + BRAGG = 'bragg' + TOTAL = 'total' + + @classmethod + def default(cls) -> 'ScatteringTypeEnum': + return cls.BRAGG + + def description(self) -> str: + if self is ScatteringTypeEnum.BRAGG: + return 'Bragg diffraction for conventional structure refinement.' + elif self is ScatteringTypeEnum.TOTAL: + return 'Total scattering for pair distribution function analysis (PDF).' + + +class RadiationProbeEnum(str, Enum): + """Incident radiation probe used in the experiment.""" + + NEUTRON = 'neutron' + XRAY = 'xray' + + @classmethod + def default(cls) -> 'RadiationProbeEnum': + return cls.NEUTRON + + def description(self) -> str: + if self is RadiationProbeEnum.NEUTRON: + return 'Neutron diffraction.' + elif self is RadiationProbeEnum.XRAY: + return 'X-ray diffraction.' + + +class BeamModeEnum(str, Enum): + """Beam delivery mode for the instrument.""" + + CONSTANT_WAVELENGTH = 'constant wavelength' + TIME_OF_FLIGHT = 'time-of-flight' + + @classmethod + def default(cls) -> 'BeamModeEnum': + return cls.CONSTANT_WAVELENGTH + + def description(self) -> str: + if self is BeamModeEnum.CONSTANT_WAVELENGTH: + return 'Constant wavelength (CW) diffraction.' + elif self is BeamModeEnum.TIME_OF_FLIGHT: + return 'Time-of-flight (TOF) diffraction.' + + +class PeakProfileTypeEnum(str, Enum): + """Available peak profile types per scattering and beam mode.""" + + PSEUDO_VOIGT = 'pseudo-voigt' + SPLIT_PSEUDO_VOIGT = 'split pseudo-voigt' + THOMPSON_COX_HASTINGS = 'thompson-cox-hastings' + PSEUDO_VOIGT_IKEDA_CARPENTER = 'pseudo-voigt * ikeda-carpenter' + PSEUDO_VOIGT_BACK_TO_BACK = 'pseudo-voigt * back-to-back' + GAUSSIAN_DAMPED_SINC = 'gaussian-damped-sinc' + + @classmethod + def default( + cls, + scattering_type: ScatteringTypeEnum | None = None, + beam_mode: BeamModeEnum | None = None, + ) -> 'PeakProfileTypeEnum': + if scattering_type is None: + scattering_type = ScatteringTypeEnum.default() + if beam_mode is None: + beam_mode = BeamModeEnum.default() + return { + (ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH): cls.PSEUDO_VOIGT, + ( + ScatteringTypeEnum.BRAGG, + BeamModeEnum.TIME_OF_FLIGHT, + ): cls.PSEUDO_VOIGT_IKEDA_CARPENTER, + (ScatteringTypeEnum.TOTAL, BeamModeEnum.CONSTANT_WAVELENGTH): cls.GAUSSIAN_DAMPED_SINC, + (ScatteringTypeEnum.TOTAL, BeamModeEnum.TIME_OF_FLIGHT): cls.GAUSSIAN_DAMPED_SINC, + }[(scattering_type, beam_mode)] + + def description(self) -> str: + if self is PeakProfileTypeEnum.PSEUDO_VOIGT: + return 'Pseudo-Voigt profile' + elif self is PeakProfileTypeEnum.SPLIT_PSEUDO_VOIGT: + return 'Split pseudo-Voigt profile with empirical asymmetry correction.' + elif self is PeakProfileTypeEnum.THOMPSON_COX_HASTINGS: + return 'Thompson-Cox-Hastings profile with FCJ asymmetry correction.' + elif self is PeakProfileTypeEnum.PSEUDO_VOIGT_IKEDA_CARPENTER: + return 'Pseudo-Voigt profile with Ikeda-Carpenter asymmetry correction.' + elif self is PeakProfileTypeEnum.PSEUDO_VOIGT_BACK_TO_BACK: + return 'Pseudo-Voigt profile with Back-to-Back Exponential asymmetry correction.' + elif self is PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC: + return 'Gaussian-damped sinc profile for pair distribution function (PDF) analysis.' diff --git a/src/easydiffraction/experiments/experiment/factory.py b/src/easydiffraction/experiments/experiment/factory.py new file mode 100644 index 00000000..ca03ffde --- /dev/null +++ b/src/easydiffraction/experiments/experiment/factory.py @@ -0,0 +1,150 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.core.factory import FactoryBase +from easydiffraction.experiments.categories.experiment_type import ExperimentType +from easydiffraction.experiments.experiment import BraggPdExperiment +from easydiffraction.experiments.experiment import BraggScExperiment +from easydiffraction.experiments.experiment import TotalPdExperiment +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + +class ExperimentFactory(FactoryBase): + """Creates Experiment instances with only relevant attributes.""" + + _ALLOWED_ARG_SPECS = [ + {'required': ['cif_path'], 'optional': []}, + {'required': ['cif_str'], 'optional': []}, + { + 'required': ['name', 'data_path'], + 'optional': ['sample_form', 'beam_mode', 'radiation_probe', 'scattering_type'], + }, + { + 'required': ['name'], + 'optional': ['sample_form', 'beam_mode', 'radiation_probe', 'scattering_type'], + }, + ] + + _SUPPORTED = { + ScatteringTypeEnum.BRAGG: { + SampleFormEnum.POWDER: BraggPdExperiment, + SampleFormEnum.SINGLE_CRYSTAL: BraggScExperiment, + }, + ScatteringTypeEnum.TOTAL: { + SampleFormEnum.POWDER: TotalPdExperiment, + }, + } + + @classmethod + def create(cls, **kwargs): + """Create an `ExperimentBase` using a validated argument + combination. + """ + # Check for valid argument combinations + user_args = {k for k, v in kwargs.items() if v is not None} + cls._validate_args(user_args, cls._ALLOWED_ARG_SPECS, cls.__name__) + + # Validate enum arguments if provided + if 'sample_form' in kwargs: + SampleFormEnum(kwargs['sample_form']) + if 'beam_mode' in kwargs: + BeamModeEnum(kwargs['beam_mode']) + if 'radiation_probe' in kwargs: + RadiationProbeEnum(kwargs['radiation_probe']) + if 'scattering_type' in kwargs: + ScatteringTypeEnum(kwargs['scattering_type']) + + # Dispatch to the appropriate creation method + if 'cif_path' in kwargs: + return cls._create_from_cif_path(kwargs['cif_path']) + elif 'cif_str' in kwargs: + return cls._create_from_cif_str(kwargs['cif_str']) + elif 'data_path' in kwargs: + return cls._create_from_data_path(kwargs) + elif 'name' in kwargs: + return cls._create_without_data(kwargs) + + # ------------------------------- + # Private creation helper methods + # ------------------------------- + + @staticmethod + def _create_from_cif_path(cif_path): + """Create an experiment from a CIF file path. + + Not yet implemented. + """ + # TODO: Implement CIF file loading logic + raise NotImplementedError('CIF file loading not implemented yet.') + + @staticmethod + def _create_from_cif_str(cif_str): + """Create an experiment from a CIF string. + + Not yet implemented. + """ + # TODO: Implement CIF string loading logic + raise NotImplementedError('CIF string loading not implemented yet.') + + @classmethod + def _create_from_data_path(cls, kwargs): + """Create an experiment from a raw data ASCII file. + + Loads the experiment and attaches measured data from the + specified file. + """ + expt_type = cls._make_experiment_type(kwargs) + scattering_type = expt_type.scattering_type.value + sample_form = expt_type.sample_form.value + expt_class = cls._SUPPORTED[scattering_type][sample_form] + expt_name = kwargs['name'] + expt_obj = expt_class(name=expt_name, type=expt_type) + data_path = kwargs['data_path'] + expt_obj._load_ascii_data_to_experiment(data_path) + return expt_obj + + @classmethod + def _create_without_data(cls, kwargs): + """Create an experiment without measured data. + + Returns an experiment instance with only metadata and + configuration. + """ + expt_type = cls._make_experiment_type(kwargs) + scattering_type = expt_type.scattering_type.value + sample_form = expt_type.sample_form.value + expt_class = cls._SUPPORTED[scattering_type][sample_form] + expt_name = kwargs['name'] + expt_obj = expt_class(name=expt_name, type=expt_type) + return expt_obj + + @classmethod + def _make_experiment_type(cls, kwargs): + """Helper to construct an ExperimentType from keyword arguments, + using defaults as needed. + """ + return ExperimentType( + sample_form=kwargs.get('sample_form', SampleFormEnum.default()), + beam_mode=kwargs.get('beam_mode', BeamModeEnum.default()), + radiation_probe=kwargs.get('radiation_probe', RadiationProbeEnum.default()), + scattering_type=kwargs.get('scattering_type', ScatteringTypeEnum.default()), + ) + + @staticmethod + def _is_valid_args(user_args): + """Validate user argument set against allowed combinations. + + Returns True if the argument set matches any valid combination, + else False. + """ + user_arg_set = set(user_args) + for arg_set in ExperimentFactory._valid_arg_sets: + required = set(arg_set['required']) + optional = set(arg_set['optional']) + # Must have all required, and only required+optional + if required.issubset(user_arg_set) and user_arg_set <= (required | optional): + return True + return False diff --git a/src/easydiffraction/experiments/experiment/instrument_mixin.py b/src/easydiffraction/experiments/experiment/instrument_mixin.py new file mode 100644 index 00000000..0b32c769 --- /dev/null +++ b/src/easydiffraction/experiments/experiment/instrument_mixin.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from typeguard import typechecked + +from easydiffraction.experiments.categories.instrument.factory import InstrumentFactory + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.instrument.base import InstrumentBase + + +class InstrumentMixin: + """Mixin that wires an experiment to an instrument category. + + Creates a default instrument via `InstrumentFactory` using the + experiment type (scattering type and beam mode) at initialization. + """ + + def __init__(self, *args, **kwargs): + expt_type = kwargs.get('type') + super().__init__(*args, **kwargs) + self._instrument = InstrumentFactory.create( + scattering_type=expt_type.scattering_type.value, + beam_mode=expt_type.beam_mode.value, + ) + + @property + def instrument(self): + """Instrument category object associated with the experiment.""" + return self._instrument + + @instrument.setter + @typechecked + def instrument(self, new_instrument: InstrumentBase): + """Replace the instrument and re-parent it to this experiment. + + Args: + new_instrument: Instrument instance compatible with the + experiment type. + """ + self._instrument = new_instrument + self._instrument._parent = self diff --git a/src/easydiffraction/experiments/experiment/total_pd.py b/src/easydiffraction/experiments/experiment/total_pd.py new file mode 100644 index 00000000..0364acfe --- /dev/null +++ b/src/easydiffraction/experiments/experiment/total_pd.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import numpy as np + +from easydiffraction.experiments.experiment.base import PdExperimentBase +from easydiffraction.utils.formatting import paragraph + +if TYPE_CHECKING: + from easydiffraction.experiments.categories.experiment_type import ExperimentType + + +class TotalPdExperiment(PdExperimentBase): + """PDF experiment class with specific attributes.""" + + def __init__( + self, + name: str, + type: ExperimentType, + ): + super().__init__(name=name, type=type) + + def _load_ascii_data_to_experiment(self, data_path): + """Loads x, y, sy values from an ASCII data file into the + experiment. + + The file must be structured as: + x y sy + """ + try: + from diffpy.utils.parsers.loaddata import loadData + except ImportError: + raise ImportError('diffpy module not found.') from None + try: + data = loadData(data_path) + except Exception as e: + raise IOError(f'Failed to read data from {data_path}: {e}') from e + + if data.shape[1] < 2: + raise ValueError('Data file must have at least two columns: x and y.') + + default_sy = 0.03 + if data.shape[1] < 3: + print(f'Warning: No uncertainty (sy) column provided. Defaulting to {default_sy}.') + + x = data[:, 0] + y = data[:, 1] + sy = data[:, 2] if data.shape[1] > 2 else np.full_like(y, fill_value=default_sy) + + self.datastore.x = x + self.datastore.meas = y + self.datastore.meas_su = sy + + print(paragraph('Data loaded successfully')) + print(f"Experiment 🔬 '{self.name}'. Number of data points: {len(x)}") diff --git a/src/easydiffraction/experiments/experiments.py b/src/easydiffraction/experiments/experiments.py index 0db524ed..c22c10a3 100644 --- a/src/easydiffraction/experiments/experiments.py +++ b/src/easydiffraction/experiments/experiments.py @@ -1,45 +1,53 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Dict -from typing import List - -from easydiffraction.core.objects import Collection -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import RadiationProbeEnum -from easydiffraction.experiments.components.experiment_type import SampleFormEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum -from easydiffraction.experiments.experiment import BaseExperiment -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.utils.decorators import enforce_type +from typeguard import typechecked + +from easydiffraction.core.datablock import DatablockCollection +from easydiffraction.experiments.experiment.base import ExperimentBase +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum +from easydiffraction.experiments.experiment.factory import ExperimentFactory from easydiffraction.utils.formatting import paragraph -class Experiments(Collection): - """Collection manager for multiple Experiment instances.""" +class Experiments(DatablockCollection): + """Collection of Experiment data blocks. - @property - def _child_class(self): - return BaseExperiment + Provides convenience constructors for common creation patterns and + helper methods for simple presentation of collection contents. + """ def __init__(self) -> None: - super().__init__() - self._experiments: Dict[str, BaseExperiment] = self._items # Alias for legacy support + super().__init__(item_type=ExperimentBase) - def add(self, experiment: BaseExperiment): - """Add a pre-built experiment instance.""" - self._add_prebuilt_experiment(experiment) + # -------------------- + # Add / Remove methods + # -------------------- + @typechecked def add_from_cif_path(self, cif_path: str): - """Add a new experiment from a CIF file path.""" - experiment = Experiment(cif_path=cif_path) - self._add_prebuilt_experiment(experiment) + """Add an experiment from a CIF file path. + Args: + cif_path: Path to a CIF document. + """ + experiment = ExperimentFactory.create(cif_path=cif_path) + self.add(experiment) + + @typechecked def add_from_cif_str(self, cif_str: str): - """Add a new experiment from CIF file content (string).""" - experiment = Experiment(cif_str=cif_str) - self._add_prebuilt_experiment(experiment) + """Add an experiment from a CIF string. + + Args: + cif_str: Full CIF document as a string. + """ + experiment = ExperimentFactory.create(cif_str=cif_str) + self.add(experiment) + @typechecked def add_from_data_path( self, name: str, @@ -49,8 +57,17 @@ def add_from_data_path( radiation_probe: str = RadiationProbeEnum.default().value, scattering_type: str = ScatteringTypeEnum.default().value, ): - """Add a new experiment from a data file path.""" - experiment = Experiment( + """Add an experiment from a data file path. + + Args: + name: Experiment identifier. + data_path: Path to the measured data file. + sample_form: Sample form (powder or single crystal). + beam_mode: Beam mode (constant wavelength or TOF). + radiation_probe: Radiation probe (neutron or xray). + scattering_type: Scattering type (bragg or total). + """ + experiment = ExperimentFactory.create( name=name, data_path=data_path, sample_form=sample_form, @@ -58,8 +75,9 @@ def add_from_data_path( radiation_probe=radiation_probe, scattering_type=scattering_type, ) - self._add_prebuilt_experiment(experiment) + self.add(experiment) + @typechecked def add_without_data( self, name: str, @@ -68,35 +86,40 @@ def add_without_data( radiation_probe: str = RadiationProbeEnum.default().value, scattering_type: str = ScatteringTypeEnum.default().value, ): - """Add a new experiment without any data file.""" - experiment = Experiment( + """Add an experiment without associating a data file. + + Args: + name: Experiment identifier. + sample_form: Sample form (powder or single crystal). + beam_mode: Beam mode (constant wavelength or TOF). + radiation_probe: Radiation probe (neutron or xray). + scattering_type: Scattering type (bragg or total). + """ + experiment = ExperimentFactory.create( name=name, sample_form=sample_form, beam_mode=beam_mode, radiation_probe=radiation_probe, scattering_type=scattering_type, ) - self._add_prebuilt_experiment(experiment) + self.add(experiment) - @enforce_type - def _add_prebuilt_experiment(self, experiment: BaseExperiment): - self._experiments[experiment.name] = experiment + @typechecked + def remove(self, name: str) -> None: + """Remove an experiment by name if it exists.""" + if name in self: + del self[name] - def remove(self, experiment_id: str) -> None: - if experiment_id in self._experiments: - del self._experiments[experiment_id] + # ------------ + # Show methods + # ------------ def show_names(self) -> None: + """Print the list of experiment names.""" print(paragraph('Defined experiments' + ' 🔬')) - print(self.ids) - - @property - def ids(self) -> List[str]: - return list(self._experiments.keys()) + print(self.names) def show_params(self) -> None: - for exp in self._experiments.values(): - print(exp) - - def as_cif(self) -> str: - return '\n\n'.join([exp.as_cif() for exp in self._experiments.values()]) + """Print parameters for each experiment in the collection.""" + for exp in self.values(): + exp.show_params() diff --git a/src/easydiffraction/io/cif/handler.py b/src/easydiffraction/io/cif/handler.py new file mode 100644 index 00000000..a21ae16e --- /dev/null +++ b/src/easydiffraction/io/cif/handler.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Minimal CIF tag handler used by descriptors/parameters.""" + +from __future__ import annotations + + +class CifHandler: + """Canonical CIF handler used by descriptors/parameters. + + Holds CIF tags (names) and attaches to an owning descriptor so it + can derive a stable uid if needed. + """ + + def __init__(self, *, names: list[str]) -> None: + self._names = names + self._owner = None # set by attach + + def attach(self, owner): + """Attach to a descriptor or parameter instance.""" + self._owner = owner + + @property + def names(self) -> list[str]: + """List of CIF tag names associated with the owner.""" + return self._names + + @property + def uid(self) -> str | None: + """Unique identifier taken from the owner, if attached.""" + if self._owner is None: + return None + return self._owner.unique_name diff --git a/src/easydiffraction/io/cif/serialize.py b/src/easydiffraction/io/cif/serialize.py new file mode 100644 index 00000000..09013a62 --- /dev/null +++ b/src/easydiffraction/io/cif/serialize.py @@ -0,0 +1,268 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +from typing import Any +from typing import Optional +from typing import Sequence + +import numpy as np + +from easydiffraction.utils.utils import str_to_ufloat + + +def format_value(value) -> str: + """Format a single CIF value, quoting strings with whitespace.""" + if isinstance(value, str) and (' ' in value or '\t' in value): + return f'"{value}"' + return str(value) + + +def param_to_cif(param) -> str: + """Render a single descriptor/parameter to a CIF line. + + Expects ``param`` to expose ``_cif_handler.names`` and ``value``. + """ + tags: Sequence[str] = param._cif_handler.names # type: ignore[attr-defined] + main_key: str = tags[0] + return f'{main_key} {format_value(param.value)}' + + +def category_item_to_cif(item) -> str: + """Render a CategoryItem-like object to CIF text. + + Expects ``item.parameters`` iterable of params with + ``_cif_handler.names`` and ``value``. + """ + lines: list[str] = [] + for p in item.parameters: + lines.append(param_to_cif(p)) + return '\n'.join(lines) + + +def category_collection_to_cif(collection) -> str: + """Render a CategoryCollection-like object to CIF text. + + Uses first item to build loop header, then emits rows for each item. + """ + if not len(collection): + return '' + + lines: list[str] = [] + + # Header + first_item = list(collection.values())[0] + lines.append('loop_') + for p in first_item.parameters: + tags = p._cif_handler.names # type: ignore[attr-defined] + lines.append(tags[0]) + + # Rows + for item in collection.values(): + row_vals = [format_value(p.value) for p in item.parameters] + lines.append(' '.join(row_vals)) + + return '\n'.join(lines) + + +def datastore_to_cif(datastore, max_points: Optional[int] = None) -> str: + """Render a datastore to CIF text. + + Expects ``datastore`` to have ``_cif_mapping()`` and attributes per + mapping keys. + """ + cif_lines: list[str] = ['loop_'] + + mapping: dict[str, str] = datastore._cif_mapping() # type: ignore[attr-defined] + for cif_key in mapping.values(): + cif_lines.append(cif_key) + + data_arrays: list[np.ndarray] = [] + for attr_name in mapping: + arr = getattr(datastore, attr_name, None) + data_arrays.append(np.array([]) if arr is None else arr) + + if not data_arrays or not data_arrays[0].size: + return '' + + n_points = len(data_arrays[0]) + + def format_row(i: int) -> str: + return ' '.join(str(data_arrays[j][i]) for j in range(len(data_arrays))) + + if max_points is not None and n_points > 2 * max_points: + for i in range(max_points): + cif_lines.append(format_row(i)) + cif_lines.append('...') + for i in range(-max_points, 0): + cif_lines.append(format_row(i)) + else: + for i in range(n_points): + cif_lines.append(format_row(i)) + + return '\n'.join(cif_lines) + + +def datablock_item_to_cif(datablock) -> str: + """Render a DatablockItem-like object to CIF text. + + Emits a data_ header and then concatenates category CIF sections. + """ + # Local imports to avoid import-time cycles + from easydiffraction.core.category import CategoryCollection + from easydiffraction.core.category import CategoryItem + + header = f'data_{datablock._identity.datablock_entry_name}' + parts: list[str] = [header] + for v in vars(datablock).values(): + if isinstance(v, CategoryItem): + parts.append(v.as_cif) + for v in vars(datablock).values(): + if isinstance(v, CategoryCollection): + parts.append(v.as_cif) + return '\n\n'.join(parts) + + +def datablock_collection_to_cif(collection) -> str: + """Render a collection of datablocks by joining their CIF blocks.""" + return '\n\n'.join([block.as_cif for block in collection.values()]) + + +def project_info_to_cif(info) -> str: + """Render ProjectInfo to CIF text (id, title, description, + dates). + """ + from textwrap import wrap + + wrapped_title = wrap(info.title, width=46) + wrapped_description = wrap(info.description, width=46) + + title_str = f"_project.title '{wrapped_title[0]}'" if wrapped_title else '' + for line in wrapped_title[1:]: + title_str += f"\n{' ' * 27}'{line}'" + + if wrapped_description: + base = '_project.description ' + indent = ' ' * len(base) + desc_str = f"{base}'{wrapped_description[0]}" + for line in wrapped_description[1:]: + desc_str += f'\n{indent}{line}' + desc_str += "'" + else: + desc_str = "_project.description ''" + + created = info._created.strftime('%d %b %Y %H:%M:%S') + modified = info._last_modified.strftime('%d %b %Y %H:%M:%S') + + return ( + f'_project.id {info.name}\n' + f'{title_str}\n' + f'{desc_str}\n' + f"_project.created '{created}'\n" + f"_project.last_modified '{modified}'\n" + ) + + +def project_to_cif(project) -> str: + """Render a whole project by concatenating sections when present.""" + parts: list[str] = [] + if hasattr(project, 'info'): + parts.append(project.info.as_cif) + if getattr(project, 'sample_models', None): + parts.append(project.sample_models.as_cif) + if getattr(project, 'experiments', None): + parts.append(project.experiments.as_cif) + if getattr(project, 'analysis', None): + parts.append(project.analysis.as_cif()) + if getattr(project, 'summary', None): + parts.append(project.summary.as_cif()) + return '\n\n'.join([p for p in parts if p]) + + +def experiment_to_cif(experiment) -> str: + """Render an experiment: datablock part plus measured data.""" + block = datablock_item_to_cif(experiment) + data = experiment.datastore.as_cif + return f'{block}\n\n{data}' if data else block + + +def analysis_to_cif(analysis) -> str: + """Render analysis metadata, aliases, and constraints to CIF.""" + cur_min = format_value(analysis.current_minimizer) + lines: list[str] = [] + lines.append(f'_analysis.calculator_engine {format_value(analysis.current_calculator)}') + lines.append(f'_analysis.fitting_engine {cur_min}') + lines.append(f'_analysis.fit_mode {format_value(analysis.fit_mode)}') + lines.append('') + lines.append(analysis.aliases.as_cif) + lines.append('') + lines.append(analysis.constraints.as_cif) + return '\n'.join(lines) + + +def summary_to_cif(_summary) -> str: + """Render a summary CIF block (placeholder for now).""" + return 'To be added...' + + +# TODO: Check the following methods: + + +def param_from_cif(self, block: Any, idx: int = 0) -> None: + found_values: list[Any] = [] + for tag in self.full_cif_names: + candidate = list(block.find_values(tag)) + if candidate: + found_values = candidate + break + if not found_values: + self.value = self.default_value + return + raw = found_values[idx] + if self.value_type is float: + u = str_to_ufloat(raw) + self.value = u.n + if hasattr(self, 'uncertainty'): + self.uncertainty = u.s # type: ignore[attr-defined] + elif self.value_type is str: + if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in {"'", '"'}: + self.value = raw[1:-1] + else: + self.value = raw + else: + self.value = raw + + +def category_item_from_cif(self, block, idx: int = 0) -> None: + """Populate each parameter from CIF block at given loop index.""" + for param in self.parameters: + param.from_cif(block, idx=idx) + + +# TODO: from_cif or add_from_cif as in collections? +def category_collection_from_cif(self, block): + # Derive loop size using category_entry_name first CIF tag alias + if self._item_type is None: + raise ValueError('Child class is not defined.') + # TODO: Find a better way and then remove TODO in the AtomSite + # class + # Create a temporary instance to access category_entry_name + # attribute used as ID column for the items in this collection + child_obj = self._item_type() + entry_attr = getattr(child_obj, child_obj._category_entry_attr_name) + # Try to find the value(s) from the CIF block iterating over + # the possible cif names in order of preference. + size = 0 + for name in entry_attr.full_cif_names: + size = len(block.find_values(name)) + break + # If no values found, nothing to do + if not size: + return + # If values found, delegate to child class to parse each + # row and add to collection + for row_idx in range(size): + child_obj = self._item_type() + child_obj.from_cif(block, idx=row_idx) + self.add(child_obj) diff --git a/src/easydiffraction/plotting/plotters/plotter_ascii.py b/src/easydiffraction/plotting/plotters/plotter_ascii.py index 7b257283..2694eace 100644 --- a/src/easydiffraction/plotting/plotters/plotter_ascii.py +++ b/src/easydiffraction/plotting/plotters/plotter_ascii.py @@ -16,6 +16,8 @@ class AsciiPlotter(PlotterBase): + """Terminal-based plotter using ASCII art.""" + def _get_legend_item(self, label): color_start = DEFAULT_COLORS[label] color_end = asciichartpy.reset @@ -33,6 +35,17 @@ def plot( title, height=None, ): + """Render a compact ASCII chart in the terminal. + + Args: + x: 1D array-like of x values (only used for range + display). + y_series: Sequence of y arrays to plot. + labels: Series identifiers corresponding to y_series. + axes_labels: Ignored; kept for API compatibility. + title: Figure title printed above the chart. + height: Number of text rows to allocate for the chart. + """ # Intentionally unused; kept for a consistent plotting API del axes_labels title = paragraph(title) diff --git a/src/easydiffraction/plotting/plotters/plotter_base.py b/src/easydiffraction/plotting/plotters/plotter_base.py index 98d02d39..45bf1068 100644 --- a/src/easydiffraction/plotting/plotters/plotter_base.py +++ b/src/easydiffraction/plotting/plotters/plotter_base.py @@ -6,8 +6,8 @@ import numpy as np -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum from easydiffraction.utils.utils import is_notebook DEFAULT_ENGINE = 'plotly' if is_notebook() else 'asciichartpy' @@ -55,6 +55,13 @@ class PlotterBase(ABC): + """Abstract base for plotting backends. + + Concrete implementations should accept x values, multiple y-series + and a small set of labeling arguments and render a plot to the + chosen medium. + """ + @abstractmethod def plot( self, @@ -65,4 +72,14 @@ def plot( title, height, ): + """Render a plot. + + Args: + x: 1D array-like of x-axis values. + y_series: Sequence of y arrays to plot. + labels: Series identifiers corresponding to y_series. + axes_labels: Pair of strings for the x and y axis titles. + title: Figure title. + height: Backend-specific height in text or pixels. + """ pass diff --git a/src/easydiffraction/plotting/plotters/plotter_plotly.py b/src/easydiffraction/plotting/plotters/plotter_plotly.py index 6e7d95fc..544b991d 100644 --- a/src/easydiffraction/plotting/plotters/plotter_plotly.py +++ b/src/easydiffraction/plotting/plotters/plotter_plotly.py @@ -24,7 +24,11 @@ class PlotlyPlotter(PlotterBase): + """Interactive plotter using Plotly for notebooks and browsers.""" + pio.templates.default = 'plotly_dark' if darkdetect.isDark() else 'plotly_white' + if is_pycharm(): + pio.renderers.default = 'browser' def _get_trace(self, x, y, label): mode = SERIES_CONFIG[label]['mode'] @@ -51,6 +55,16 @@ def plot( title, height=None, ): + """Render an interactive Plotly figure. + + Args: + x: 1D array-like of x-axis values. + y_series: Sequence of y arrays to plot. + labels: Series identifiers corresponding to y_series. + axes_labels: Pair of strings for the x and y titles. + title: Figure title. + height: Ignored; Plotly auto-sizes based on renderer. + """ # Intentionally unused; accepted for API compatibility del height data = [] @@ -105,14 +119,13 @@ def plot( layout=layout, ) - # Format the axes ticks - # Keeps decimals for small numbers; - # groups thousands for large ones + # Format the axes ticks. + # Keeps decimals for small numbers; groups thousands for large + # ones fig.update_xaxes(tickformat=',.6~g', separatethousands=True) fig.update_yaxes(tickformat=',.6~g', separatethousands=True) # Show the figure - if is_pycharm() or display is None or HTML is None: fig.show(config=config) else: diff --git a/src/easydiffraction/plotting/plotting.py b/src/easydiffraction/plotting/plotting.py index e68000c6..93e388d7 100644 --- a/src/easydiffraction/plotting/plotting.py +++ b/src/easydiffraction/plotting/plotting.py @@ -1,5 +1,8 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""Plotting facade for measured and calculated patterns.""" + +import numpy as np from easydiffraction.plotting.plotters.plotter_ascii import AsciiPlotter from easydiffraction.plotting.plotters.plotter_base import DEFAULT_AXES_LABELS @@ -14,6 +17,8 @@ class Plotter: + """User-facing plotting facade backed by concrete plotters.""" + def __init__(self): # Plotting engine self._engine = DEFAULT_ENGINE @@ -127,6 +132,7 @@ def plot_meas( x_max=None, d_spacing=False, ): + """Plot measured pattern using current engine.""" if pattern.x is None: error(f'No data available for experiment {expt_name}') return @@ -184,6 +190,7 @@ def plot_calc( x_max=None, d_spacing=False, ): + """Plot calculated pattern using current engine.""" if pattern.x is None: error(f'No data available for experiment {expt_name}') return @@ -242,6 +249,7 @@ def plot_meas_vs_calc( show_residual=False, d_spacing=False, ): + """Plot measured and calculated series and optional residual.""" if pattern.x is None: print(error(f'No data available for experiment {expt_name}')) return @@ -252,7 +260,18 @@ def plot_meas_vs_calc( print(error(f'No calculated data available for experiment {expt_name}')) return + # Select x-axis data based on d-spacing or original x values x_array = pattern.d if d_spacing else pattern.x + + # For asciichartpy, if x_min or x_max is not provided, center + # around the maximum intensity peak + if self._engine == 'asciichartpy' and (x_min is None or x_max is None): + max_intensity_pos = np.argmax(pattern.meas) + half_range = 50 + x_min = x_array[max_intensity_pos - half_range] + x_max = x_array[max_intensity_pos + half_range] + + # Filter x, y_meas, and y_calc based on x_min and x_max x = self._filtered_y_array( y_array=x_array, x_array=x_array, @@ -323,6 +342,8 @@ def _filtered_y_array( class PlotterFactory: + """Factory for plotter implementations.""" + _SUPPORTED_ENGINES_DICT = { 'asciichartpy': { 'description': 'Console ASCII line charts', @@ -336,12 +357,14 @@ class PlotterFactory: @classmethod def supported_engines(cls): + """Return list of supported engine names.""" keys = cls._SUPPORTED_ENGINES_DICT.keys() engines = list(keys) return engines @classmethod def create_plotter(cls, engine_name): + """Create a concrete plotter by engine name.""" config = cls._SUPPORTED_ENGINES_DICT.get(engine_name) if not config: supported_engines = cls.supported_engines() diff --git a/src/easydiffraction/project/__init__.py b/src/easydiffraction/project/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/project/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/project.py b/src/easydiffraction/project/project.py similarity index 59% rename from src/easydiffraction/project.py rename to src/easydiffraction/project/project.py index 732ef96a..38252a5a 100644 --- a/src/easydiffraction/project.py +++ b/src/easydiffraction/project/project.py @@ -1,155 +1,136 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""Project facade to orchestrate models, experiments, and analysis.""" -import datetime import pathlib import tempfile -from textwrap import wrap -from typing import List +from typeguard import typechecked from varname import varname from easydiffraction.analysis.analysis import Analysis -from easydiffraction.experiments.components.experiment_type import BeamModeEnum +from easydiffraction.core.guard import GuardedBase +from easydiffraction.experiments.experiment.enums import BeamModeEnum from easydiffraction.experiments.experiments import Experiments +from easydiffraction.io.cif.serialize import project_to_cif from easydiffraction.plotting.plotting import Plotter +from easydiffraction.project.project_info import ProjectInfo from easydiffraction.sample_models.sample_models import SampleModels -from easydiffraction.summary import Summary +from easydiffraction.summary.summary import Summary from easydiffraction.utils.formatting import error from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.utils import render_cif from easydiffraction.utils.utils import tof_to_d from easydiffraction.utils.utils import twotheta_to_d -class ProjectInfo: - """Stores metadata about the project, such as name, title, - description, and file paths. +class Project(GuardedBase): + """Central API for managing a diffraction data analysis project. + + Provides access to sample models, experiments, analysis, and + summary. """ - def __init__(self) -> None: - self._name: str = 'untitled_project' - self._title: str = 'Untitled Project' - self._description: str = '' - self._path: pathlib.Path = pathlib.Path.cwd() - self._created: datetime.datetime = datetime.datetime.now() - self._last_modified: datetime.datetime = datetime.datetime.now() + # ------------------------------------------------------------------ + # Initialization + # ------------------------------------------------------------------ + def __init__( + self, + name: str = 'untitled_project', + title: str = 'Untitled Project', + description: str = '', + ) -> None: + super().__init__() + + self._info: ProjectInfo = ProjectInfo(name, title, description) + self._sample_models = SampleModels() + self._experiments = Experiments() + self._plotter = Plotter() + self._analysis = Analysis(self) + self._summary = Summary(self) + self._saved = False + self._varname = varname() - @property - def name(self) -> str: - """Return the project name.""" - return self._name + # ------------------------------------------------------------------ + # Dunder methods + # ------------------------------------------------------------------ + def __str__(self) -> str: + """Human-readable representation.""" + class_name = self.__class__.__name__ + project_name = self.name + sample_models_count = len(self.sample_models) + experiments_count = len(self.experiments) + return ( + f"{class_name} '{project_name}' " + f'({sample_models_count} sample models, ' + f'{experiments_count} experiments)' + ) - @name.setter - def name(self, value: str) -> None: - self._name = value + # ------------------------------------------------------------------ + # Public read-only properties + # ------------------------------------------------------------------ @property - def title(self) -> str: - """Return the project title.""" - return self._title - - @title.setter - def title(self, value: str) -> None: - self._title = value + def info(self) -> ProjectInfo: + """Project metadata container.""" + return self._info @property - def description(self) -> str: - """Return sanitized description with single spaces.""" - return ' '.join(self._description.split()) - - @description.setter - def description(self, value: str) -> None: - self._description = ' '.join(value.split()) + def name(self) -> str: + """Convenience property to access the project's name + directly. + """ + return self._info.name @property - def path(self) -> pathlib.Path: - """Return the project path as a Path object.""" - return self._path - - @path.setter - def path(self, value) -> None: - # Accept str or Path; normalize to Path - self._path = pathlib.Path(value) + def full_name(self) -> str: + return self.name @property - def created(self) -> datetime.datetime: - """Return the creation timestamp.""" - return self._created + def sample_models(self) -> SampleModels: + """Collection of sample models in the project.""" + return self._sample_models - @property - def last_modified(self) -> datetime.datetime: - """Return the last modified timestamp.""" - return self._last_modified - - def update_last_modified(self) -> None: - """Update the last modified timestamp.""" - self._last_modified = datetime.datetime.now() - - def as_cif(self) -> str: - """Export project metadata to CIF.""" - wrapped_title: List[str] = wrap(self.title, width=46) - wrapped_description: List[str] = wrap(self.description, width=46) - - title_str: str = f"_project.title '{wrapped_title[0]}'" - for line in wrapped_title[1:]: - title_str += f"\n{' ' * 27}'{line}'" - - if wrapped_description: - base_indent: str = '_project.description ' - indent_spaces: str = ' ' * len(base_indent) - formatted_description: str = f"{base_indent}'{wrapped_description[0]}" - for line in wrapped_description[1:]: - formatted_description += f'\n{indent_spaces}{line}' - formatted_description += "'" - else: - formatted_description: str = "_project.description ''" + @sample_models.setter + @typechecked + def sample_models(self, sample_models: SampleModels) -> None: + self._sample_models = sample_models - return ( - f'_project.id {self.name}\n' - f'{title_str}\n' - f'{formatted_description}\n' - f"_project.created '{self._created.strftime('%d %b %Y %H:%M:%S')}'\n" - f"_project.last_modified '{self._last_modified.strftime('%d %b %Y %H:%M:%S')}'\n" - ) + @property + def experiments(self): + """Collection of experiments in the project.""" + return self._experiments - def show_as_cif(self) -> None: - cif_text: str = self.as_cif() - paragraph_title: str = paragraph(f"Project 📦 '{self.name}' info as cif") - render_cif(cif_text, paragraph_title) + @experiments.setter + @typechecked + def experiments(self, experiments: Experiments): + self._experiments = experiments + @property + def plotter(self): + """Plotting facade bound to the project.""" + return self._plotter -class Project: - """Central API for managing a diffraction data analysis project. + @property + def analysis(self): + """Analysis entry-point bound to the project.""" + return self._analysis - Provides access to sample models, experiments, analysis, and - summary. - """ + @property + def summary(self): + """Summary report builder bound to the project.""" + return self._summary - def __init__( - self, - name: str = 'untitled_project', - title: str = 'Untitled Project', - description: str = '', - ) -> None: - self.info: ProjectInfo = ProjectInfo() - self.info.name = name - self.info.title = title - self.info.description = description - self.sample_models = SampleModels() - self.experiments = Experiments() - self.plotter = Plotter() - self.analysis = Analysis(self) - self.summary = Summary(self) - self._saved = False - self._varname = varname() + @property + def parameters(self): + """Return parameters from all components (TBD).""" + # To be implemented: return all parameters in the project + return [] @property - def name(self) -> str: - """Convenience property to access the project's name - directly. - """ - return self.info.name + def as_cif(self): + """Export whole project as CIF text.""" + # Concatenate sections using centralized CIF serializers + return project_to_cif(self) # ------------------------------------------ # Project File I/O @@ -162,84 +143,74 @@ def load(self, dir_path: str) -> None: """ print(paragraph(f'Loading project 📦 from {dir_path}')) print(dir_path) - self.info.path = dir_path + self._info.path = dir_path # TODO: load project components from files inside dir_path print('Loading project is not implemented yet.') self._saved = True - def save_as( - self, - dir_path: str, - temporary: bool = False, - ) -> None: - """Save the project into a new directory.""" - if temporary: - tmp: str = tempfile.gettempdir() - dir_path = pathlib.Path(tmp) / dir_path - self.info.path = dir_path - self.save() - def save(self) -> None: """Save the project into the existing project directory.""" - if not self.info.path: + if not self._info.path: print(error('Project path not specified. Use save_as() to define the path first.')) return print(paragraph(f"Saving project 📦 '{self.name}' to")) - print(self.info.path.resolve()) + print(self._info.path.resolve()) # Ensure project directory exists - self.info.path.mkdir(parents=True, exist_ok=True) + self._info.path.mkdir(parents=True, exist_ok=True) # Save project info - with (self.info.path / 'project.cif').open('w') as f: - f.write(self.info.as_cif()) + with (self._info.path / 'project.cif').open('w') as f: + f.write(self._info.as_cif()) print('✅ project.cif') # Save sample models - sm_dir = self.info.path / 'sample_models' + sm_dir = self._info.path / 'sample_models' sm_dir.mkdir(parents=True, exist_ok=True) - for model in self.sample_models: + # Iterate over sample model objects (MutableMapping iter gives + # keys) + for model in self.sample_models.values(): file_name: str = f'{model.name}.cif' file_path = sm_dir / file_name with file_path.open('w') as f: - f.write(model.as_cif()) + f.write(model.as_cif) print(f'✅ sample_models/{file_name}') # Save experiments - expt_dir = self.info.path / 'experiments' + expt_dir = self._info.path / 'experiments' expt_dir.mkdir(parents=True, exist_ok=True) - for experiment in self.experiments: + for experiment in self.experiments.values(): file_name: str = f'{experiment.name}.cif' file_path = expt_dir / file_name with file_path.open('w') as f: - f.write(experiment.as_cif()) + f.write(experiment.as_cif) print(f'✅ experiments/{file_name}') # Save analysis - with (self.info.path / 'analysis.cif').open('w') as f: + with (self._info.path / 'analysis.cif').open('w') as f: f.write(self.analysis.as_cif()) print('✅ analysis.cif') # Save summary - with (self.info.path / 'summary.cif').open('w') as f: + with (self._info.path / 'summary.cif').open('w') as f: f.write(self.summary.as_cif()) print('✅ summary.cif') - self.info.update_last_modified() + self._info.update_last_modified() self._saved = True - # ------------------------------------------ - # Sample Models API Convenience Methods - # ------------------------------------------ - - def set_sample_models(self, sample_models: SampleModels) -> None: - """Attach a collection of sample models to the project.""" - self.sample_models = sample_models - - def set_experiments(self, experiments: Experiments) -> None: - """Attach a collection of experiments to the project.""" - self.experiments = experiments + def save_as( + self, + dir_path: str, + temporary: bool = False, + ) -> None: + """Save the project into a new directory.""" + if temporary: + tmp: str = tempfile.gettempdir() + dir_path = pathlib.Path(tmp) / dir_path + self._info.path = dir_path + self.save() # ------------------------------------------ # Plotting diff --git a/src/easydiffraction/project/project_info.py b/src/easydiffraction/project/project_info.py new file mode 100644 index 00000000..56a4cdd1 --- /dev/null +++ b/src/easydiffraction/project/project_info.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Project metadata container used by Project.""" + +import datetime +import pathlib + +from easydiffraction import paragraph +from easydiffraction.core.guard import GuardedBase +from easydiffraction.io.cif.serialize import project_info_to_cif +from easydiffraction.utils.utils import render_cif + + +class ProjectInfo(GuardedBase): + """Stores metadata about the project, such as name, title, + description, and file paths. + """ + + def __init__( + self, + name: str = 'untitled_project', + title: str = 'Untitled Project', + description: str = '', + ) -> None: + super().__init__() + + self._name = name + self._title = title + self._description = description + self._path: pathlib.Path = pathlib.Path.cwd() + self._created: datetime.datetime = datetime.datetime.now() + self._last_modified: datetime.datetime = datetime.datetime.now() + + @property + def name(self) -> str: + """Return the project name.""" + return self._name + + @name.setter + def name(self, value: str) -> None: + self._name = value + + @property + def unique_name(self) -> str: + """Unique name for GuardedBase diagnostics.""" + return self.name + + @property + def title(self) -> str: + """Return the project title.""" + return self._title + + @title.setter + def title(self, value: str) -> None: + self._title = value + + @property + def description(self) -> str: + """Return sanitized description with single spaces.""" + return ' '.join(self._description.split()) + + @description.setter + def description(self, value: str) -> None: + self._description = ' '.join(value.split()) + + @property + def path(self) -> pathlib.Path: + """Return the project path as a Path object.""" + return self._path + + @path.setter + def path(self, value) -> None: + # Accept str or Path; normalize to Path + self._path = pathlib.Path(value) + + @property + def created(self) -> datetime.datetime: + """Return the creation timestamp.""" + return self._created + + @property + def last_modified(self) -> datetime.datetime: + """Return the last modified timestamp.""" + return self._last_modified + + def update_last_modified(self) -> None: + """Update the last modified timestamp.""" + self._last_modified = datetime.datetime.now() + + def parameters(self): + """Placeholder for parameter listing.""" + pass + + # TODO: Consider moving to io.cif.serialize + def as_cif(self) -> str: + """Export project metadata to CIF.""" + return project_info_to_cif(self) + + # TODO: Consider moving to io.cif.serialize + def show_as_cif(self) -> None: + """Pretty-print CIF via shared utilities.""" + cif_text: str = self.as_cif() + paragraph_title: str = paragraph(f"Project 📦 '{self.name}' info as cif") + render_cif(cif_text, paragraph_title) diff --git a/src/easydiffraction/sample_models/categories/__init__.py b/src/easydiffraction/sample_models/categories/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/sample_models/categories/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/sample_models/categories/atom_sites.py b/src/easydiffraction/sample_models/categories/atom_sites.py new file mode 100644 index 00000000..5535b27f --- /dev/null +++ b/src/easydiffraction/sample_models/categories/atom_sites.py @@ -0,0 +1,295 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Atom site category. + +Defines AtomSite items and AtomSites collection used in sample models. +Only documentation was added; behavior remains unchanged. +""" + +from cryspy.A_functions_base.database import DATABASE + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.core.validation import RangeValidator +from easydiffraction.core.validation import RegexValidator +from easydiffraction.io.cif.handler import CifHandler + + +class AtomSite(CategoryItem): + """Single atom site with fractional coordinates and ADP. + + Attributes are represented by descriptors to support validation and + CIF serialization. + """ + + def __init__( + self, + *, + label=None, + type_symbol=None, + fract_x=None, + fract_y=None, + fract_z=None, + wyckoff_letter=None, + occupancy=None, + b_iso=None, + adp_type=None, + ) -> None: + super().__init__() + + self._label: StringDescriptor = StringDescriptor( + name='label', + description='Unique identifier for the atom site.', + value_spec=AttributeSpec( + value=label, + type_=DataTypes.STRING, + default='Si', + # TODO: the following pattern is valid for dict key + # (keywords are not checked). CIF label is less strict. + # Do we need conversion between CIF and internal label? + content_validator=RegexValidator(pattern=r'^[A-Za-z_][A-Za-z0-9_]*$'), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.label', + ] + ), + ) + self._type_symbol: StringDescriptor = StringDescriptor( + name='type_symbol', + description='Chemical symbol of the atom at this site.', + value_spec=AttributeSpec( + value=type_symbol, + type_=DataTypes.STRING, + default='Tb', + content_validator=MembershipValidator(allowed=self._type_symbol_allowed_values), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.type_symbol', + ] + ), + ) + self._fract_x: Parameter = Parameter( + name='fract_x', + description='Fractional x-coordinate of the atom site within the unit cell.', + value_spec=AttributeSpec( + value=fract_x, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.fract_x', + ] + ), + ) + self._fract_y: Parameter = Parameter( + name='fract_y', + description='Fractional y-coordinate of the atom site within the unit cell.', + value_spec=AttributeSpec( + value=fract_y, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.fract_y', + ] + ), + ) + self._fract_z: Parameter = Parameter( + name='fract_z', + description='Fractional z-coordinate of the atom site within the unit cell.', + value_spec=AttributeSpec( + value=fract_z, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.fract_z', + ] + ), + ) + self._wyckoff_letter: StringDescriptor = StringDescriptor( + name='wyckoff_letter', + description='Wyckoff letter indicating the symmetry of the ' + 'atom site within the space group.', + value_spec=AttributeSpec( + value=wyckoff_letter, + type_=DataTypes.STRING, + default=self._wyckoff_letter_default_value, + content_validator=MembershipValidator(allowed=self._wyckoff_letter_allowed_values), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.Wyckoff_letter', + '_atom_site.Wyckoff_symbol', + ] + ), + ) + self._occupancy: Parameter = Parameter( + name='occupancy', + description='Occupancy of the atom site, representing the ' + 'fraction of the site occupied by the atom type.', + value_spec=AttributeSpec( + value=occupancy, + type_=DataTypes.NUMERIC, + default=1.0, + content_validator=RangeValidator(), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.occupancy', + ] + ), + ) + self._b_iso: Parameter = Parameter( + name='b_iso', + description='Isotropic atomic displacement parameter (ADP) for the atom site.', + value_spec=AttributeSpec( + value=b_iso, + type_=DataTypes.NUMERIC, + default=0.0, + content_validator=RangeValidator(), + ), + units='Ų', + cif_handler=CifHandler( + names=[ + '_atom_site.B_iso_or_equiv', + ] + ), + ) + self._adp_type: StringDescriptor = StringDescriptor( + name='adp_type', + description='Type of atomic displacement parameter (ADP) ' + 'used (e.g., Biso, Uiso, Uani, Bani).', + value_spec=AttributeSpec( + value=adp_type, + type_=DataTypes.STRING, + default='Biso', + content_validator=MembershipValidator(allowed=['Biso']), + ), + cif_handler=CifHandler( + names=[ + '_atom_site.adp_type', + ] + ), + ) + + self._identity.category_code = 'atom_site' + self._identity.category_entry_name = lambda: self.label.value + + @property + def _type_symbol_allowed_values(self): + return list({key[1] for key in DATABASE['Isotopes']}) + + @property + def _wyckoff_letter_allowed_values(self): + # TODO: Need to now current space group. How to access it? Via + # parent Cell? Then letters = + # list(SPACE_GROUPS[62, 'cab']['Wyckoff_positions'].keys()) + # Temporarily return hardcoded list: + return ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] + + @property + def _wyckoff_letter_default_value(self): + # TODO: What to pass as default? + return self._wyckoff_letter_allowed_values[0] + + @property + def label(self): + """Label descriptor for the site (unique key).""" + return self._label + + @label.setter + def label(self, value): + self._label.value = value + + @property + def type_symbol(self): + """Chemical symbol descriptor (e.g. 'Si').""" + return self._type_symbol + + @type_symbol.setter + def type_symbol(self, value): + self._type_symbol.value = value + + @property + def adp_type(self): + """ADP type descriptor (e.g. 'Biso').""" + return self._adp_type + + @adp_type.setter + def adp_type(self, value): + self._adp_type.value = value + + @property + def wyckoff_letter(self): + """Wyckoff letter descriptor (space-group position).""" + return self._wyckoff_letter + + @wyckoff_letter.setter + def wyckoff_letter(self, value): + self._wyckoff_letter.value = value + + @property + def fract_x(self): + """Fractional x coordinate descriptor.""" + return self._fract_x + + @fract_x.setter + def fract_x(self, value): + self._fract_x.value = value + + @property + def fract_y(self): + """Fractional y coordinate descriptor.""" + return self._fract_y + + @fract_y.setter + def fract_y(self, value): + self._fract_y.value = value + + @property + def fract_z(self): + """Fractional z coordinate descriptor.""" + return self._fract_z + + @fract_z.setter + def fract_z(self, value): + self._fract_z.value = value + + @property + def occupancy(self): + """Occupancy descriptor (0..1).""" + return self._occupancy + + @occupancy.setter + def occupancy(self, value): + self._occupancy.value = value + + @property + def b_iso(self): + """Isotropic ADP descriptor in Ų.""" + return self._b_iso + + @b_iso.setter + def b_iso(self, value): + self._b_iso.value = value + + +class AtomSites(CategoryCollection): + """Collection of AtomSite instances.""" + + def __init__(self): + super().__init__(item_type=AtomSite) diff --git a/src/easydiffraction/sample_models/categories/cell.py b/src/easydiffraction/sample_models/categories/cell.py new file mode 100644 index 00000000..0e132749 --- /dev/null +++ b/src/easydiffraction/sample_models/categories/cell.py @@ -0,0 +1,157 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Unit cell parameters category for sample models.""" + +from typing import Optional + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import RangeValidator +from easydiffraction.io.cif.handler import CifHandler + + +class Cell(CategoryItem): + """Unit cell with lengths a, b, c and angles alpha, beta, gamma.""" + + def __init__( + self, + *, + length_a: Optional[int | float] = None, + length_b: Optional[int | float] = None, + length_c: Optional[int | float] = None, + angle_alpha: Optional[int | float] = None, + angle_beta: Optional[int | float] = None, + angle_gamma: Optional[int | float] = None, + ) -> None: + super().__init__() + + self._length_a: Parameter = Parameter( + name='length_a', + description='Length of the a axis of the unit cell.', + value_spec=AttributeSpec( + value=length_a, + type_=DataTypes.NUMERIC, + default=10.0, + content_validator=RangeValidator(ge=0, le=1000), + ), + units='Å', + cif_handler=CifHandler(names=['_cell.length_a']), + ) + self._length_b: Parameter = Parameter( + name='length_b', + description='Length of the b axis of the unit cell.', + value_spec=AttributeSpec( + value=length_b, + type_=DataTypes.NUMERIC, + default=10.0, + content_validator=RangeValidator(ge=0, le=1000), + ), + units='Å', + cif_handler=CifHandler(names=['_cell.length_b']), + ) + self._length_c: Parameter = Parameter( + name='length_c', + description='Length of the c axis of the unit cell.', + value_spec=AttributeSpec( + value=length_c, + type_=DataTypes.NUMERIC, + default=10.0, + content_validator=RangeValidator(ge=0, le=1000), + ), + units='Å', + cif_handler=CifHandler(names=['_cell.length_c']), + ) + self._angle_alpha: Parameter = Parameter( + name='angle_alpha', + description='Angle between edges b and c.', + value_spec=AttributeSpec( + value=angle_alpha, + type_=DataTypes.NUMERIC, + default=90.0, + content_validator=RangeValidator(ge=0, le=180), + ), + units='deg', + cif_handler=CifHandler(names=['_cell.angle_alpha']), + ) + self._angle_beta: Parameter = Parameter( + name='angle_beta', + description='Angle between edges a and c.', + value_spec=AttributeSpec( + value=angle_beta, + type_=DataTypes.NUMERIC, + default=90.0, + content_validator=RangeValidator(ge=0, le=180), + ), + units='deg', + cif_handler=CifHandler(names=['_cell.angle_beta']), + ) + self._angle_gamma: Parameter = Parameter( + name='angle_gamma', + description='Angle between edges a and b.', + value_spec=AttributeSpec( + value=angle_gamma, + type_=DataTypes.NUMERIC, + default=90.0, + content_validator=RangeValidator(ge=0, le=180), + ), + units='deg', + cif_handler=CifHandler(names=['_cell.angle_gamma']), + ) + + self._identity.category_code = 'cell' + + @property + def length_a(self): + """Descriptor for a-axis length in Å.""" + return self._length_a + + @length_a.setter + def length_a(self, value): + self._length_a.value = value + + @property + def length_b(self): + """Descriptor for b-axis length in Å.""" + return self._length_b + + @length_b.setter + def length_b(self, value): + self._length_b.value = value + + @property + def length_c(self): + """Descriptor for c-axis length in Å.""" + return self._length_c + + @length_c.setter + def length_c(self, value): + self._length_c.value = value + + @property + def angle_alpha(self): + """Descriptor for angle alpha in degrees.""" + return self._angle_alpha + + @angle_alpha.setter + def angle_alpha(self, value): + self._angle_alpha.value = value + + @property + def angle_beta(self): + """Descriptor for angle beta in degrees.""" + return self._angle_beta + + @angle_beta.setter + def angle_beta(self, value): + self._angle_beta.value = value + + @property + def angle_gamma(self): + """Descriptor for angle gamma in degrees.""" + return self._angle_gamma + + @angle_gamma.setter + def angle_gamma(self, value): + self._angle_gamma.value = value diff --git a/src/easydiffraction/sample_models/categories/space_group.py b/src/easydiffraction/sample_models/categories/space_group.py new file mode 100644 index 00000000..5b5f2f45 --- /dev/null +++ b/src/easydiffraction/sample_models/categories/space_group.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Space group category for crystallographic sample models.""" + +from cryspy.A_functions_base.function_2_space_group import ACCESIBLE_NAME_HM_SHORT +from cryspy.A_functions_base.function_2_space_group import ( + get_it_coordinate_system_codes_by_it_number, +) +from cryspy.A_functions_base.function_2_space_group import get_it_number_by_name_hm_short + +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.core.validation import MembershipValidator +from easydiffraction.io.cif.handler import CifHandler + + +class SpaceGroup(CategoryItem): + """Space group with Hermann–Mauguin symbol and IT code.""" + + def __init__( + self, + *, + name_h_m: str = None, + it_coordinate_system_code: str = None, + ) -> None: + super().__init__() + self._name_h_m: StringDescriptor = StringDescriptor( + name='name_h_m', + description='Hermann-Mauguin symbol of the space group.', + value_spec=AttributeSpec( + value=name_h_m, + type_=DataTypes.STRING, + default='P 1', + content_validator=MembershipValidator( + allowed=lambda: self._name_h_m_allowed_values + ), + ), + cif_handler=CifHandler( + names=[ + '_space_group.name_H-M_alt', + '_space_group_name_H-M_alt', + '_symmetry.space_group_name_H-M', + '_symmetry_space_group_name_H-M', + ] + ), + ) + self._it_coordinate_system_code: StringDescriptor = StringDescriptor( + name='it_coordinate_system_code', + description='A qualifier identifying which setting in IT is used.', + value_spec=AttributeSpec( + value=it_coordinate_system_code, + type_=DataTypes.STRING, + default=lambda: self._it_coordinate_system_code_default_value, + content_validator=MembershipValidator( + allowed=lambda: self._it_coordinate_system_code_allowed_values + ), + ), + cif_handler=CifHandler( + names=[ + '_space_group.IT_coordinate_system_code', + '_space_group_IT_coordinate_system_code', + '_symmetry.IT_coordinate_system_code', + '_symmetry_IT_coordinate_system_code', + ] + ), + ) + self._identity.category_code = 'space_group' + + def _reset_it_coordinate_system_code(self): + self._it_coordinate_system_code.value = self._it_coordinate_system_code_default_value + + @property + def _name_h_m_allowed_values(self): + return ACCESIBLE_NAME_HM_SHORT + + @property + def _it_coordinate_system_code_allowed_values(self): + name = self.name_h_m.value + it_number = get_it_number_by_name_hm_short(name) + codes = get_it_coordinate_system_codes_by_it_number(it_number) + codes = [str(code) for code in codes] + return codes if codes else [''] + + @property + def _it_coordinate_system_code_default_value(self): + return self._it_coordinate_system_code_allowed_values[0] + + @property + def name_h_m(self): + """Descriptor for Hermann–Mauguin symbol.""" + return self._name_h_m + + @name_h_m.setter + def name_h_m(self, value): + self._name_h_m.value = value + self._reset_it_coordinate_system_code() + + @property + def it_coordinate_system_code(self): + """Descriptor for IT coordinate system code.""" + return self._it_coordinate_system_code + + @it_coordinate_system_code.setter + def it_coordinate_system_code(self, value): + self._it_coordinate_system_code.value = value diff --git a/src/easydiffraction/sample_models/collections/atom_sites.py b/src/easydiffraction/sample_models/collections/atom_sites.py deleted file mode 100644 index fc8af2ed..00000000 --- a/src/easydiffraction/sample_models/collections/atom_sites.py +++ /dev/null @@ -1,113 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter - - -class AtomSite(Component): - """Represents a single atom site within the crystal structure.""" - - @property - def category_key(self): - return 'atom_sites' - - @property - def cif_category_key(self): - return 'atom_site' - - def __init__( - self, - label: str, - type_symbol: str, - fract_x: float, - fract_y: float, - fract_z: float, - wyckoff_letter: str = None, - occupancy: float = 1.0, - b_iso: float = 0.0, - adp_type: str = 'Biso', - ): # TODO: add support for Uiso, Uani and Bani - super().__init__() - - self.label = Descriptor( - value=label, - name='label', - cif_name='label', - description='Unique identifier for the atom site.', - ) - self.type_symbol = Descriptor( - value=type_symbol, - name='type_symbol', - cif_name='type_symbol', - description='Chemical symbol of the atom at this site.', - ) - self.adp_type = Descriptor( - value=adp_type, - name='adp_type', - cif_name='ADP_type', - description='Type of atomic displacement parameter (ADP) ' - 'used (e.g., Biso, Uiso, Uani, Bani).', - ) - self.wyckoff_letter = Descriptor( - value=wyckoff_letter, - name='wyckoff_letter', - cif_name='Wyckoff_letter', - description='Wyckoff letter indicating the symmetry of the ' - 'atom site within the space group.', - ) - self.fract_x = Parameter( - value=fract_x, - name='fract_x', - cif_name='fract_x', - description='Fractional x-coordinate of the atom site within the unit cell.', - ) - self.fract_y = Parameter( - value=fract_y, - name='fract_y', - cif_name='fract_y', - description='Fractional y-coordinate of the atom site within the unit cell.', - ) - self.fract_z = Parameter( - value=fract_z, - name='fract_z', - cif_name='fract_z', - description='Fractional z-coordinate of the atom site within the unit cell.', - ) - self.occupancy = Parameter( - value=occupancy, - name='occupancy', - cif_name='occupancy', - description='Occupancy of the atom site, representing the ' - 'fraction of the site occupied by the atom type.', - ) - self.b_iso = Parameter( - value=b_iso, - name='b_iso', - units='Ų', - cif_name='B_iso_or_equiv', - description='Isotropic atomic displacement parameter (ADP) for the atom site.', - ) - # Select which of the input parameters is used for the - # as ID for the whole object - self._entry_id = label - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True - - -class AtomSites(Collection): - """Collection of AtomSite instances.""" - - # TODO: Check, if we can get rid of this property - # We could use class name instead - @property - def _type(self): - return 'category' # datablock or category - - @property - def _child_class(self): - return AtomSite diff --git a/src/easydiffraction/sample_models/components/cell.py b/src/easydiffraction/sample_models/components/cell.py deleted file mode 100644 index a40f784e..00000000 --- a/src/easydiffraction/sample_models/components/cell.py +++ /dev/null @@ -1,75 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Parameter - - -class Cell(Component): - """Represents the unit cell parameters of a sample model.""" - - @property - def category_key(self) -> str: - return 'cell' - - @property - def cif_category_key(self) -> str: - return 'cell' - - def __init__( - self, - length_a: float = 10.0, - length_b: float = 10.0, - length_c: float = 10.0, - angle_alpha: float = 90.0, - angle_beta: float = 90.0, - angle_gamma: float = 90.0, - ) -> None: - super().__init__() - - self.length_a = Parameter( - value=length_a, - name='length_a', - cif_name='length_a', - units='Å', - description='Length of the unit cell edge a.', - ) - self.length_b = Parameter( - value=length_b, - name='length_b', - cif_name='length_b', - units='Å', - description='Length of the unit cell edge b.', - ) - self.length_c = Parameter( - value=length_c, - name='length_c', - cif_name='length_c', - units='Å', - description='Length of the unit cell edge c.', - ) - self.angle_alpha = Parameter( - value=angle_alpha, - name='angle_alpha', - cif_name='angle_alpha', - units='deg', - description='Angle between edges b and c.', - ) - self.angle_beta = Parameter( - value=angle_beta, - name='angle_beta', - cif_name='angle_beta', - units='deg', - description='Angle between edges a and c.', - ) - self.angle_gamma = Parameter( - value=angle_gamma, - name='angle_gamma', - cif_name='angle_gamma', - units='deg', - description='Angle between edges a and b.', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked: bool = True diff --git a/src/easydiffraction/sample_models/components/space_group.py b/src/easydiffraction/sample_models/components/space_group.py deleted file mode 100644 index b300cea0..00000000 --- a/src/easydiffraction/sample_models/components/space_group.py +++ /dev/null @@ -1,43 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -from typing import Optional - -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Descriptor - - -class SpaceGroup(Component): - """Represents the space group of a sample model.""" - - @property - def category_key(self) -> str: - return 'space_group' - - @property - def cif_category_key(self) -> str: - return 'space_group' - - def __init__( - self, - name_h_m: str = 'P 1', - it_coordinate_system_code: Optional[int] = None, - ) -> None: - super().__init__() - - self.name_h_m = Descriptor( - value=name_h_m, - name='name_h_m', - cif_name='name_H-M_alt', - description='Hermann-Mauguin symbol of the space group.', - ) - self.it_coordinate_system_code = Descriptor( - value=it_coordinate_system_code, - name='it_coordinate_system_code', - cif_name='IT_coordinate_system_code', - description='A qualifier identifying which setting in IT is used.', - ) - - # Lock further attribute additions to prevent - # accidental modifications by users - self._locked = True diff --git a/src/easydiffraction/sample_models/sample_model/__init__.py b/src/easydiffraction/sample_models/sample_model/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/sample_models/sample_model/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/sample_models/sample_model.py b/src/easydiffraction/sample_models/sample_model/base.py similarity index 57% rename from src/easydiffraction/sample_models/sample_model.py rename to src/easydiffraction/sample_models/sample_model/base.py index 6ce82842..302af96d 100644 --- a/src/easydiffraction/sample_models/sample_model.py +++ b/src/easydiffraction/sample_models/sample_model/base.py @@ -1,86 +1,99 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from easydiffraction.core.objects import Datablock +from easydiffraction import paragraph +from easydiffraction.core.datablock import DatablockItem from easydiffraction.crystallography import crystallography as ecr -from easydiffraction.sample_models.collections.atom_sites import AtomSites -from easydiffraction.sample_models.components.cell import Cell -from easydiffraction.sample_models.components.space_group import SpaceGroup -from easydiffraction.utils.decorators import enforce_type -from easydiffraction.utils.formatting import paragraph +from easydiffraction.sample_models.categories.atom_sites import AtomSites +from easydiffraction.sample_models.categories.cell import Cell +from easydiffraction.sample_models.categories.space_group import SpaceGroup from easydiffraction.utils.utils import render_cif -class SampleModel(Datablock): - """Represents an individual structural model of a sample. +class SampleModelBase(DatablockItem): + """Base sample model and container for structural information. - Wraps crystallographic information including space group, cell, and - atomic sites. + Holds space group, unit cell and atom-site categories. The + factory is responsible for creating rich instances from CIF; + this base accepts just the ``name`` and exposes helpers for + applying symmetry. """ - # TODO: Move cif_path and cif_str out of __init__ and into separate - # methods def __init__( self, - name: str, - cif_path: str = None, - cif_str: str = None, - ): + *, + name, + ) -> None: super().__init__() self._name = name - self.space_group = SpaceGroup() - self.cell = Cell() - self.atom_sites = AtomSites() - - if cif_path: - self.load_from_cif_file(cif_path) - elif cif_str: - self.load_from_cif_string(cif_str) - - # ----------- - # Space group - # ----------- + self._cell: Cell = Cell() + self._space_group: SpaceGroup = SpaceGroup() + self._atom_sites: AtomSites = AtomSites() + self._identity.datablock_entry_name = lambda: self.name + + def __str__(self) -> str: + """Human-readable representation of this component.""" + name = self._log_name + items = ', '.join( + f'{k}={v}' + for k, v in { + 'cell': self.cell, + 'space_group': self.space_group, + 'atom_sites': self.atom_sites, + }.items() + ) + return f'<{name} ({items})>' @property - def space_group(self): - return self._space_group + def name(self) -> str: + """Model name. - @space_group.setter - @enforce_type - def space_group(self, new_space_group: SpaceGroup): - self._space_group = new_space_group + Returns: + The user-facing identifier for this model. + """ + return self._name - # ---- - # Cell - # ---- + @name.setter + def name(self, new: str) -> None: + """Update model name.""" + self._name = new @property - def cell(self): + def cell(self) -> Cell: + """Unit-cell category object.""" return self._cell @cell.setter - @enforce_type - def cell(self, new_cell: Cell): - self._cell = new_cell + def cell(self, new: Cell) -> None: + """Replace the unit-cell category object.""" + self._cell = new + + @property + def space_group(self) -> SpaceGroup: + """Space-group category object.""" + return self._space_group - # ---------- - # Atom sites - # ---------- + @space_group.setter + def space_group(self, new: SpaceGroup) -> None: + """Replace the space-group category object.""" + self._space_group = new @property - def atom_sites(self): + def atom_sites(self) -> AtomSites: + """Atom-sites collection for this model.""" return self._atom_sites @atom_sites.setter - @enforce_type - def atom_sites(self, new_atom_sites: AtomSites): - self._atom_sites = new_atom_sites + def atom_sites(self, new: AtomSites) -> None: + """Replace the atom-sites collection.""" + self._atom_sites = new # -------------------- # Symmetry constraints # -------------------- def _apply_cell_symmetry_constraints(self): + """Apply symmetry rules to unit-cell parameters in place.""" dummy_cell = { 'lattice_a': self.cell.length_a.value, 'lattice_b': self.cell.length_b.value, @@ -99,6 +112,9 @@ def _apply_cell_symmetry_constraints(self): self.cell.angle_gamma.value = dummy_cell['angle_gamma'] def _apply_atomic_coordinates_symmetry_constraints(self): + """Apply symmetry rules to fractional coordinates of atom + sites. + """ space_group_name = self.space_group.name_h_m.value space_group_coord_code = self.space_group.it_coordinate_system_code.value for atom in self.atom_sites: @@ -127,56 +143,17 @@ def _apply_atomic_coordinates_symmetry_constraints(self): atom.fract_z.value = dummy_atom['fract_z'] def _apply_atomic_displacement_symmetry_constraints(self): + """Placeholder for ADP symmetry constraints (not + implemented). + """ pass def apply_symmetry_constraints(self): + """Apply all available symmetry constraints to this model.""" self._apply_cell_symmetry_constraints() self._apply_atomic_coordinates_symmetry_constraints() self._apply_atomic_displacement_symmetry_constraints() - # ----------------- - # Creation from CIF - # ----------------- - - def load_from_cif_file(self, cif_path: str): - """Load model data from a CIF file.""" - # TODO: Implement CIF parsing here - print(f'Loading SampleModel from CIF file: {cif_path}') - print('Not implemented yet.') - - def load_from_cif_string(self, cif_str: str): - """Load model data from a CIF string.""" - # TODO: Implement CIF parsing from a string - print('Loading SampleModel from CIF string.') - print('Not implemented yet.') - # Intentionally unused, to avoid linter warning unless - # implemented later - del cif_str - - # ----------------- - # Convertion to CIF - # ----------------- - - def as_cif(self) -> str: - """Export the sample model to CIF format. - - Returns: - str: CIF string representation of the sample model. - """ - # Data block header - cif_lines = [f'data_{self.name}'] - - # Space Group - cif_lines += ['', self.space_group.as_cif()] - - # Unit Cell - cif_lines += ['', self.cell.as_cif()] - - # Atom Sites - cif_lines += ['', self.atom_sites.as_cif()] - - return '\n'.join(cif_lines) - # ------------ # Show methods # ------------ @@ -187,16 +164,19 @@ def show_structure(self): print('Not implemented yet.') def show_params(self): - """Display structural parameters (space group, unit cell, atomic + """Display structural parameters (space group, cell, atom sites). """ print(f'\nSampleModel ID: {self.name}') print(f'Space group: {self.space_group.name_h_m}') - print(f'Cell parameters: {self.cell.as_dict()}') + print(f'Cell parameters: {self.cell.as_dict}') print('Atom sites:') self.atom_sites.show() def show_as_cif(self) -> None: - cif_text: str = self.as_cif() + """Render the CIF text for this model in a terminal-friendly + view. + """ + cif_text: str = self.as_cif paragraph_title: str = paragraph(f"Sample model 🧩 '{self.name}' as cif") render_cif(cif_text, paragraph_title) diff --git a/src/easydiffraction/sample_models/sample_model/factory.py b/src/easydiffraction/sample_models/sample_model/factory.py new file mode 100644 index 00000000..9346e97c --- /dev/null +++ b/src/easydiffraction/sample_models/sample_model/factory.py @@ -0,0 +1,170 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Factory for creating sample models from simple inputs or CIF. + +Supports three argument combinations: ``name``, ``cif_path``, or +``cif_str``. Returns a minimal ``SampleModelBase`` populated from CIF +when provided, or an empty model with the given name. +""" + +from __future__ import annotations + +import gemmi + +from easydiffraction.core.factory import FactoryBase +from easydiffraction.sample_models.sample_model.base import SampleModelBase + + +class SampleModelFactory(FactoryBase): + """Create ``SampleModelBase`` instances from supported inputs.""" + + _ALLOWED_ARG_SPECS = [ + {'required': ['name'], 'optional': []}, + {'required': ['cif_path'], 'optional': []}, + {'required': ['cif_str'], 'optional': []}, + ] + + @classmethod + def create(cls, **kwargs) -> SampleModelBase: + """Create a model based on a validated argument combination. + + Keyword Args: + name: Name of the sample model to create. + cif_path: Path to a CIF file to parse. + cif_str: Raw CIF string to parse. + **kwargs: Extra args are ignored if None; only the above + three keys are supported. + + Returns: + SampleModelBase: A populated or empty model instance. + """ + # Check for valid argument combinations + user_args = {k for k, v in kwargs.items() if v is not None} + cls._validate_args(user_args, cls._ALLOWED_ARG_SPECS, cls.__name__) + + if 'cif_path' in kwargs: + return cls._create_from_cif_path(kwargs['cif_path']) + elif 'cif_str' in kwargs: + return cls._create_from_cif_str(kwargs['cif_str']) + elif 'name' in kwargs: + return SampleModelBase(name=kwargs['name']) + + # ------------------------------- + # Private creation helper methods + # ------------------------------- + + @classmethod + def _create_from_cif_path( + cls, + cif_path: str, + ) -> SampleModelBase: + """Create a model by reading and parsing a CIF file.""" + # Parse CIF and build model + doc = cls._read_cif_document_from_path(cif_path) + block = cls._pick_first_structural_block(doc) + return cls._create_model_from_block(block) + + @classmethod + def _create_from_cif_str( + cls, + cif_str: str, + ) -> SampleModelBase: + """Create a model by parsing a CIF string.""" + # Parse CIF string and build model + doc = cls._read_cif_document_from_string(cif_str) + block = cls._pick_first_structural_block(doc) + return cls._create_model_from_block(block) + + # TODO: Move to io.cif.parse? + + # ------------- + # gemmi helpers + # ------------- + + @staticmethod + def _read_cif_document_from_path(path: str) -> gemmi.cif.Document: + """Read a CIF document from a file path.""" + return gemmi.cif.read_file(path) + + @staticmethod + def _read_cif_document_from_string(text: str) -> gemmi.cif.Document: + """Read a CIF document from a raw text string.""" + return gemmi.cif.read_string(text) + + @staticmethod + def _has_structural_content(block: gemmi.cif.Block) -> bool: + """Return True if the CIF block contains structural content.""" + # Basic heuristics: atom_site loop or full set of cell params + loop = block.find_loop('_atom_site.fract_x') + if loop is not None: + return True + required_cell = [ + '_cell.length_a', + '_cell.length_b', + '_cell.length_c', + '_cell.angle_alpha', + '_cell.angle_beta', + '_cell.angle_gamma', + ] + return all(block.find_value(tag) for tag in required_cell) + + @classmethod + def _pick_first_structural_block( + cls, + doc: gemmi.cif.Document, + ) -> gemmi.cif.Block: + """Pick the most likely structural block from a CIF document.""" + # Prefer blocks with atom_site loop; else first block with cell + for block in doc: + if cls._has_structural_content(block): + return block + # As a fallback, return the sole or first block + try: + return doc.sole_block() + except Exception: + return doc[0] + + @classmethod + def _create_model_from_block( + cls, + block: gemmi.cif.Block, + ) -> SampleModelBase: + """Build a model instance from a single CIF block.""" + name = cls._extract_name_from_block(block) + model = SampleModelBase(name=name) + cls._set_space_group_from_cif_block(model, block) + cls._set_cell_from_cif_block(model, block) + cls._set_atom_sites_from_cif_block(model, block) + return model + + @classmethod + def _extract_name_from_block(cls, block: gemmi.cif.Block) -> str: + """Extract a model name from the CIF block name.""" + return block.name or 'model' + + @classmethod + def _set_space_group_from_cif_block( + cls, + model: SampleModelBase, + block: gemmi.cif.Block, + ) -> None: + """Populate the model's space group from a CIF block.""" + model.space_group.from_cif(block) + + @classmethod + def _set_cell_from_cif_block( + cls, + model: SampleModelBase, + block: gemmi.cif.Block, + ) -> None: + """Populate the model's unit cell from a CIF block.""" + model.cell.from_cif(block) + + @classmethod + def _set_atom_sites_from_cif_block( + cls, + model: SampleModelBase, + block: gemmi.cif.Block, + ) -> None: + """Populate the model's atom sites from a CIF block.""" + model.atom_sites.from_cif(block) diff --git a/src/easydiffraction/sample_models/sample_models.py b/src/easydiffraction/sample_models/sample_models.py index 0f845073..858d982f 100644 --- a/src/easydiffraction/sample_models/sample_models.py +++ b/src/easydiffraction/sample_models/sample_models.py @@ -1,124 +1,74 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import List -from typing import Optional +from typeguard import typechecked -from easydiffraction.core.objects import Collection -from easydiffraction.sample_models.sample_model import SampleModel -from easydiffraction.utils.decorators import enforce_type +from easydiffraction.core.datablock import DatablockCollection +from easydiffraction.sample_models.sample_model.base import SampleModelBase +from easydiffraction.sample_models.sample_model.factory import SampleModelFactory from easydiffraction.utils.formatting import paragraph -class SampleModels(Collection): +class SampleModels(DatablockCollection): """Collection manager for multiple SampleModel instances.""" - @property - def _child_class(self): - return SampleModel - def __init__(self) -> None: - super().__init__() # Initialize Collection - self._models = self._items # Alias for legacy support - - def add( - self, - model: Optional[SampleModel] = None, - name: Optional[str] = None, - cif_path: Optional[str] = None, - cif_str: Optional[str] = None, - ) -> None: - """Add a new sample model to the collection. - Dispatches based on input type: pre-built model or parameters - for new creation. + super().__init__(item_type=SampleModelBase) - Args: - model: An existing SampleModel instance. - name: Name for a new model if created from scratch. - cif_path: Path to a CIF file to create a model from. - cif_str: CIF content as string to create a model from. - """ - if model: - self._add_prebuilt_sample_model(model) - else: - self._create_and_add_sample_model(name, cif_path, cif_str) + # -------------------- + # Add / Remove methods + # -------------------- - def remove(self, name: str) -> None: - """Remove a sample model by its ID. + @typechecked + def add_from_cif_path(self, cif_path: str) -> None: + """Create and add a model from a CIF file path. Args: - name: ID of the model to remove. + cif_path: Path to a CIF file. """ - if name in self._models: - del self._models[name] + sample_model = SampleModelFactory.create(cif_path=cif_path) + self.add(sample_model) - def get_ids(self) -> List[str]: - """Return a list of all model IDs in the collection. + @typechecked + def add_from_cif_str(self, cif_str: str) -> None: + """Create and add a model from CIF content (string). - Returns: - List of model IDs. + Args: + cif_str: CIF file content. """ - return list(self._models.keys()) + sample_model = SampleModelFactory.create(cif_str=cif_str) + self.add(sample_model) - @property - def ids(self) -> List[str]: - """Property accessor for model IDs.""" - return self.get_ids() - - def show_names(self) -> None: - """List all model IDs in the collection.""" - print(paragraph('Defined sample models' + ' 🧩')) - print(self.get_ids()) - - def show_params(self) -> None: - """Show parameters of all sample models in the collection.""" - for model in self._models.values(): - model.show_params() + @typechecked + def add_minimal(self, name: str) -> None: + """Create and add a minimal model (defaults, no atoms). - def as_cif(self) -> str: - """Export all sample models to CIF format. - - Returns: - CIF string representation of all sample models. + Args: + name: Identifier to assign to the new model. """ - return '\n'.join([model.as_cif() for model in self._models.values()]) + sample_model = SampleModelFactory.create(name=name) + self.add(sample_model) - @enforce_type - def _add_prebuilt_sample_model(self, sample_model: SampleModel) -> None: - """Add a pre-built SampleModel instance. + @typechecked + def remove(self, name: str) -> None: + """Remove a sample model by its ID. Args: - sample_model: The SampleModel instance to add. - - Raises: - TypeError: If model is not a SampleModel instance. + name: ID of the model to remove. """ - self._models[sample_model.name] = sample_model + if name in self: + del self[name] - def _create_and_add_sample_model( - self, - name: Optional[str] = None, - cif_path: Optional[str] = None, - cif_str: Optional[str] = None, - ) -> None: - """Create a SampleModel instance and add it to the collection. + # ------------ + # Show methods + # ------------ - Args: - name: Name for the new model. - cif_path: Path to a CIF file. - cif_str: CIF content as string. + def show_names(self) -> None: + """List all model names in the collection.""" + print(paragraph('Defined sample models' + ' 🧩')) + print(self.names) - Raises: - ValueError: If neither name, cif_path, nor cif_str is - provided. - """ - if cif_path: - model = SampleModel(cif_path=cif_path) - elif cif_str: - model = SampleModel(cif_str=cif_str) - elif name: - model = SampleModel(name=name) - else: - raise ValueError('You must provide a name, cif_path, or cif_str.') - - self._models[model.name] = model + def show_params(self) -> None: + """Show parameters of all sample models in the collection.""" + for model in self.values(): + model.show_params() diff --git a/src/easydiffraction/summary/__init__.py b/src/easydiffraction/summary/__init__.py new file mode 100644 index 00000000..3e95b5e9 --- /dev/null +++ b/src/easydiffraction/summary/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easydiffraction/summary.py b/src/easydiffraction/summary/summary.py similarity index 86% rename from src/easydiffraction/summary.py rename to src/easydiffraction/summary/summary.py index a9cce642..4482f818 100644 --- a/src/easydiffraction/summary.py +++ b/src/easydiffraction/summary/summary.py @@ -51,7 +51,7 @@ def show_crystallographic_data(self) -> None: """ print(section('Crystallographic data')) - for model in self.project.sample_models._models.values(): + for model in self.project.sample_models.values(): print(paragraph('Phase datablock')) print(f'🧩 {model.name}') @@ -61,8 +61,8 @@ def show_crystallographic_data(self) -> None: print(paragraph('Cell parameters')) columns_alignment: List[str] = ['left', 'right'] cell_data = [ - [k.replace('length_', '').replace('angle_', ''), f'{v:.5f}'] - for k, v in model.cell.as_dict().items() + [p.name.replace('length_', '').replace('angle_', ''), f'{p.value:.5f}'] + for p in model.cell.parameters ] render_table( columns_alignment=columns_alignment, @@ -71,13 +71,13 @@ def show_crystallographic_data(self) -> None: print(paragraph('Atom sites')) columns_headers = [ - 'Label', - 'Type', - 'fract_x', - 'fract_y', - 'fract_z', - 'Occupancy', - 'B_iso', + 'label', + 'type', + 'x', + 'y', + 'z', + 'occ', + 'Biso', ] columns_alignment = [ 'left', @@ -111,7 +111,7 @@ def show_experimental_data(self) -> None: """ print(section('Experiments')) - for expt in self.project.experiments._experiments.values(): + for expt in self.project.experiments.values(): print(paragraph('Experiment datablock')) print(f'🔬 {expt.name}') @@ -122,20 +122,20 @@ def show_experimental_data(self) -> None: f'{expt.type.beam_mode.value}' ) - if hasattr(expt, 'instrument'): - if hasattr(expt.instrument, 'setup_wavelength'): + if 'instrument' in expt._public_attrs(): + if 'setup_wavelength' in expt.instrument._public_attrs(): print(paragraph('Wavelength')) print(f'{expt.instrument.setup_wavelength.value:.5f}') - if hasattr(expt.instrument, 'calib_twotheta_offset'): + if 'calib_twotheta_offset' in expt.instrument._public_attrs(): print(paragraph('2θ offset')) print(f'{expt.instrument.calib_twotheta_offset.value:.5f}') - if hasattr(expt, 'peak_profile_type'): + if 'peak_profile_type' in expt._public_attrs(): print(paragraph('Profile type')) print(expt.peak_profile_type) - if hasattr(expt, 'peak'): - if hasattr(expt.peak, 'broad_gauss_u'): + if 'peak' in expt._public_attrs(): + if 'broad_gauss_u' in expt.peak._public_attrs(): print(paragraph('Peak broadening (Gaussian)')) columns_alignment = ['left', 'right'] columns_data = [ @@ -147,7 +147,7 @@ def show_experimental_data(self) -> None: columns_alignment=columns_alignment, columns_data=columns_data, ) - if hasattr(expt.peak, 'broad_lorentz_x'): + if 'broad_lorentz_x' in expt.peak._public_attrs(): print(paragraph('Peak broadening (Lorentzian)')) columns_alignment = ['left', 'right'] columns_data = [ @@ -192,4 +192,6 @@ def as_cif(self) -> str: """Export the final fitted data and analysis results as CIF format. """ - return 'To be added...' + from easydiffraction.io.cif.serialize import summary_to_cif + + return summary_to_cif(self) diff --git a/src/easydiffraction/utils/decorators.py b/src/easydiffraction/utils/decorators.py deleted file mode 100644 index 7bc23c2c..00000000 --- a/src/easydiffraction/utils/decorators.py +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors -# SPDX-License-Identifier: BSD-3-Clause - -import inspect -import typing - -import numpy as np - - -def enforce_type(func): - sig = inspect.signature(func) - - def wrapper(self, *args, **kwargs): - bound_args = sig.bind(self, *args, **kwargs) - bound_args.apply_defaults() - - for name, value in list(bound_args.arguments.items())[1:]: # skip 'self' - expected_type = sig.parameters[name].annotation - if expected_type is inspect._empty: - continue # no annotation, skip - - origin = typing.get_origin(expected_type) - if origin is not None: - args_types = typing.get_args(expected_type) - valid_types = tuple(t for t in args_types if isinstance(t, type)) - if not any(isinstance(value, t) for t in valid_types): - raise TypeError( - f"Parameter '{name}': expected {expected_type}, " - f'got {type(value).__name__}.' - ) - else: - if isinstance(expected_type, type): - if not isinstance(value, expected_type): - raise TypeError( - f"Parameter '{name}': expected {expected_type.__name__}, " - f'got {type(value).__name__}.' - ) - elif isinstance(expected_type, str) and expected_type == 'np.ndarray': - if not isinstance(value, np.ndarray): - raise TypeError( - f"Parameter '{name}': expected np.ndarray, got {type(value).__name__}." - ) - else: - if hasattr(expected_type, '__name__'): - if type(value).__name__ != expected_type.__name__: - raise TypeError( - f"Parameter '{name}': expected {expected_type}, " - f'got {type(value).__name__}.' - ) - else: - if type(value).__name__ != str(expected_type): - raise TypeError( - f"Parameter '{name}': expected {expected_type}, " - f'got {type(value).__name__}.' - ) - - return func(self, *args, **kwargs) - - return wrapper diff --git a/src/easydiffraction/utils/logging.py b/src/easydiffraction/utils/logging.py new file mode 100644 index 00000000..b059ae3d --- /dev/null +++ b/src/easydiffraction/utils/logging.py @@ -0,0 +1,310 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import logging +import os +import warnings +from contextlib import suppress +from enum import Enum +from enum import IntEnum +from enum import auto +from typing import TYPE_CHECKING + +if TYPE_CHECKING: # pragma: no cover + from types import TracebackType + +from rich.logging import RichHandler + + +class Logger: + """Centralized logging with Rich formatting and two modes. + + Environment variables: + ED_LOG_MODE: set default mode ('verbose' or 'compact') + ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', etc.) + """ + + class Mode(Enum): + """Output modes (see :class:`Logger`).""" + + VERBOSE = 'verbose' # rich traceback panel + COMPACT = 'compact' # single line; no traceback + + class Level(IntEnum): + """Mirror stdlib logging levels.""" + + DEBUG = logging.DEBUG + INFO = logging.INFO + WARNING = logging.WARNING + ERROR = logging.ERROR + CRITICAL = logging.CRITICAL + + class Reaction(Enum): + """Reaction to errors (see :class:`Logger`).""" + + RAISE = auto() + WARN = auto() + + _logger = logging.getLogger('easydiffraction') + _configured = False + _mode: 'Logger.Mode' = Mode.VERBOSE + _reaction: 'Logger.Reaction' = Reaction.RAISE # TODO: not default? + + # ---------------- environment detection ---------------- + @staticmethod + def _in_jupyter() -> bool: # pragma: no cover - heuristic + try: + from IPython import get_ipython # type: ignore[import-not-found] + + return get_ipython() is not None + except Exception: # noqa: BLE001 + return False + + @staticmethod + def _in_pytest() -> bool: + import sys + + return 'pytest' in sys.modules + + # ---------------- configuration ---------------- + @classmethod + def configure( + cls, + *, + mode: 'Logger.Mode' | None = None, + level: 'Logger.Level' | None = None, + reaction: 'Logger.Reaction' | None = None, + rich_tracebacks: bool | None = None, + ) -> None: + """Configure logger. + + mode: default COMPACT in Jupyter else VERBOSE + level: minimum log level + rich_tracebacks: override automatic choice + + Environment variables: + ED_LOG_MODE: set default mode ('verbose' or 'compact') + ED_LOG_LEVEL: set default level ('DEBUG', 'INFO', etc.) + """ + env_mode = os.getenv('ED_LOG_MODE') + env_level = os.getenv('ED_LOG_LEVEL') + env_reaction = os.getenv('ED_LOG_REACTION') + + if mode is None and env_mode is not None: + with suppress(ValueError): + mode = cls.Mode(env_mode.lower()) + + if level is None and env_level is not None: + with suppress(KeyError): + level = cls.Level[env_level.upper()] + + if reaction is None and env_reaction is not None: + with suppress(KeyError): + reaction = cls.Reaction[env_reaction.upper()] + + if mode is None: + # Default to VERBOSE even in Jupyter unless explicitly set + mode = cls.Mode.VERBOSE + if level is None: + level = cls.Level.INFO + if reaction is None: + reaction = cls.Reaction.RAISE + cls._mode = mode + cls._reaction = reaction + + if rich_tracebacks is None: + rich_tracebacks = mode == cls.Mode.VERBOSE + + log = cls._logger + log.handlers.clear() + log.propagate = False + log.setLevel(int(level)) + + from rich.console import Console + + # Enable rich tracebacks inside Jupyter environments + if cls._in_jupyter(): + from rich import traceback + + traceback.install( + show_locals=False, + suppress=['easydiffraction'], + # max_frames=10 if mode == cls.Mode.VERBOSE else 1, + # word_wrap=False, + # extra_lines=0, # no extra context lines + # locals_max_length=0, # no local vars shown + # locals_max_string=0, # no local string previews + ) + console = Console( + width=120, + # color_system="truecolor", + force_jupyter=False, + # force_terminal=False, + # force_interactive=True, + # legacy_windows=False, + # soft_wrap=True, + ) + handler = RichHandler( + rich_tracebacks=rich_tracebacks, + markup=True, + show_time=False, + show_path=False, + tracebacks_show_locals=False, + tracebacks_suppress=['easydiffraction'], + tracebacks_max_frames=10, + console=console, + ) + handler.setFormatter(logging.Formatter('%(message)s')) + log.addHandler(handler) + cls._configured = True + + import sys + + if rich_tracebacks and mode == cls.Mode.VERBOSE: + if not hasattr(cls, '_orig_excepthook'): + cls._orig_excepthook = sys.excepthook # type: ignore[attr-defined] + + def _aligned_excepthook( + exc_type: type[BaseException], + exc: BaseException, + tb: TracebackType | None, + ) -> None: + original_args = getattr(exc, 'args', tuple()) + message = str(exc) + with suppress(Exception): + exc.args = tuple() + try: + cls._logger.error(message, exc_info=(exc_type, exc, tb)) + except Exception: # pragma: no cover + cls._logger.error('Unhandled exception (logging failure)') + with suppress(Exception): + exc.args = original_args + + sys.excepthook = _aligned_excepthook # type: ignore[assignment] + elif mode == cls.Mode.COMPACT: + import sys + + if not hasattr(cls, '_orig_excepthook'): + cls._orig_excepthook = sys.excepthook # type: ignore[attr-defined] + + def _compact_excepthook( + _exc_type: type[BaseException], + exc: BaseException, + _tb: TracebackType | None, + ) -> None: + # Use Rich logger to keep formatting in terminal + cls._logger.error(str(exc)) + raise SystemExit(1) + + sys.excepthook = _compact_excepthook # type: ignore[assignment] + + # Disable Jupyter/IPython tracebacks properly + cls._install_jupyter_traceback_suppressor() + else: + if hasattr(cls, '_orig_excepthook'): + sys.excepthook = cls._orig_excepthook # type: ignore[attr-defined] + + @classmethod + def _install_jupyter_traceback_suppressor(cls) -> None: + """Install traceback suppressor in Jupyter, safely and lint- + clean. + """ + try: + from IPython import get_ipython + + ip = get_ipython() + if ip is not None: + + def _suppress_jupyter_traceback( + _shell, + _etype, + _evalue, + _tb, + _tb_offset=None, + ): + cls._logger.error(str(_evalue)) + return None + + ip.set_custom_exc((BaseException,), _suppress_jupyter_traceback) + except Exception as err: + msg = f'Failed to install Jupyter traceback suppressor: {err!r}' + cls._logger.debug(msg) + + # ---------------- helpers ---------------- + @classmethod + def set_mode(cls, mode: 'Logger.Mode') -> None: + cls.configure(mode=mode, level=cls.Level(cls._logger.level)) + + @classmethod + def set_level(cls, level: 'Logger.Level') -> None: + cls.configure(mode=cls._mode, level=level) + + @classmethod + def mode(cls) -> 'Logger.Mode': + return cls._mode + + @classmethod + def _lazy_config(cls) -> None: + if not cls._configured: # pragma: no cover - trivial + cls.configure() + + # ---------------- core routing ---------------- + @classmethod + def handle( + cls, + message: str, + *, + level: 'Logger.Level' = Level.ERROR, + exc_type: type[BaseException] | None = AttributeError, + ) -> None: + """Route a log message (see class docs for policy).""" + cls._lazy_config() + if exc_type is not None: + if exc_type is UserWarning: + if cls._in_pytest(): + # Always issue a real warning so pytest can catch it + warnings.warn(message, UserWarning, stacklevel=2) + else: + # Outside pytest → normal Rich logging + cls._logger.warning(message) + return + if cls._mode is cls.Mode.VERBOSE: + raise exc_type(message) + if cls._mode is cls.Mode.COMPACT: + raise exc_type(message) from None + cls._logger.log(int(level), message) + + # ---------------- convenience API ---------------- + @classmethod + def debug(cls, message: str) -> None: + cls.handle(message, level=cls.Level.DEBUG, exc_type=None) + + @classmethod + def info(cls, message: str) -> None: + cls.handle(message, level=cls.Level.INFO, exc_type=None) + + @classmethod + def warning(cls, message: str, exc_type: type[BaseException] | None = None) -> None: + cls.handle(message, level=cls.Level.WARNING, exc_type=exc_type) + + @classmethod + def error(cls, message: str, exc_type: type[BaseException] = AttributeError) -> None: + if cls._reaction is cls.Reaction.RAISE: + cls.handle(message, level=cls.Level.ERROR, exc_type=exc_type) + elif cls._reaction is cls.Reaction.WARN: + cls.handle(message, level=cls.Level.WARNING, exc_type=UserWarning) + + @classmethod + def critical(cls, message: str, exc_type: type[BaseException] = RuntimeError) -> None: + cls.handle(message, level=cls.Level.CRITICAL, exc_type=exc_type) + + @classmethod + def exception(cls, message: str) -> None: + """Log current exception from inside ``except`` block.""" + cls._lazy_config() + cls._logger.error(message, exc_info=True) + + +log = Logger # ergonomic alias diff --git a/src/easydiffraction/utils/utils.py b/src/easydiffraction/utils/utils.py index bf21835a..5e3b7de2 100644 --- a/src/easydiffraction/utils/utils.py +++ b/src/easydiffraction/utils/utils.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -import importlib import io import json import os @@ -10,7 +9,9 @@ import zipfile from importlib.metadata import PackageNotFoundError from importlib.metadata import version +from importlib.util import find_spec from typing import List +from typing import Optional from urllib.parse import urlparse import numpy as np @@ -18,6 +19,9 @@ import pooch from packaging.version import Version from tabulate import tabulate +from uncertainties import UFloat +from uncertainties import ufloat +from uncertainties import ufloat_fromstr try: import IPython @@ -405,7 +409,7 @@ def is_colab() -> bool: bool: True if running in Google Colab PyCharm, False otherwise. """ try: - return importlib.util.find_spec('google.colab') is not None + return find_spec('google.colab') is not None except ModuleNotFoundError: return False @@ -689,3 +693,47 @@ def get_value_from_xye_header(file_path, key): return float(match.group(1)) else: raise ValueError(f'{key} not found in the header.') + + +def str_to_ufloat(s: Optional[str], default: Optional[float] = None) -> UFloat: + """Parse a CIF-style numeric string into a `ufloat` with an optional + uncertainty. + + Examples of supported input: + - "3.566" → ufloat(3.566, nan) + - "3.566(2)" → ufloat(3.566, 0.002) + - None → ufloat(default, nan) + + Behavior: + - If the input string contains a value with parentheses (e.g. + "3.566(2)"), the number in parentheses is interpreted as an + estimated standard deviation (esd) in the last digit(s). + - If the input string has no parentheses, an uncertainty of NaN is + assigned to indicate "no esd provided". + - If parsing fails, the function falls back to the given `default` + value with uncertainty NaN. + + Parameters + ---------- + s : str or None + Numeric string in CIF format (e.g. "3.566", "3.566(2)") or None. + default : float or None, optional + Default value to use if `s` is None or parsing fails. + Defaults to None. + + Returns: + ------- + UFloat + An `uncertainties.UFloat` object with the parsed value and + uncertainty. The uncertainty will be NaN if not specified or + parsing failed. + """ + if s is None: + return ufloat(default, np.nan) + + if '(' not in s and ')' not in s: + s = f'{s}(nan)' + try: + return ufloat_fromstr(s) + except Exception: + return ufloat(default, np.nan) diff --git a/tests/functional/fitting/test_pair-distribution-function.py b/tests/functional/fitting/test_pair-distribution-function.py index 70f03d7a..a1df4ff8 100644 --- a/tests/functional/fitting/test_pair-distribution-function.py +++ b/tests/functional/fitting/test_pair-distribution-function.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile @@ -13,16 +16,28 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None: project = ed.Project() # Set sample model - project.sample_models.add(name='nacl') + project.sample_models.add_minimal(name='nacl') sample_model = project.sample_models['nacl'] sample_model.space_group.name_h_m = 'F m -3 m' sample_model.space_group.it_coordinate_system_code = '1' sample_model.cell.length_a = 5.6018 - sample_model.atom_sites.add( - label='Na', type_symbol='Na', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=1.1053 + sample_model.atom_sites.add_from_args( + label='Na', + type_symbol='Na', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=1.1053, ) - sample_model.atom_sites.add( - label='Cl', type_symbol='Cl', fract_x=0.5, fract_y=0.5, fract_z=0.5, wyckoff_letter='b', b_iso=0.5708 + sample_model.atom_sites.add_from_args( + label='Cl', + type_symbol='Cl', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5708, ) # Set experiment @@ -44,7 +59,7 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None: experiment.peak.sharp_delta_1 = 0 experiment.peak.sharp_delta_2 = 3.5041 experiment.peak.damp_particle_diameter = 0 - experiment.linked_phases.add(id='nacl', scale=0.4254) + experiment.linked_phases.add_from_args(id='nacl', scale=0.4254) # Select fitting parameters sample_model.cell.length_a.free = True @@ -59,7 +74,8 @@ def test_single_fit_pdf_xray_pd_cw_nacl() -> None: project.analysis.fit() # Compare fit quality - assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=1.48, decimal=2) + chi2 = project.analysis.fit_results.reduced_chi_square + assert_almost_equal(chi2, desired=1.48, decimal=2) @pytest.mark.fast @@ -67,13 +83,19 @@ def test_single_fit_pdf_neutron_pd_cw_ni(): project = ed.Project() # Set sample model - project.sample_models.add(name='ni') + project.sample_models.add_minimal(name='ni') sample_model = project.sample_models['ni'] sample_model.space_group.name_h_m.value = 'F m -3 m' sample_model.space_group.it_coordinate_system_code = '1' sample_model.cell.length_a = 3.526 - sample_model.atom_sites.add( - label='Ni', type_symbol='Ni', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=0.4281 + sample_model.atom_sites.add_from_args( + label='Ni', + type_symbol='Ni', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.4281, ) # Set experiment @@ -94,7 +116,7 @@ def test_single_fit_pdf_neutron_pd_cw_ni(): experiment.peak.sharp_delta_1 = 0 experiment.peak.sharp_delta_2 = 2.5587 experiment.peak.damp_particle_diameter = 0 - experiment.linked_phases.add(id='ni', scale=0.9892) + experiment.linked_phases.add_from_args(id='ni', scale=0.9892) # Select fitting parameters sample_model.cell.length_a.free = True @@ -108,19 +130,28 @@ def test_single_fit_pdf_neutron_pd_cw_ni(): project.analysis.fit() # Compare fit quality - assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=207.1, decimal=1) + chi2 = project.analysis.fit_results.reduced_chi_square + assert_almost_equal(chi2, desired=207.1, decimal=1) def test_single_fit_pdf_neutron_pd_tof_si(): project = ed.Project() # Set sample model - project.sample_models.add(name='si') + project.sample_models.add_minimal(name='si') sample_model = project.sample_models['si'] sample_model.space_group.name_h_m.value = 'F d -3 m' sample_model.space_group.it_coordinate_system_code = '1' sample_model.cell.length_a = 5.4306 - sample_model.atom_sites.add(label='Si', type_symbol='Si', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=0.717) + sample_model.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.717, + ) # Set experiment data_file = 'NOM_9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr' @@ -140,7 +171,7 @@ def test_single_fit_pdf_neutron_pd_tof_si(): experiment.peak.sharp_delta_1 = 2.54 experiment.peak.sharp_delta_2 = -1.7525 experiment.peak.damp_particle_diameter = 0 - experiment.linked_phases.add(id='si', scale=1.2728) + experiment.linked_phases.add_from_args(id='si', scale=1.2728) # Select fitting parameters project.sample_models['si'].cell.length_a.free = True @@ -156,7 +187,8 @@ def test_single_fit_pdf_neutron_pd_tof_si(): project.analysis.fit() # Compare fit quality - assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=170.54, decimal=1) + chi2 = project.analysis.fit_results.reduced_chi_square + assert_almost_equal(chi2, desired=170.54, decimal=1) if __name__ == '__main__': diff --git a/tests/functional/fitting/test_powder-diffraction_constant-wavelength.py b/tests/functional/fitting/test_powder-diffraction_constant-wavelength.py index 98c0a5fc..81ba8ef2 100644 --- a/tests/functional/fitting/test_powder-diffraction_constant-wavelength.py +++ b/tests/functional/fitting/test_powder-diffraction_constant-wavelength.py @@ -1,12 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile import pytest from numpy.testing import assert_almost_equal -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository TEMP_DIR = tempfile.gettempdir() @@ -14,18 +17,52 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None: # Set sample model - model = SampleModel('lbco') + model = SampleModelFactory.create(name='lbco') model.space_group.name_h_m = 'P m -3 m' model.cell.length_a = 3.88 - model.atom_sites.add('La', 'La', 0, 0, 0, occupancy=0.5, b_iso=0.1) - model.atom_sites.add('Ba', 'Ba', 0, 0, 0, occupancy=0.5, b_iso=0.1) - model.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, b_iso=0.1) - model.atom_sites.add('O', 'O', 0, 0.5, 0.5, b_iso=0.1) + model.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + occupancy=0.5, + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + occupancy=0.5, + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=0.1, + ) # Set experiment data_file = 'hrpt_lbco.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) + expt = ExperimentFactory.create(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) expt.instrument.setup_wavelength = 1.494 expt.instrument.calib_twotheta_offset = 0 expt.peak.broad_gauss_u = 0.1 @@ -33,9 +70,9 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None: expt.peak.broad_gauss_w = 0.2 expt.peak.broad_lorentz_x = 0 expt.peak.broad_lorentz_y = 0 - expt.linked_phases.add('lbco', scale=5.0) - expt.background.add(x=10, y=170) - expt.background.add(x=165, y=170) + expt.linked_phases.add_from_args(id='lbco', scale=5.0) + expt.background.add_from_args(x=10, y=170) + expt.background.add_from_args(x=165, y=170) # Create project project = Project() @@ -93,7 +130,7 @@ def test_single_fit_neutron_pd_cwl_lbco() -> None: @pytest.mark.fast def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: # Set sample model - model = SampleModel('lbco') + model = SampleModelFactory.create(name='lbco') space_group = model.space_group space_group.name_h_m = 'P m -3 m' @@ -102,16 +139,50 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: cell.length_a = 3.8909 atom_sites = model.atom_sites - atom_sites.add('La', 'La', 0, 0, 0, b_iso=1.0, occupancy=0.5) - atom_sites.add('Ba', 'Ba', 0, 0, 0, b_iso=1.0, occupancy=0.5) - atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, b_iso=1.0) - atom_sites.add('O', 'O', 0, 0.5, 0.5, b_iso=1.0) + atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=1.0, + occupancy=0.5, + ) + atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=1.0, + occupancy=0.5, + ) + atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=1.0, + ) + atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=1.0, + ) # Set experiment data_file = 'hrpt_lbco.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) + expt = ExperimentFactory.create(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) instrument = expt.instrument instrument.setup_wavelength = 1.494 @@ -125,18 +196,18 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: peak.broad_lorentz_y = 0.0797 background = expt.background - background.add(x=10, y=174.3) - background.add(x=20, y=159.8) - background.add(x=30, y=167.9) - background.add(x=50, y=166.1) - background.add(x=70, y=172.3) - background.add(x=90, y=171.1) - background.add(x=110, y=172.4) - background.add(x=130, y=182.5) - background.add(x=150, y=173.0) - background.add(x=165, y=171.1) - - expt.linked_phases.add('lbco', scale=9.0976) + background.add_from_args(x=10, y=174.3) + background.add_from_args(x=20, y=159.8) + background.add_from_args(x=30, y=167.9) + background.add_from_args(x=50, y=166.1) + background.add_from_args(x=70, y=172.3) + background.add_from_args(x=90, y=171.1) + background.add_from_args(x=110, y=172.4) + background.add_from_args(x=130, y=182.5) + background.add_from_args(x=150, y=173.0) + background.add_from_args(x=165, y=171.1) + + expt.linked_phases.add_from_args(id='lbco', scale=9.0976) # Create project project = Project() @@ -182,14 +253,18 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: # ------------ 2nd fitting ------------ # Set aliases for parameters - project.analysis.aliases.add(label='biso_La', param_uid=atom_sites['La'].b_iso.uid) - project.analysis.aliases.add(label='biso_Ba', param_uid=atom_sites['Ba'].b_iso.uid) - project.analysis.aliases.add(label='occ_La', param_uid=atom_sites['La'].occupancy.uid) - project.analysis.aliases.add(label='occ_Ba', param_uid=atom_sites['Ba'].occupancy.uid) + project.analysis.aliases.add_from_args(label='biso_La', param_uid=atom_sites['La'].b_iso.uid) + project.analysis.aliases.add_from_args(label='biso_Ba', param_uid=atom_sites['Ba'].b_iso.uid) + project.analysis.aliases.add_from_args( + label='occ_La', param_uid=atom_sites['La'].occupancy.uid + ) + project.analysis.aliases.add_from_args( + label='occ_Ba', param_uid=atom_sites['Ba'].occupancy.uid + ) # Set constraints - project.analysis.constraints.add(lhs_alias='biso_Ba', rhs_expr='biso_La') - project.analysis.constraints.add(lhs_alias='occ_Ba', rhs_expr='1 - occ_La') + project.analysis.constraints.add_from_args(lhs_alias='biso_Ba', rhs_expr='biso_La') + project.analysis.constraints.add_from_args(lhs_alias='occ_Ba', rhs_expr='1 - occ_La') # Apply constraints project.analysis.apply_constraints() @@ -211,22 +286,62 @@ def test_single_fit_neutron_pd_cwl_lbco_with_constraints() -> None: def test_fit_neutron_pd_cwl_hs() -> None: # Set sample model - model = SampleModel('hs') + model = SampleModelFactory.create(name='hs') model.space_group.name_h_m = 'R -3 m' model.space_group.it_coordinate_system_code = 'h' model.cell.length_a = 6.8615 model.cell.length_c = 14.136 - model.atom_sites.add('Zn', 'Zn', 0, 0, 0.5, wyckoff_letter='b', b_iso=0.1) - model.atom_sites.add('Cu', 'Cu', 0.5, 0, 0, wyckoff_letter='e', b_iso=1.2) - model.atom_sites.add('O', 'O', 0.206, -0.206, 0.061, wyckoff_letter='h', b_iso=0.7) - model.atom_sites.add('Cl', 'Cl', 0, 0, 0.197, wyckoff_letter='c', b_iso=1.1) - model.atom_sites.add('H', '2H', 0.132, -0.132, 0.09, wyckoff_letter='h', b_iso=2.3) + model.atom_sites.add_from_args( + label='Zn', + type_symbol='Zn', + fract_x=0, + fract_y=0, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Cu', + type_symbol='Cu', + fract_x=0.5, + fract_y=0, + fract_z=0, + wyckoff_letter='e', + b_iso=1.2, + ) + model.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0.206, + fract_y=-0.206, + fract_z=0.061, + wyckoff_letter='h', + b_iso=0.7, + ) + model.atom_sites.add_from_args( + label='Cl', + type_symbol='Cl', + fract_x=0, + fract_y=0, + fract_z=0.197, + wyckoff_letter='c', + b_iso=1.1, + ) + model.atom_sites.add_from_args( + label='H', + type_symbol='2H', + fract_x=0.132, + fract_y=-0.132, + fract_z=0.09, + wyckoff_letter='h', + b_iso=2.3, + ) model.apply_symmetry_constraints() # Set experiment data_file = 'hrpt_hs.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) + expt = ExperimentFactory.create(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) expt.instrument.setup_wavelength = 1.89 expt.instrument.calib_twotheta_offset = 0.0 expt.peak.broad_gauss_u = 0.1579 @@ -234,16 +349,16 @@ def test_fit_neutron_pd_cwl_hs() -> None: expt.peak.broad_gauss_w = 0.3498 expt.peak.broad_lorentz_x = 0.2927 expt.peak.broad_lorentz_y = 0 - expt.background.add(x=4.4196, y=648.413) - expt.background.add(x=6.6207, y=523.788) - expt.background.add(x=10.4918, y=454.938) - expt.background.add(x=15.4634, y=435.913) - expt.background.add(x=45.6041, y=472.972) - expt.background.add(x=74.6844, y=486.606) - expt.background.add(x=103.4187, y=472.409) - expt.background.add(x=121.6311, y=496.734) - expt.background.add(x=159.4116, y=473.146) - expt.linked_phases.add('hs', scale=0.492) + expt.background.add_from_args(x=4.4196, y=648.413) + expt.background.add_from_args(x=6.6207, y=523.788) + expt.background.add_from_args(x=10.4918, y=454.938) + expt.background.add_from_args(x=15.4634, y=435.913) + expt.background.add_from_args(x=45.6041, y=472.972) + expt.background.add_from_args(x=74.6844, y=486.606) + expt.background.add_from_args(x=103.4187, y=472.409) + expt.background.add_from_args(x=121.6311, y=496.734) + expt.background.add_from_args(x=159.4116, y=473.146) + expt.linked_phases.add_from_args(id='hs', scale=0.492) # Create project project = Project() diff --git a/tests/functional/fitting/test_powder-diffraction_joint-fit.py b/tests/functional/fitting/test_powder-diffraction_joint-fit.py index a9b505a6..2cfb97f7 100644 --- a/tests/functional/fitting/test_powder-diffraction_joint-fit.py +++ b/tests/functional/fitting/test_powder-diffraction_joint-fit.py @@ -1,12 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile import pytest from numpy.testing import assert_almost_equal -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository TEMP_DIR = tempfile.gettempdir() @@ -15,21 +18,61 @@ @pytest.mark.fast def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: # Set sample model - model = SampleModel('pbso4') - model.space_group.name_h_m.value = 'P n m a' - model.cell.length_a.value = 8.47 - model.cell.length_b.value = 5.39 - model.cell.length_c.value = 6.95 - model.atom_sites.add('Pb', 'Pb', 0.1876, 0.25, 0.167, b_iso=1.37) - model.atom_sites.add('S', 'S', 0.0654, 0.25, 0.684, b_iso=0.3777) - model.atom_sites.add('O1', 'O', 0.9082, 0.25, 0.5954, b_iso=1.9764) - model.atom_sites.add('O2', 'O', 0.1935, 0.25, 0.5432, b_iso=1.4456) - model.atom_sites.add('O3', 'O', 0.0811, 0.0272, 0.8086, b_iso=1.2822) + model = SampleModelFactory.create(name='pbso4') + model.space_group.name_h_m = 'P n m a' + model.cell.length_a = 8.47 + model.cell.length_b = 5.39 + model.cell.length_c = 6.95 + model.atom_sites.add_from_args( + label='Pb', + type_symbol='Pb', + fract_x=0.1876, + fract_y=0.25, + fract_z=0.167, + wyckoff_letter='c', + b_iso=1.37, + ) + model.atom_sites.add_from_args( + label='S', + type_symbol='S', + fract_x=0.0654, + fract_y=0.25, + fract_z=0.684, + wyckoff_letter='c', + b_iso=0.3777, + ) + model.atom_sites.add_from_args( + label='O1', + type_symbol='O', + fract_x=0.9082, + fract_y=0.25, + fract_z=0.5954, + wyckoff_letter='c', + b_iso=1.9764, + ) + model.atom_sites.add_from_args( + label='O2', + type_symbol='O', + fract_x=0.1935, + fract_y=0.25, + fract_z=0.5432, + wyckoff_letter='c', + b_iso=1.4456, + ) + model.atom_sites.add_from_args( + label='O3', + type_symbol='O', + fract_x=0.0811, + fract_y=0.0272, + fract_z=0.8086, + wyckoff_letter='d', + b_iso=1.2822, + ) # Set experiments data_file = 'd1a_pbso4_first-half.dat' download_from_repository(data_file, destination=TEMP_DIR) - expt1 = Experiment(name='npd1', data_path=os.path.join(TEMP_DIR, data_file)) + expt1 = ExperimentFactory.create(name='npd1', data_path=os.path.join(TEMP_DIR, data_file)) expt1.instrument.setup_wavelength = 1.91 expt1.instrument.calib_twotheta_offset = -0.1406 expt1.peak.broad_gauss_u = 0.139 @@ -37,7 +80,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: expt1.peak.broad_gauss_w = 0.386 expt1.peak.broad_lorentz_x = 0 expt1.peak.broad_lorentz_y = 0.0878 - expt1.linked_phases.add('pbso4', scale=1.46) + expt1.linked_phases.add_from_args(id='pbso4', scale=1.46) expt1.background_type = 'line-segment' for x, y in [ (11.0, 206.1624), @@ -49,11 +92,11 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: (120.0, 244.4525), (153.0, 226.0595), ]: - expt1.background.add(x, y) + expt1.background.add_from_args(x=x, y=y) data_file = 'd1a_pbso4_second-half.dat' download_from_repository(data_file, destination=TEMP_DIR) - expt2 = Experiment(name='npd2', data_path=os.path.join(TEMP_DIR, data_file)) + expt2 = ExperimentFactory.create(name='npd2', data_path=os.path.join(TEMP_DIR, data_file)) expt2.instrument.setup_wavelength = 1.91 expt2.instrument.calib_twotheta_offset = -0.1406 expt2.peak.broad_gauss_u = 0.139 @@ -61,7 +104,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: expt2.peak.broad_gauss_w = 0.386 expt2.peak.broad_lorentz_x = 0 expt2.peak.broad_lorentz_y = 0.0878 - expt2.linked_phases.add('pbso4', scale=1.46) + expt2.linked_phases.add_from_args(id='pbso4', scale=1.46) expt2.background_type = 'line-segment' for x, y in [ (11.0, 206.1624), @@ -73,7 +116,7 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: (120.0, 244.4525), (153.0, 226.0595), ]: - expt2.background.add(x, y) + expt2.background.add_from_args(x=x, y=y) # Create project project = Project() @@ -101,21 +144,61 @@ def test_joint_fit_split_dataset_neutron_pd_cwl_pbso4() -> None: @pytest.mark.fast def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: # Set sample model - model = SampleModel('pbso4') + model = SampleModelFactory.create(name='pbso4') model.space_group.name_h_m = 'P n m a' model.cell.length_a = 8.47 model.cell.length_b = 5.39 model.cell.length_c = 6.95 - model.atom_sites.add('Pb', 'Pb', 0.1876, 0.25, 0.167, b_iso=1.37) - model.atom_sites.add('S', 'S', 0.0654, 0.25, 0.684, b_iso=0.3777) - model.atom_sites.add('O1', 'O', 0.9082, 0.25, 0.5954, b_iso=1.9764) - model.atom_sites.add('O2', 'O', 0.1935, 0.25, 0.5432, b_iso=1.4456) - model.atom_sites.add('O3', 'O', 0.0811, 0.0272, 0.8086, b_iso=1.2822) + model.atom_sites.add_from_args( + label='Pb', + type_symbol='Pb', + fract_x=0.1876, + fract_y=0.25, + fract_z=0.167, + wyckoff_letter='c', + b_iso=1.37, + ) + model.atom_sites.add_from_args( + label='S', + type_symbol='S', + fract_x=0.0654, + fract_y=0.25, + fract_z=0.684, + wyckoff_letter='c', + b_iso=0.3777, + ) + model.atom_sites.add_from_args( + label='O1', + type_symbol='O', + fract_x=0.9082, + fract_y=0.25, + fract_z=0.5954, + wyckoff_letter='c', + b_iso=1.9764, + ) + model.atom_sites.add_from_args( + label='O2', + type_symbol='O', + fract_x=0.1935, + fract_y=0.25, + fract_z=0.5432, + wyckoff_letter='c', + b_iso=1.4456, + ) + model.atom_sites.add_from_args( + label='O3', + type_symbol='O', + fract_x=0.0811, + fract_y=0.0272, + fract_z=0.8086, + wyckoff_letter='d', + b_iso=1.2822, + ) # Set experiments data_file = 'd1a_pbso4.dat' download_from_repository(data_file, destination=TEMP_DIR) - expt1 = Experiment( + expt1 = ExperimentFactory.create( name='npd', data_path=os.path.join(TEMP_DIR, data_file), radiation_probe='neutron', @@ -127,7 +210,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: expt1.peak.broad_gauss_w = 0.386 expt1.peak.broad_lorentz_x = 0 expt1.peak.broad_lorentz_y = 0.088 - expt1.linked_phases.add('pbso4', scale=1.5) + expt1.linked_phases.add_from_args(id='pbso4', scale=1.5) for x, y in [ (11.0, 206.1624), (15.0, 194.75), @@ -138,11 +221,11 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: (120.0, 244.4525), (153.0, 226.0595), ]: - expt1.background.add(x, y) + expt1.background.add_from_args(x=x, y=y) data_file = 'lab_pbso4.dat' download_from_repository(data_file, destination=TEMP_DIR) - expt2 = Experiment( + expt2 = ExperimentFactory.create( name='xrd', data_path=os.path.join(TEMP_DIR, data_file), radiation_probe='xray', @@ -154,7 +237,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: expt2.peak.broad_gauss_w = 0.021272 expt2.peak.broad_lorentz_x = 0 expt2.peak.broad_lorentz_y = 0.057691 - expt2.linked_phases.add('pbso4', scale=0.001) + expt2.linked_phases.add_from_args(id='pbso4', scale=0.001) for x, y in [ (11.0, 141.8516), (13.0, 102.8838), @@ -165,7 +248,7 @@ def test_joint_fit_neutron_xray_pd_cwl_pbso4() -> None: (90.0, 113.7473), (110.0, 132.4643), ]: - expt2.background.add(x, y) + expt2.background.add_from_args(x=x, y=y) # Create project project = Project() diff --git a/tests/functional/fitting/test_powder-diffraction_multiphase.py b/tests/functional/fitting/test_powder-diffraction_multiphase.py index c75bc110..c3595cba 100644 --- a/tests/functional/fitting/test_powder-diffraction_multiphase.py +++ b/tests/functional/fitting/test_powder-diffraction_multiphase.py @@ -1,11 +1,14 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile from numpy.testing import assert_almost_equal -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository TEMP_DIR = tempfile.gettempdir() @@ -13,25 +16,67 @@ def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None: # Set sample models - model_1 = SampleModel('lbco') + model_1 = SampleModelFactory.create(name='lbco') model_1.space_group.name_h_m = 'P m -3 m' model_1.space_group.it_coordinate_system_code = '1' model_1.cell.length_a = 3.8909 - model_1.atom_sites.add('La', 'La', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5) - model_1.atom_sites.add('Ba', 'Ba', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5) - model_1.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, wyckoff_letter='b', b_iso=0.2567) - model_1.atom_sites.add('O', 'O', 0, 0.5, 0.5, wyckoff_letter='c', b_iso=1.4041) + model_1.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.2, + occupancy=0.5, + ) + model_1.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.2, + occupancy=0.5, + ) + model_1.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.2567, + ) + model_1.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=1.4041, + ) - model_2 = SampleModel('si') + model_2 = SampleModelFactory.create(name='si') model_2.space_group.name_h_m = 'F d -3 m' model_2.space_group.it_coordinate_system_code = '2' model_2.cell.length_a = 5.43146 - model_2.atom_sites.add('Si', 'Si', 0.0, 0.0, 0.0, wyckoff_letter='a', b_iso=0.0) + model_2.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.0, + fract_y=0.0, + fract_z=0.0, + wyckoff_letter='a', + b_iso=0.0, + ) # Set experiment data_file = 'mcstas_lbco-si.xys' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment( + expt = ExperimentFactory.create( name='mcstas', data_path=os.path.join(TEMP_DIR, data_file), beam_mode='time-of-flight', @@ -48,10 +93,10 @@ def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None: expt.peak.broad_mix_beta_1 = 0.0041 expt.peak.asym_alpha_0 = 0.0 expt.peak.asym_alpha_1 = 0.0097 - expt.linked_phases.add('lbco', scale=4.0) - expt.linked_phases.add('si', scale=0.2) + expt.linked_phases.add_from_args(id='lbco', scale=4.0) + expt.linked_phases.add_from_args(id='si', scale=0.2) for x in range(45000, 115000, 5000): - expt.background.add(x=x, y=0.2) + expt.background.add_from_args(x=x, y=0.2) # Create project project = Project() @@ -60,7 +105,7 @@ def test_single_fit_neutron_pd_tof_mcstas_lbco_si() -> None: project.experiments.add(expt) # Exclude regions from fitting - project.experiments['mcstas'].excluded_regions.add(start=108000, end=200000) + project.experiments['mcstas'].excluded_regions.add_from_args(start=108000, end=200000) # Prepare for fitting project.analysis.current_calculator = 'cryspy' diff --git a/tests/functional/fitting/test_powder-diffraction_time-of-flight.py b/tests/functional/fitting/test_powder-diffraction_time-of-flight.py index f77fba0a..b40c4369 100644 --- a/tests/functional/fitting/test_powder-diffraction_time-of-flight.py +++ b/tests/functional/fitting/test_powder-diffraction_time-of-flight.py @@ -1,11 +1,14 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import tempfile from numpy.testing import assert_almost_equal -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository TEMP_DIR = tempfile.gettempdir() @@ -13,16 +16,24 @@ def test_single_fit_neutron_pd_tof_si() -> None: # Set sample model - model = SampleModel('si') + model = SampleModelFactory.create(name='si') model.space_group.name_h_m = 'F d -3 m' model.space_group.it_coordinate_system_code = '2' model.cell.length_a = 5.4315 - model.atom_sites.add('Si', 'Si', 0.125, 0.125, 0.125, b_iso=0.529) + model.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.125, + fract_y=0.125, + fract_z=0.125, + wyckoff_letter='a', + b_iso=0.529, + ) # Set experiment data_file = 'sepd_si.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment( + expt = ExperimentFactory.create( name='sepd', data_path=os.path.join(TEMP_DIR, data_file), beam_mode='time-of-flight', @@ -39,9 +50,9 @@ def test_single_fit_neutron_pd_tof_si() -> None: expt.peak.broad_mix_beta_1 = 0.00946 expt.peak.asym_alpha_0 = 0.0 expt.peak.asym_alpha_1 = 0.5971 - expt.linked_phases.add('si', scale=14.92) + expt.linked_phases.add_from_args(id='si', scale=14.92) for x in range(0, 35000, 5000): - expt.background.add(x=x, y=200) + expt.background.add_from_args(x=x, y=200) expt.show_as_cif() # Create project @@ -70,21 +81,69 @@ def test_single_fit_neutron_pd_tof_si() -> None: def test_single_fit_neutron_pd_tof_ncaf() -> None: # Set sample model - model = SampleModel('ncaf') + model = SampleModelFactory.create(name='ncaf') model.space_group.name_h_m = 'I 21 3' model.space_group.it_coordinate_system_code = '1' model.cell.length_a = 10.250256 - model.atom_sites.add('Ca', 'Ca', 0.4661, 0.0, 0.25, wyckoff_letter='b', b_iso=0.9) - model.atom_sites.add('Al', 'Al', 0.25171, 0.25171, 0.25171, wyckoff_letter='a', b_iso=0.66) - model.atom_sites.add('Na', 'Na', 0.08481, 0.08481, 0.08481, wyckoff_letter='a', b_iso=1.9) - model.atom_sites.add('F1', 'F', 0.1375, 0.3053, 0.1195, wyckoff_letter='c', b_iso=0.9) - model.atom_sites.add('F2', 'F', 0.3626, 0.3634, 0.1867, wyckoff_letter='c', b_iso=1.28) - model.atom_sites.add('F3', 'F', 0.4612, 0.4612, 0.4612, wyckoff_letter='a', b_iso=0.79) + model.atom_sites.add_from_args( + label='Ca', + type_symbol='Ca', + fract_x=0.4661, + fract_y=0.0, + fract_z=0.25, + wyckoff_letter='b', + b_iso=0.9, + ) + model.atom_sites.add_from_args( + label='Al', + type_symbol='Al', + fract_x=0.25171, + fract_y=0.25171, + fract_z=0.25171, + wyckoff_letter='a', + b_iso=0.66, + ) + model.atom_sites.add_from_args( + label='Na', + type_symbol='Na', + fract_x=0.08481, + fract_y=0.08481, + fract_z=0.08481, + wyckoff_letter='a', + b_iso=1.9, + ) + model.atom_sites.add_from_args( + label='F1', + type_symbol='F', + fract_x=0.1375, + fract_y=0.3053, + fract_z=0.1195, + wyckoff_letter='c', + b_iso=0.9, + ) + model.atom_sites.add_from_args( + label='F2', + type_symbol='F', + fract_x=0.3626, + fract_y=0.3634, + fract_z=0.1867, + wyckoff_letter='c', + b_iso=1.28, + ) + model.atom_sites.add_from_args( + label='F3', + type_symbol='F', + fract_x=0.4612, + fract_y=0.4612, + fract_z=0.4612, + wyckoff_letter='a', + b_iso=0.79, + ) # Set experiment data_file = 'wish_ncaf.xye' download_from_repository(data_file, destination=TEMP_DIR) - expt = Experiment( + expt = ExperimentFactory.create( name='wish', data_path=os.path.join(TEMP_DIR, data_file), beam_mode='time-of-flight', @@ -101,7 +160,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None: expt.peak.broad_mix_beta_1 = 0.0099 expt.peak.asym_alpha_0 = -0.009 expt.peak.asym_alpha_1 = 0.1085 - expt.linked_phases.add('ncaf', scale=1.0928) + expt.linked_phases.add_from_args(id='ncaf', scale=1.0928) for x, y in [ (9162, 465), (11136, 593), @@ -132,7 +191,7 @@ def test_single_fit_neutron_pd_tof_ncaf() -> None: (91958, 268), (102712, 262), ]: - expt.background.add(x, y) + expt.background.add_from_args(x=x, y=y) # Create project project = Project() diff --git a/tests/unit/analysis/calculators/test_calculator_base.py b/tests/unit/analysis/calculators/test_calculator_base.py deleted file mode 100644 index 7c9df501..00000000 --- a/tests/unit/analysis/calculators/test_calculator_base.py +++ /dev/null @@ -1,83 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.calculators.calculator_base import CalculatorBase - - -# Mock subclass of CalculatorBase to test its concrete methods -class MockCalculator(CalculatorBase): - @property - def name(self): - return 'MockCalculator' - - @property - def engine_imported(self): - return True - - def calculate_structure_factors(self, sample_model, experiment): - return np.array([1.0, 2.0, 3.0]) - - def _calculate_single_model_pattern(self, sample_model, experiment, called_by_minimizer): - return np.array([1.0, 2.0, 3.0]) - - -@pytest.fixture -def mock_sample_models(): - sample_models = MagicMock() - sample_models.get_all_params.return_value = {'param1': 1, 'param2': 2} - sample_models.get_ids.return_value = ['phase1', 'phase2'] - sample_models.__getitem__.side_effect = lambda key: MagicMock(apply_symmetry_constraints=MagicMock()) - return sample_models - - -@pytest.fixture -def mock_experiment(): - experiment = MagicMock() - experiment.datastore.x = np.array([1.0, 2.0, 3.0]) - experiment.datastore.bkg = None - experiment.datastore.calc = None - experiment.linked_phases = [ - MagicMock(_entry_id='phase1', scale=MagicMock(value=2.0)), - MagicMock(_entry_id='phase2', scale=MagicMock(value=1.5)), - ] - experiment.background.calculate.return_value = np.array([0.1, 0.2, 0.3]) - return experiment - - -@patch('easydiffraction.core.singletons.ConstraintsHandler.get') -def test_calculate_pattern(mock_constraints_handler, mock_sample_models, mock_experiment): - mock_constraints_handler.return_value.apply = MagicMock() - - calculator = MockCalculator() - calculator.calculate_pattern(mock_sample_models, mock_experiment) - result = mock_experiment.datastore.calc - - # Assertions - assert np.allclose(result, np.array([3.6, 7.2, 10.8])) - mock_constraints_handler.return_value.apply.assert_called_once_with() - assert mock_experiment.datastore.bkg is not None - assert mock_experiment.datastore.calc is not None - - -def test_get_valid_linked_phases(mock_sample_models, mock_experiment): - calculator = MockCalculator() - - valid_phases = calculator._get_valid_linked_phases(mock_sample_models, mock_experiment) - - # Assertions - assert len(valid_phases) == 2 - assert valid_phases[0]._entry_id == 'phase1' - assert valid_phases[1]._entry_id == 'phase2' - - -def test_calculate_structure_factors(mock_sample_models, mock_experiment): - calculator = MockCalculator() - - # Mock the method's behavior if necessary - result = calculator.calculate_structure_factors(mock_sample_models, mock_experiment) - - # Assertions - assert np.allclose(result, np.array([1.0, 2.0, 3.0])) diff --git a/tests/unit/analysis/calculators/test_calculator_cryspy.py b/tests/unit/analysis/calculators/test_calculator_cryspy.py deleted file mode 100644 index 1d611854..00000000 --- a/tests/unit/analysis/calculators/test_calculator_cryspy.py +++ /dev/null @@ -1,103 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.calculators.calculator_cryspy import CryspyCalculator - - -@pytest.fixture -def mock_sample_model(): - sample_model = MagicMock() - sample_model.name = 'sample1' - sample_model.cell.length_a.value = 1.0 - sample_model.cell.length_b.value = 2.0 - sample_model.cell.length_c.value = 3.0 - sample_model.cell.angle_alpha.value = 90.0 - sample_model.cell.angle_beta.value = 90.0 - sample_model.cell.angle_gamma.value = 90.0 - sample_model.atom_sites = [ - MagicMock( - fract_x=MagicMock(value=0.1), - fract_y=MagicMock(value=0.2), - fract_z=MagicMock(value=0.3), - occupancy=MagicMock(value=1.0), - b_iso=MagicMock(value=0.5), - ) - ] - return sample_model - - -@pytest.fixture -def mock_experiment(): - experiment = MagicMock() - experiment.name = 'experiment1' - experiment.type.beam_mode.value = 'constant wavelength' - experiment.datastore.x = np.array([1.0, 2.0, 3.0]) - experiment.datastore.meas = np.array([10.0, 20.0, 30.0]) - experiment.datastore.meas_su = np.array([0.1, 0.2, 0.3]) - experiment.instrument.calib_twotheta_offset.value = 0.0 - experiment.instrument.setup_wavelength.value = 1.54 - experiment.peak.broad_gauss_u.value = 0.1 - experiment.peak.broad_gauss_v.value = 0.2 - experiment.peak.broad_gauss_w.value = 0.3 - experiment.peak.broad_lorentz_x.value = 0.4 - experiment.peak.broad_lorentz_y.value = 0.5 - return experiment - - -@patch('easydiffraction.analysis.calculators.calculator_cryspy.str_to_globaln') -def test_recreate_cryspy_obj(mock_str_to_globaln, mock_sample_model, mock_experiment): - mock_str_to_globaln.return_value = MagicMock(add_items=MagicMock()) - - calculator = CryspyCalculator() - cryspy_obj = calculator._recreate_cryspy_obj(mock_sample_model, mock_experiment) - - # Assertions - mock_str_to_globaln.assert_called() - assert cryspy_obj.add_items.called - - -@patch('easydiffraction.analysis.calculators.calculator_cryspy.rhochi_calc_chi_sq_by_dictionary') -def test_calculate_single_model_pattern(mock_rhochi_calc, mock_sample_model, mock_experiment): - mock_rhochi_calc.return_value = None - - calculator = CryspyCalculator() - calculator._cryspy_dicts = {'experiment1': {'mock_key': 'mock_value'}} - - result = calculator._calculate_single_model_pattern(mock_sample_model, mock_experiment, called_by_minimizer=False) - - # Assertions - assert isinstance(result, np.ndarray) or result == [] - mock_rhochi_calc.assert_called() - - -def test_recreate_cryspy_dict(mock_sample_model, mock_experiment): - calculator = CryspyCalculator() - calculator._cryspy_dicts = { - 'sample1_experiment1': { - 'pd_experiment1': { - 'offset_ttheta': [0.1], - 'wavelength': [1.54], - 'resolution_parameters': [0.1, 0.2, 0.3, 0.4, 0.5], - }, - 'crystal_sample1': { - 'unit_cell_parameters': [0, 0, 0, 0, 0, 0], - 'atom_fract_xyz': [[0], [0], [0]], - 'atom_occupancy': [0], - 'atom_b_iso': [0], - }, - } - } - - cryspy_dict = calculator._recreate_cryspy_dict(mock_sample_model, mock_experiment) - - # Assertions - assert cryspy_dict['crystal_sample1']['unit_cell_parameters'][:3] == [1.0, 2.0, 3.0] - assert cryspy_dict['crystal_sample1']['atom_fract_xyz'][0][0] == 0.1 - assert cryspy_dict['crystal_sample1']['atom_occupancy'][0] == 1.0 - assert cryspy_dict['crystal_sample1']['atom_b_iso'][0] == 0.5 - assert cryspy_dict['pd_experiment1']['offset_ttheta'][0] == 0.0 - assert cryspy_dict['pd_experiment1']['wavelength'][0] == 1.54 - assert cryspy_dict['pd_experiment1']['resolution_parameters'] == [0.1, 0.2, 0.3, 0.4, 0.5] diff --git a/tests/unit/analysis/calculators/test_calculator_factory.py b/tests/unit/analysis/calculators/test_calculator_factory.py deleted file mode 100644 index 2a35d0b1..00000000 --- a/tests/unit/analysis/calculators/test_calculator_factory.py +++ /dev/null @@ -1,65 +0,0 @@ -from unittest.mock import patch - -import pytest - -from easydiffraction.analysis.calculators.calculator_crysfml import CrysfmlCalculator -from easydiffraction.analysis.calculators.calculator_cryspy import CryspyCalculator -from easydiffraction.analysis.calculators.calculator_factory import CalculatorFactory -from easydiffraction.analysis.calculators.calculator_pdffit import PdffitCalculator -from easydiffraction.utils.formatting import paragraph - - -@pytest.fixture -def mock_calculators(): - with ( - patch.object(CrysfmlCalculator, 'engine_imported', True), - patch.object(CryspyCalculator, 'engine_imported', True), - patch.object(PdffitCalculator, 'engine_imported', False), - ): - yield - - -def test_supported_calculators(mock_calculators): - supported = CalculatorFactory._supported_calculators() - - # Assertions - assert 'crysfml' in supported - assert 'cryspy' in supported - assert 'pdffit' not in supported # Engine not imported - - -def test_list_supported_calculators(mock_calculators): - supported_list = CalculatorFactory.list_supported_calculators() - - # Assertions - assert 'crysfml' in supported_list - assert 'cryspy' in supported_list - assert 'pdffit' not in supported_list # Engine not imported - - -@patch('builtins.print') -def test_show_supported_calculators(mock_print, mock_calculators): - CalculatorFactory.show_supported_calculators() - - # Assertions - mock_print.assert_any_call(paragraph('Supported calculators')) - assert any('CrysFML library for crystallographic calculations' in call.args[0] for call in mock_print.call_args_list) - assert any('CrysPy library for crystallographic calculations' in call.args[0] for call in mock_print.call_args_list) - - -def test_create_calculator(mock_calculators): - crysfml_calculator = CalculatorFactory.create_calculator('crysfml') - cryspy_calculator = CalculatorFactory.create_calculator('cryspy') - pdffit_calculator = CalculatorFactory.create_calculator('pdffit') # Not supported - - # Assertions - assert isinstance(crysfml_calculator, CrysfmlCalculator) - assert isinstance(cryspy_calculator, CryspyCalculator) - assert pdffit_calculator is None - - -def test_create_calculator_unknown(mock_calculators): - unknown_calculator = CalculatorFactory.create_calculator('unknown') - - # Assertions - assert unknown_calculator is None diff --git a/tests/unit/analysis/collections/test_joint_fit_experiment.py b/tests/unit/analysis/collections/test_joint_fit_experiment.py deleted file mode 100644 index bb6d9d02..00000000 --- a/tests/unit/analysis/collections/test_joint_fit_experiment.py +++ /dev/null @@ -1,22 +0,0 @@ -from easydiffraction.analysis.collections.joint_fit_experiments import JointFitExperiment - -# filepath: src/easydiffraction/analysis/collections/test_joint_fit_experiments.py - - -def test_joint_fit_experiment_initialization(): - # Test initialization of JointFitExperiment - expt = JointFitExperiment(id='exp1', weight=1.5) - assert expt.id.value == 'exp1' - assert expt.id.name == 'id' - assert expt.id.cif_name == 'id' - assert expt.weight.value == 1.5 - assert expt.weight.name == 'weight' - assert expt.weight.cif_name == 'weight' - - -def test_joint_fit_experiment_properties(): - # Test properties of JointFitExperiment - expt = JointFitExperiment(id='exp2', weight=2.0) - assert expt.cif_category_key == 'joint_fit_experiment' - assert expt.category_key == 'joint_fit_experiment' - assert expt._entry_id == 'exp2' diff --git a/tests/unit/analysis/minimizers/test_fitting_progress_tracker.py b/tests/unit/analysis/minimizers/test_fitting_progress_tracker.py deleted file mode 100644 index c82f6e49..00000000 --- a/tests/unit/analysis/minimizers/test_fitting_progress_tracker.py +++ /dev/null @@ -1,98 +0,0 @@ -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.fitting.progress_tracker import FittingProgressTracker -from easydiffraction.analysis.fitting.progress_tracker import format_cell - - -def test_format_cell(): - # Test center alignment - assert format_cell('test', width=10, align='center') == ' test ' - # Test left alignment - assert format_cell('test', width=10, align='left') == 'test ' - # Test right alignment - assert format_cell('test', width=10, align='right') == ' test' - # Test default alignment (center) - assert format_cell('test', width=10) == ' test ' - # Test invalid alignment - assert format_cell('test', width=10, align='invalid') == 'test' - - -@pytest.fixture -def tracker(): - return FittingProgressTracker() - - -@patch('builtins.print') -def test_start_tracking(mock_print, tracker): - tracker.start_tracking('MockMinimizer') - - # Assertions - mock_print.assert_any_call("🚀 Starting fit process with 'MockMinimizer'...") - mock_print.assert_any_call('📈 Goodness-of-fit (reduced χ²) change:') - assert mock_print.call_count > 2 # Ensure headers and borders are printed - - -@patch('builtins.print') -def test_add_tracking_info(mock_print, tracker): - tracker.add_tracking_info([1, '9.0', '10% ↓']) - - # Assertions - mock_print.assert_called_once() - assert '│ 1 │ 9.0 │ 10% ↓ │' in mock_print.call_args[0][0] - - -@patch('builtins.print') -def test_finish_tracking(mock_print, tracker): - tracker._last_iteration = 5 - tracker._last_chi2 = 1.23 - tracker._best_chi2 = 1.23 - tracker._best_iteration = 5 - - tracker.finish_tracking() - - # Assertions - mock_print.assert_any_call('🏆 Best goodness-of-fit (reduced χ²) is 1.23 at iteration 5') - mock_print.assert_any_call('✅ Fitting complete.') - - -def test_reset(tracker): - tracker._iteration = 5 - tracker._previous_chi2 = 1.23 - tracker.reset() - - # Assertions - assert tracker._iteration == 0 - assert tracker._previous_chi2 is None - - -@patch('easydiffraction.analysis.fitting.metrics.calculate_reduced_chi_square', return_value=1.23) -@patch('builtins.print') -def test_track(mock_print, mock_calculate_chi2, tracker): - residuals = np.array([1.1, 2.1, 3.1, 4.1, 5.1]) - parameters = [1.0, 2.0, 3.0] - - tracker.track(residuals, parameters) - - # Assertions - # mock_calculate_chi2.assert_called_once_with(residuals, len(parameters)) - assert tracker._iteration == 1 - assert tracker._previous_chi2 == 29.025 - assert tracker._best_chi2 == 29.025 - assert tracker._best_iteration == 1 - mock_print.assert_called() - - -def test_start_timer(tracker): - with patch('time.perf_counter', return_value=100.0): - tracker.start_timer() - assert tracker._start_time == 100.0 - - -def test_stop_timer(tracker): - with patch('time.perf_counter', side_effect=[100.0, 105.0]): - tracker.start_timer() - tracker.stop_timer() - assert tracker._fitting_time == 5.0 diff --git a/tests/unit/analysis/minimizers/test_minimizer_base.py b/tests/unit/analysis/minimizers/test_minimizer_base.py deleted file mode 100644 index be613c05..00000000 --- a/tests/unit/analysis/minimizers/test_minimizer_base.py +++ /dev/null @@ -1,116 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import pytest - -from easydiffraction.analysis.fitting.results import FitResults -from easydiffraction.analysis.minimizers.minimizer_base import MinimizerBase - - -# Mock subclass of MinimizerBase to test its methods -class MockMinimizer(MinimizerBase): - def _prepare_solver_args(self, parameters): - return {'mock_arg': 'mock_value'} - - def _run_solver(self, objective_function, **engine_parameters): - return {'success': True, 'raw_result': 'mock_result'} - - def _sync_result_to_parameters(self, raw_result, parameters): - for param in parameters: - param.value = 1.0 # Mock synchronization - - def _check_success(self, raw_result): - return raw_result.get('success', False) - - def _finalize_fit(self, parameters, raw_result): - return FitResults( - success=raw_result.get('success', False), - parameters=parameters, - chi_square=raw_result.get('chi_square', 0.0), - reduced_chi_square=raw_result.get('reduced_chi_square', 0.0), - message=raw_result.get('message', ''), - iterations=raw_result.get('iterations', 0), - engine_result=raw_result.get('raw_result', None), - starting_parameters=[p.start_value for p in parameters], - fitting_time=raw_result.get('fitting_time', 0.0), - ) - - -@pytest.fixture -def mock_minimizer(): - return MockMinimizer(name='MockMinimizer', method='mock_method', max_iterations=100) - - -@pytest.fixture -def mock_parameters(): - param1 = MagicMock(name='param1', value=None, start_value=0.5, uncertainty=None) - param2 = MagicMock(name='param2', value=None, start_value=1.0, uncertainty=None) - return [param1, param2] - - -@pytest.fixture -def mock_objective_function(): - return MagicMock(return_value=[1.0, 2.0, 3.0]) - - -def test_prepare_solver_args(mock_minimizer, mock_parameters): - solver_args = mock_minimizer._prepare_solver_args(mock_parameters) - assert solver_args == {'mock_arg': 'mock_value'} - - -def test_run_solver(mock_minimizer, mock_objective_function): - raw_result = mock_minimizer._run_solver(mock_objective_function, mock_arg='mock_value') - assert raw_result == {'success': True, 'raw_result': 'mock_result'} - - -def test_sync_result_to_parameters(mock_minimizer, mock_parameters): - raw_result = {'success': True} - mock_minimizer._sync_result_to_parameters(raw_result, mock_parameters) - - # Assertions - for param in mock_parameters: - assert param.value == 1.0 - - -def test_check_success(mock_minimizer): - raw_result = {'success': True} - assert mock_minimizer._check_success(raw_result) is True - - raw_result = {'success': False} - assert mock_minimizer._check_success(raw_result) is False - - -def test_finalize_fit(mock_minimizer, mock_parameters): - raw_result = {'success': True} - result = mock_minimizer._finalize_fit(mock_parameters, raw_result) - - # Assertions - assert isinstance(result, FitResults) - assert result.success is True - assert result.parameters == mock_parameters - - -@patch('easydiffraction.analysis.fitting.progress_tracker.FittingProgressTracker') -def test_fit(mock_tracker, mock_minimizer, mock_parameters, mock_objective_function): - mock_minimizer.tracker.finish_tracking = MagicMock() - result = mock_minimizer.fit(mock_parameters, mock_objective_function) - - # Assertions - assert isinstance(result, FitResults) - assert result.success is True - - -def test_create_objective_function(mock_minimizer): - parameters = [MagicMock()] - sample_models = MagicMock() - experiments = MagicMock() - calculator = MagicMock() - - objective_function = mock_minimizer._create_objective_function(parameters, sample_models, experiments, calculator) - - # Assertions - assert callable(objective_function) - with patch.object(mock_minimizer, '_objective_function', return_value=[1.0, 2.0, 3.0]) as mock_objective: - residuals = objective_function({'param1': 1.0}) - mock_objective.assert_called_once_with({'param1': 1.0}, parameters, sample_models, experiments, calculator) - assert residuals == [1.0, 2.0, 3.0] diff --git a/tests/unit/analysis/minimizers/test_minimizer_dfols.py b/tests/unit/analysis/minimizers/test_minimizer_dfols.py deleted file mode 100644 index db26f0bc..00000000 --- a/tests/unit/analysis/minimizers/test_minimizer_dfols.py +++ /dev/null @@ -1,79 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.minimizers.minimizer_dfols import DfolsMinimizer - - -@pytest.fixture -def mock_parameters(): - param1 = MagicMock(name='param1', value=1.0, min=0.0, max=2.0, uncertainty=None) - param2 = MagicMock(name='param2', value=2.0, min=1.0, max=3.0, uncertainty=None) - return [param1, param2] - - -@pytest.fixture -def mock_objective_function(): - return MagicMock(return_value=np.array([1.0, 2.0, 3.0])) - - -@pytest.fixture -def dfols_minimizer(): - return DfolsMinimizer(name='dfols', max_iterations=100) - - -def test_prepare_solver_args(dfols_minimizer, mock_parameters): - solver_args = dfols_minimizer._prepare_solver_args(mock_parameters) - - # Assertions - assert np.allclose(solver_args['x0'], [1.0, 2.0]) - assert np.allclose(solver_args['bounds'][0], [0.0, 1.0]) # Lower bounds - assert np.allclose(solver_args['bounds'][1], [2.0, 3.0]) # Upper bounds - - -@patch('easydiffraction.analysis.minimizers.minimizer_dfols.solve') -def test_run_solver(mock_solve, dfols_minimizer, mock_objective_function): - mock_solve.return_value = MagicMock(x=np.array([1.5, 2.5]), flag=0) - - solver_args = {'x0': np.array([1.0, 2.0]), 'bounds': (np.array([0.0, 1.0]), np.array([2.0, 3.0]))} - raw_result = dfols_minimizer._run_solver(mock_objective_function, **solver_args) - - # Assertions - mock_solve.assert_called_once_with( - mock_objective_function, x0=solver_args['x0'], bounds=solver_args['bounds'], maxfun=dfols_minimizer.max_iterations - ) - assert np.allclose(raw_result.x, [1.5, 2.5]) - - -def test_sync_result_to_parameters(dfols_minimizer, mock_parameters): - raw_result = MagicMock(x=np.array([1.5, 2.5])) - - dfols_minimizer._sync_result_to_parameters(mock_parameters, raw_result) - - # Assertions - assert mock_parameters[0].value == 1.5 - assert mock_parameters[1].value == 2.5 - assert mock_parameters[0].uncertainty is None - assert mock_parameters[1].uncertainty is None - - -def test_check_success(dfols_minimizer): - raw_result = MagicMock(flag=0, EXIT_SUCCESS=0) - assert dfols_minimizer._check_success(raw_result) is True - - raw_result = MagicMock(flag=1, EXIT_SUCCESS=0) - assert dfols_minimizer._check_success(raw_result) is False - - -@patch('easydiffraction.analysis.minimizers.minimizer_dfols.solve') -def test_fit(mock_solve, dfols_minimizer, mock_parameters, mock_objective_function): - mock_solve.return_value = MagicMock(x=np.array([1.5, 2.5]), flag=0) - dfols_minimizer.tracker.finish_tracking = MagicMock() - - result = dfols_minimizer.fit(mock_parameters, mock_objective_function) - - # Assertions - assert np.allclose([p.value for p in result.parameters], [1.5, 2.5]) - assert result.iterations == 0 # DFO-LS doesn't provide iteration count by default diff --git a/tests/unit/analysis/minimizers/test_minimizer_factory.py b/tests/unit/analysis/minimizers/test_minimizer_factory.py deleted file mode 100644 index a0d5caed..00000000 --- a/tests/unit/analysis/minimizers/test_minimizer_factory.py +++ /dev/null @@ -1,66 +0,0 @@ -from unittest.mock import patch - -import pytest - -from easydiffraction.analysis.minimizers.minimizer_dfols import DfolsMinimizer -from easydiffraction.analysis.minimizers.minimizer_factory import MinimizerFactory -from easydiffraction.analysis.minimizers.minimizer_lmfit import LmfitMinimizer - - -def test_list_available_minimizers(): - minimizers = MinimizerFactory.list_available_minimizers() - - # Assertions - assert isinstance(minimizers, list) - assert 'lmfit' in minimizers - assert 'dfols' in minimizers - - -@patch('builtins.print') -def test_show_available_minimizers(mock_print): - MinimizerFactory.show_available_minimizers() - - # Assertions - # mock_print.assert_any_call("Available minimizers") - assert any( - 'LMFIT library using the default Levenberg-Marquardt least squares method' in call.args[0] - for call in mock_print.call_args_list - ) - assert any( - 'DFO-LS library for derivative-free least-squares optimization' in call.args[0] for call in mock_print.call_args_list - ) - - -def test_create_minimizer(): - # Test creating an LmfitMinimizer - minimizer = MinimizerFactory.create_minimizer('lmfit') - assert isinstance(minimizer, LmfitMinimizer) - assert minimizer.method == 'leastsq' - - # Test creating a DfolsMinimizer - minimizer = MinimizerFactory.create_minimizer('dfols') - assert isinstance(minimizer, DfolsMinimizer) - assert minimizer.method is None - - # Test invalid minimizer - with pytest.raises(ValueError, match="Unknown minimizer 'invalid'.*"): - MinimizerFactory.create_minimizer('invalid') - - -def test_register_minimizer(): - class MockMinimizer: - def __init__(self, method=None): - self.method = method - - MinimizerFactory.register_minimizer( - name='mock_minimizer', minimizer_cls=MockMinimizer, method='mock_method', description='Mock minimizer for testing' - ) - - # Assertions - minimizers = MinimizerFactory.list_available_minimizers() - assert 'mock_minimizer' in minimizers - - # Test creating the registered minimizer - minimizer = MinimizerFactory.create_minimizer('mock_minimizer') - assert isinstance(minimizer, MockMinimizer) - assert minimizer.method == 'mock_method' diff --git a/tests/unit/analysis/minimizers/test_minimizer_lmfit.py b/tests/unit/analysis/minimizers/test_minimizer_lmfit.py deleted file mode 100644 index 5a0e5938..00000000 --- a/tests/unit/analysis/minimizers/test_minimizer_lmfit.py +++ /dev/null @@ -1,98 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import lmfit -import pytest - -from easydiffraction.analysis.minimizers.minimizer_lmfit import LmfitMinimizer -from easydiffraction.core.objects import Parameter - - -@pytest.fixture -def mock_parameters(): - param1 = Parameter(name='param1', cif_name='param1', value=1.0, free=True, min_value=0.0, max_value=2.0, uncertainty=None) - param2 = Parameter(name='param2', cif_name='param2', value=2.0, free=False, min_value=1.0, max_value=3.0, uncertainty=None) - return [param1, param2] - - -@pytest.fixture -def mock_objective_function(): - return MagicMock(return_value=[1.0, 2.0, 3.0]) - - -@pytest.fixture -def lmfit_minimizer(): - return LmfitMinimizer(name='lmfit', method='leastsq', max_iterations=100) - - -def test_prepare_solver_args(lmfit_minimizer, mock_parameters): - solver_args = lmfit_minimizer._prepare_solver_args(mock_parameters) - - # Assertions - assert isinstance(solver_args['engine_parameters'], lmfit.Parameters) - assert 'None__param1' in solver_args['engine_parameters'] - assert 'None__param2' in solver_args['engine_parameters'] - assert solver_args['engine_parameters']['None__param1'].value == 1.0 - assert solver_args['engine_parameters']['None__param1'].min == 0.0 - assert solver_args['engine_parameters']['None__param1'].max == 2.0 - assert solver_args['engine_parameters']['None__param1'].vary is True - assert solver_args['engine_parameters']['None__param2'].value == 2.0 - assert solver_args['engine_parameters']['None__param2'].vary is False - - -@patch('easydiffraction.analysis.minimizers.minimizer_lmfit.lmfit.minimize') -def test_run_solver(mock_minimize, lmfit_minimizer, mock_objective_function, mock_parameters): - mock_minimize.return_value = MagicMock(params={'param1': MagicMock(value=1.5), 'param2': MagicMock(value=2.5)}) - - solver_args = lmfit_minimizer._prepare_solver_args(mock_parameters) - raw_result = lmfit_minimizer._run_solver(mock_objective_function, **solver_args) - - # Assertions - mock_minimize.assert_called_once_with( - mock_objective_function, - params=solver_args['engine_parameters'], - method='leastsq', - nan_policy='propagate', - max_nfev=lmfit_minimizer.max_iterations, - ) - assert raw_result.params['param1'].value == 1.5 - assert raw_result.params['param2'].value == 2.5 - - -def test_sync_result_to_parameters(lmfit_minimizer, mock_parameters): - raw_result = MagicMock( - params={'None__param1': MagicMock(value=1.5, stderr=0.1), 'None__param2': MagicMock(value=2.5, stderr=0.2)} - ) - - lmfit_minimizer._sync_result_to_parameters(mock_parameters, raw_result) - - # Assertions - assert mock_parameters[0].value == 1.5 - assert mock_parameters[0].uncertainty == 0.1 - assert mock_parameters[1].value == 2.5 - assert mock_parameters[1].uncertainty == 0.2 - - -def test_check_success(lmfit_minimizer): - raw_result = MagicMock(success=True) - assert lmfit_minimizer._check_success(raw_result) is True - - raw_result = MagicMock(success=False) - assert lmfit_minimizer._check_success(raw_result) is False - - -@patch('easydiffraction.analysis.minimizers.minimizer_lmfit.lmfit.minimize') -def test_fit(mock_minimize, lmfit_minimizer, mock_parameters, mock_objective_function): - mock_minimize.return_value = MagicMock( - params={'None__param1': MagicMock(value=1.5, stderr=0.1), 'None__param2': MagicMock(value=2.5, stderr=0.2)}, - success=True, - ) - lmfit_minimizer.tracker.finish_tracking = MagicMock() - result = lmfit_minimizer.fit(mock_parameters, mock_objective_function) - - # Assertions - assert result.success is True - assert result.parameters[0].value == 1.5 - assert result.parameters[0].uncertainty == 0.1 - assert result.parameters[1].value == 2.5 - assert result.parameters[1].uncertainty == 0.2 diff --git a/tests/unit/analysis/test_analysis.py b/tests/unit/analysis/test_analysis.py deleted file mode 100644 index 245a57b0..00000000 --- a/tests/unit/analysis/test_analysis.py +++ /dev/null @@ -1,157 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import pytest - -from easydiffraction.analysis.analysis import Analysis - - -@pytest.fixture -def mock_project(): - project = MagicMock() - project.sample_models.get_all_params.return_value = [ - MagicMock( - datablock_id='block1', - category_key='cat1', - collection_entry_id='entry1', - name='param1', - value=1.0, - units='unit1', - free=True, - min=0.0, - max=2.0, - uncertainty=0.1, - ) - ] - project.experiments.get_all_params.return_value = [ - MagicMock( - datablock_id='block2', - category_key='cat2', - collection_entry_id='entry2', - name='param2', - value=2.0, - units='unit2', - free=False, - min=1.0, - max=3.0, - uncertainty=0.2, - ) - ] - project.sample_models.get_fittable_params.return_value = project.sample_models.get_all_params() - project.experiments.get_fittable_params.return_value = project.experiments.get_all_params() - project.sample_models.get_free_params.return_value = project.sample_models.get_all_params() - project.experiments.get_free_params.return_value = project.experiments.get_all_params() - project.experiments.ids = ['experiment1', 'experiment2'] - project._varname = 'project' - return project - - -@pytest.fixture -def analysis(mock_project): - return Analysis(project=mock_project) - - -# @patch("builtins.print") -# def test_show_all_params(mock_print, analysis): -# analysis._show_params = MagicMock() -# analysis.show_all_params() -# -# # Assertions -# assert('parameters for all experiments' in mock_print.call_args[0][0]) -# -# @patch("builtins.print") -# def test_show_fittable_params(mock_print, analysis): -# analysis._show_params = MagicMock() -# analysis.show_fittable_params() -# -# # Assertions -# assert('Fittable parameters for all experiments' in mock_print.call_args[0][0]) -# -# @patch("builtins.print") -# def test_show_free_params(mock_print, analysis): -# analysis._show_params = MagicMock() -# analysis.show_free_params() -# -# # Assertions -# assert('Free parameters for both sample models' in mock_print.call_args[0][0]) -# # mock_print.assert_any_call("Free parameters for both sample models (🧩 data blocks) and experiments (🔬 data blocks)") - - -@patch('builtins.print') -def test_show_current_calculator(mock_print, analysis): - analysis.show_current_calculator() - - # Assertions - # mock_print.assert_any_call("Current calculator") - mock_print.assert_any_call('cryspy') - - -@patch('builtins.print') -def test_show_current_minimizer(mock_print, analysis): - analysis.show_current_minimizer() - - # Assertions - # mock_print.assert_any_call("Current minimizer") - mock_print.assert_any_call('lmfit (leastsq)') - - -@patch('easydiffraction.analysis.calculators.calculator_factory.CalculatorFactory.create_calculator') -@patch('builtins.print') -def test_current_calculator_setter(mock_print, mock_create_calculator, analysis): - mock_create_calculator.return_value = MagicMock() - - analysis.current_calculator = 'pdffit2' - - # Assertions - mock_create_calculator.assert_called_once_with('pdffit2') - - -@patch('easydiffraction.analysis.minimizers.minimizer_factory.MinimizerFactory.create_minimizer') -@patch('builtins.print') -def test_current_minimizer_setter(mock_print, mock_create_minimizer, analysis): - mock_create_minimizer.return_value = MagicMock() - - analysis.current_minimizer = 'dfols' - - # Assertions - mock_print.assert_any_call('dfols') - - -@patch('builtins.print') -def test_fit_mode_setter(mock_print, analysis): - analysis.fit_mode = 'joint' - - # Assertions - assert analysis.fit_mode == 'joint' - mock_print.assert_any_call('joint') - - -@patch('easydiffraction.analysis.minimization.DiffractionMinimizer.fit') -@patch('builtins.print') -def no_test_fit_single_mode(mock_print, mock_fit, analysis, mock_project): - analysis.fit_mode = 'single' - analysis.fit() - - # Assertions - mock_fit.assert_called() - mock_print.assert_any_call('single') - - -@patch('easydiffraction.analysis.minimization.DiffractionMinimizer.fit') -@patch('builtins.print') -def test_fit_joint_mode(mock_print, mock_fit, analysis, mock_project): - analysis.fit_mode = 'joint' - analysis.fit() - - # Assertions - mock_fit.assert_called_once() - - -@patch('builtins.print') -def test_as_cif(mock_print, analysis): - cif_text = analysis.as_cif() - - # Assertions - assert '_analysis.calculator_engine cryspy' in cif_text - assert '_analysis.fitting_engine "lmfit (leastsq)"' in cif_text - assert '_analysis.fit_mode single' in cif_text diff --git a/tests/unit/analysis/test_minimization.py b/tests/unit/analysis/test_minimization.py deleted file mode 100644 index 90e39c3f..00000000 --- a/tests/unit/analysis/test_minimization.py +++ /dev/null @@ -1,163 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.analysis.minimization import DiffractionMinimizer - - -@pytest.fixture -def mock_sample_models(): - sample_models = MagicMock() - sample_models.get_free_params.return_value = [ - MagicMock(name='param1', value=1.0, start_value=None, min=0.0, max=2.0, free=True), - MagicMock(name='param2', value=2.0, start_value=None, min=1.0, max=3.0, free=True), - ] - return sample_models - - -@pytest.fixture -def mock_experiments(): - experiments = MagicMock() - experiments.get_free_params.return_value = [ - MagicMock(name='param3', value=3.0, start_value=None, min=2.0, max=4.0, free=True), - ] - experiments.ids = ['experiment1'] - experiments._items = { - 'experiment1': MagicMock( - datastore=MagicMock( - pattern=MagicMock( - meas=np.array([10.0, 20.0, 30.0]), - meas_su=np.array([1.0, 1.0, 1.0]), - excluded=np.array([False, False, False]), - ) - ) - ) - } - return experiments - - -@pytest.fixture -def mock_calculator(): - calculator = MagicMock() - - def mock_calculate_pattern(sample_models, experiment, **kwargs): - experiment.datastore.calc = np.array([9.0, 19.0, 29.0]) - - calculator.calculate_pattern.side_effect = mock_calculate_pattern - return calculator - - -@pytest.fixture -def mock_minimizer(): - minimizer = MagicMock() - minimizer.fit.return_value = MagicMock(success=True) - minimizer._sync_result_to_parameters = MagicMock() - minimizer.tracker.track = MagicMock(return_value=np.array([1.0, 2.0, 3.0])) - return minimizer - - -@pytest.fixture -def diffraction_minimizer(mock_minimizer): - with patch( - 'easydiffraction.analysis.minimizers.minimizer_factory.MinimizerFactory.create_minimizer', - return_value=mock_minimizer, - ): - return DiffractionMinimizer(selection='lmfit (leastsq)') - - -def test_fit_no_params( - diffraction_minimizer, - mock_sample_models, - mock_experiments, - mock_calculator, -): - mock_sample_models.get_free_params.return_value = [] - mock_experiments.get_free_params.return_value = [] - - result = diffraction_minimizer.fit( - mock_sample_models, - mock_experiments, - mock_calculator, - ) - - # Assertions - assert result is None - - -def test_fit_with_params( - diffraction_minimizer, - mock_sample_models, - mock_experiments, - mock_calculator, -): - diffraction_minimizer.fit( - mock_sample_models, - mock_experiments, - mock_calculator, - ) - - # Assertions - assert diffraction_minimizer.results.success is True - assert mock_calculator.calculate_pattern.called - assert mock_sample_models.get_free_params.called - assert mock_experiments.get_free_params.called - - -def test_residual_function( - diffraction_minimizer, - mock_sample_models, - mock_experiments, - mock_calculator, -): - parameters = mock_sample_models.get_free_params() + mock_experiments.get_free_params() - engine_params = MagicMock() - - residuals = diffraction_minimizer._residual_function( - engine_params=engine_params, - parameters=parameters, - sample_models=mock_sample_models, - experiments=mock_experiments, - calculator=mock_calculator, - ) - - # Assertions - assert isinstance(residuals, np.ndarray) - assert len(residuals) == 3 - assert mock_calculator.calculate_pattern.called - assert diffraction_minimizer.minimizer._sync_result_to_parameters.called - - -# @patch( -# 'easydiffraction.analysis.reliability_factors.get_reliability_inputs', -# return_value=(np.array([10.0]), np.array([9.0]), np.array([1.0])), -# ) -# def test_process_fit_results( -# mock_get_reliability_inputs, -# diffraction_minimizer, -# mock_sample_models, -# mock_experiments, -# mock_calculator, -# ): -# diffraction_minimizer.results = MagicMock() -# diffraction_minimizer._process_fit_results( -# mock_sample_models, -# mock_experiments, -# mock_calculator, -# ) -# -# # Assertions -# # mock_get_reliability_inputs.assert_called_once_with(mock_sample_models, mock_experiments, mock_calculator) -# -# # Extract the arguments passed to `display_results` -# _, kwargs = diffraction_minimizer.results.display_results.call_args -# -# # Assertions for arrays -# np.testing.assert_array_equal(kwargs['y_calc'], np.array([9.0, 19.0, 29.0])) -# np.testing.assert_array_equal(kwargs['y_err'], np.array([1.0, 1.0, 1.0])) -# np.testing.assert_array_equal(kwargs['y_obs'], np.array([10.0, 20.0, 30.0])) -# -# # Assertions for other arguments -# assert kwargs['f_obs'] is None -# assert kwargs['f_calc'] is None diff --git a/tests/unit/analysis/test_reliability_factors.py b/tests/unit/analysis/test_reliability_factors.py deleted file mode 100644 index 9f326b55..00000000 --- a/tests/unit/analysis/test_reliability_factors.py +++ /dev/null @@ -1,110 +0,0 @@ -from unittest.mock import Mock - -import numpy as np - -from easydiffraction.analysis.fitting.metrics import calculate_r_factor -from easydiffraction.analysis.fitting.metrics import calculate_r_factor_squared -from easydiffraction.analysis.fitting.metrics import calculate_rb_factor -from easydiffraction.analysis.fitting.metrics import calculate_reduced_chi_square -from easydiffraction.analysis.fitting.metrics import calculate_weighted_r_factor -from easydiffraction.analysis.fitting.metrics import get_reliability_inputs - - -def test_calculate_r_factor(): - y_obs = [10, 20, 30] - y_calc = [9, 19, 29] - result = calculate_r_factor(y_obs, y_calc) - expected = 0.05 - np.testing.assert_allclose(result, expected) - - # Test with empty arrays - assert np.isnan(calculate_r_factor([], [])) - - # Test with zero denominator - assert np.isnan(calculate_r_factor([0, 0, 0], [1, 1, 1])) - - -def test_calculate_weighted_r_factor(): - y_obs = [10, 20, 30] - y_calc = [9, 19, 29] - weights = [1, 1, 1] - result = calculate_weighted_r_factor(y_obs, y_calc, weights) - expected = 0.04629100498862757 - np.testing.assert_allclose(result, expected) - - # Test with empty arrays - assert np.isnan(calculate_weighted_r_factor([], [], [])) - - # Test with zero denominator - assert np.isnan(calculate_weighted_r_factor([0, 0, 0], [1, 1, 1], [1, 1, 1])) - - -def test_calculate_rb_factor(): - y_obs = [10, 20, 30] - y_calc = [9, 19, 29] - result = calculate_rb_factor(y_obs, y_calc) - expected = 0.05 - np.testing.assert_allclose(result, expected) - - # Test with empty arrays - assert np.isnan(calculate_rb_factor([], [])) - - # Test with zero denominator - assert np.isnan(calculate_rb_factor([0, 0, 0], [1, 1, 1])) - - -def test_calculate_r_factor_squared(): - y_obs = [10, 20, 30] - y_calc = [9, 19, 29] - result = calculate_r_factor_squared(y_obs, y_calc) - expected = 0.04629100498862757 - np.testing.assert_allclose(result, expected) - - # Test with empty arrays - assert np.isnan(calculate_r_factor_squared([], [])) - - # Test with zero denominator - assert np.isnan(calculate_r_factor_squared([0, 0, 0], [1, 1, 1])) - - -def test_calculate_reduced_chi_square(): - residuals = [1, 2, 3] - num_parameters = 1 - result = calculate_reduced_chi_square(residuals, num_parameters) - expected = 7.0 - np.testing.assert_allclose(result, expected) - - # Test with empty residuals - assert np.isnan(calculate_reduced_chi_square([], 1)) - - # Test with zero degrees of freedom - assert np.isnan(calculate_reduced_chi_square([1, 2, 3], 3)) - - -def test_get_reliability_inputs(): - # Mock inputs - sample_models = None - experiments = Mock() - calculator = Mock() - - experiments._items = { - 'experiment1': Mock( - datastore=Mock( - meas=np.array([10.0, 20.0, 30.0]), - meas_su=np.array([1.0, 1.0, 1.0]), - excluded=np.array([False, False, False]), - ) - ) - } - - def mock_calculate_pattern(sample_models, experiment, **kwargs): - experiment.datastore.calc = np.array([9.0, 19.0, 29.0]) - - calculator.calculate_pattern.side_effect = mock_calculate_pattern - - y_obs, y_calc, y_err = get_reliability_inputs(sample_models, experiments, calculator) - - # Assertions - np.testing.assert_array_equal(y_obs, [10.0, 20.0, 30.0]) - np.testing.assert_array_equal(y_calc, [9.0, 19.0, 29.0]) - np.testing.assert_array_equal(y_err, [1.0, 1.0, 1.0]) diff --git a/tests/unit/core/test_objects.py b/tests/unit/core/test_objects.py deleted file mode 100644 index 7873e85b..00000000 --- a/tests/unit/core/test_objects.py +++ /dev/null @@ -1,128 +0,0 @@ -from easydiffraction.core.objects import Collection -from easydiffraction.core.objects import Component -from easydiffraction.core.objects import Datablock -from easydiffraction.core.objects import Descriptor -from easydiffraction.core.objects import Parameter - -# filepath: src/easydiffraction/core/test_objects.py - - -def test_descriptor_initialization(): - desc = Descriptor(value=10, name='test', cif_name='test_cif', editable=True) - assert desc.value == 10 - assert desc.name == 'test' - assert desc.cif_name == 'test_cif' - assert desc.editable is True - - -def test_descriptor_value_setter(): - desc = Descriptor(value=10, name='test', cif_name='test_cif', editable=True) - desc.value = 20 - assert desc.value == 20 - - desc_non_editable = Descriptor(value=10, name='test', cif_name='test_cif', editable=False) - desc_non_editable.value = 30 - assert desc_non_editable.value == 10 # Value should not change - - -def test_parameter_initialization(): - param = Parameter( - value=5.0, - name='param', - cif_name='param_cif', - uncertainty=0.1, - free=True, - constrained=False, - min_value=0.0, - max_value=10.0, - ) - assert param.value == 5.0 - assert param.uncertainty == 0.1 - assert param.free is True - assert param.constrained is False - assert param.min == 0.0 - assert param.max == 10.0 - - -def test_component_abstract_methods(): - class TestComponent(Component): - @property - def category_key(self): - return 'test_category' - - @property - def cif_category_key(self): - return 'test_cif_category' - - comp = TestComponent() - assert comp.category_key == 'test_category' - assert comp.cif_category_key == 'test_cif_category' - - -def test_component_attribute_handling(): - class TestComponent(Component): - @property - def category_key(self): - return 'test_category' - - @property - def cif_category_key(self): - return 'test_cif_category' - - comp = TestComponent() - desc = Descriptor(value=10, name='test', cif_name='test_cif') - comp.test_attr = desc - assert comp.test_attr.value == 10 # Access Descriptor value directly - - -def test_collection_add_and_retrieve(): - class TestCollection(Collection): - @property - def _child_class(self): - return str - - collection = TestCollection() - - collection._items['item1'] = 'value1' - collection._items['item2'] = 'value2' - - assert collection['item1'] == 'value1' - assert collection['item2'] == 'value2' - - -def test_collection_iteration(): - class TestCollection(Collection): - @property - def _child_class(self): - return str - - collection = TestCollection() - - collection._items['item1'] = 'value1' - collection._items['item2'] = 'value2' - - items = list(collection) - assert items == ['value1', 'value2'] - - -def test_datablock_components(): - class TestComponent(Component): - @property - def category_key(self): - return 'test_category' - - @property - def cif_category_key(self): - return 'test_cif_category' - - class TestDatablock(Datablock): - def __init__(self): - super().__init__() - self.component1 = TestComponent() - self.component2 = TestComponent() - - datablock = TestDatablock() - items = datablock.items() - assert len(items) == 2 - assert isinstance(items[0], TestComponent) - assert isinstance(items[1], TestComponent) diff --git a/tests/unit/core/test_singletons.py b/tests/unit/core/test_singletons.py deleted file mode 100644 index f7f22212..00000000 --- a/tests/unit/core/test_singletons.py +++ /dev/null @@ -1,87 +0,0 @@ -from unittest.mock import MagicMock - -import pytest - -from easydiffraction.core.objects import Parameter -from easydiffraction.core.singletons import BaseSingleton -from easydiffraction.core.singletons import ConstraintsHandler -from easydiffraction.core.singletons import UidMapHandler - - -@pytest.fixture -def params(): - param1 = Parameter(value=1.0, name='param1', cif_name='param1_cif') - param2 = Parameter(value=2.0, name='param2', cif_name='param2_cif') - return param1, param2 - - -@pytest.fixture -def mock_aliases(params): - param1, param2 = params - mock = MagicMock() - mock._items = { - 'alias1': MagicMock(label=MagicMock(value='alias1'), param_uid=MagicMock(value=param1.uid)), - 'alias2': MagicMock(label=MagicMock(value='alias2'), param_uid=MagicMock(value=param2.uid)), - } - return mock - - -@pytest.fixture -def mock_constraints(): - mock = MagicMock() - mock._items = { - 'expr1': MagicMock(lhs_alias=MagicMock(value='alias1'), rhs_expr=MagicMock(value='alias2 + 1')), - 'expr2': MagicMock(lhs_alias=MagicMock(value='alias2'), rhs_expr=MagicMock(value='alias1 * 2')), - } - return mock - - -def test_base_singleton(): - class TestSingleton(BaseSingleton): - pass - - instance1 = TestSingleton.get() - instance2 = TestSingleton.get() - - assert instance1 is instance2 # Ensure only one instance is created - - -def test_uid_map_handler(params): - param1, param2 = params - handler = UidMapHandler.get() - uid_map = handler.get_uid_map() - - assert uid_map[param1.uid] is param1 - assert uid_map[param2.uid] is param2 - assert uid_map[param1.uid].uid == 'None.param1_cif' - assert uid_map[param2.uid].uid == 'None.param2_cif' - - -def test_constraints_handler_set_aliases(mock_aliases, params): - param1, param2 = params - handler = ConstraintsHandler.get() - handler.set_aliases(mock_aliases) - - assert handler._alias_to_param['alias1'].param_uid.value is param1.uid - assert handler._alias_to_param['alias2'].param_uid.value is param2.uid - - -def test_constraints_handler_set_constraints(mock_constraints): - handler = ConstraintsHandler.get() - handler.set_constraints(mock_constraints) - - assert len(handler._parsed_constraints) == 2 - assert handler._parsed_constraints[0] == ('alias1', 'alias2 + 1') - assert handler._parsed_constraints[1] == ('alias2', 'alias1 * 2') - - -def test_constraints_handler_apply(mock_aliases, mock_constraints, params): - param1, _ = params - handler = ConstraintsHandler.get() - handler.set_aliases(mock_aliases) - handler.set_constraints(mock_constraints) - - handler.apply() - - assert param1.value == 3.0 - assert param1.constrained is True diff --git a/tests/unit/easydiffraction/analysis/calculators/test_base.py b/tests/unit/easydiffraction/analysis/calculators/test_base.py new file mode 100644 index 00000000..53a1684d --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_base.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.calculators.base as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.calculators.base' + + +def test_calculator_base_get_valid_linked_phases_filters_missing(): + from easydiffraction.analysis.calculators.base import CalculatorBase + + class DummyCalc(CalculatorBase): + @property + def name(self): + return 'dummy' + + @property + def engine_imported(self): + return True + + def calculate_structure_factors(self, sample_model, experiment): + pass + + def _calculate_single_model_pattern(self, sample_model, experiment, called_by_minimizer): + return np.zeros_like(experiment.datastore.x) + + class DummyLinked: + def __init__(self, entry_name): + self._identity = type('I', (), {'category_entry_name': entry_name}) + self.scale = type('S', (), {'value': 1.0}) + self.id = type('ID', (), {'value': entry_name}) + + class DummyStore: + def __init__(self, n=5): + self.x = np.arange(n, dtype=float) + + class DummyExperiment: + def __init__(self, linked): + self.linked_phases = linked + self.datastore = DummyStore() + + def _public(): + return [] + + self._public_attrs = _public + + class DummySampleModels(dict): + @property + def names(self): + return list(self.keys()) + + calc = DummyCalc() + expt = DummyExperiment([DummyLinked('present'), DummyLinked('absent')]) + sm = DummySampleModels({'present': object()}) + valid = calc._get_valid_linked_phases(sm, expt) + assert len(valid) == 1 and valid[0]._identity.category_entry_name == 'present' diff --git a/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py new file mode 100644 index 00000000..d0eea743 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_crysfml.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_module_import(): + import easydiffraction.analysis.calculators.crysfml as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.calculators.crysfml' + + +def test_crysfml_engine_flag_and_structure_factors_raises(): + from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator + + calc = CrysfmlCalculator() + # engine_imported is a boolean flag; it may be False in our env + assert isinstance(calc.engine_imported, bool) + with pytest.raises(NotImplementedError): + calc.calculate_structure_factors(sample_models=None, experiments=None) + + +def test_crysfml_adjust_pattern_length_truncates(): + from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator + + calc = CrysfmlCalculator() + long = list(range(10)) + out = calc._adjust_pattern_length(long, target_length=4) + assert out == [0, 1, 2, 3] diff --git a/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py new file mode 100644 index 00000000..f1bfd927 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_cryspy.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.calculators.cryspy as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.calculators.cryspy' + + +def test_cryspy_calculator_engine_flag_and_converters(): + # These tests avoid requiring real cryspy by not invoking heavy paths + from easydiffraction.analysis.calculators.cryspy import CryspyCalculator + + calc = CryspyCalculator() + # engine_imported is boolean (may be False if cryspy not installed) + assert isinstance(calc.engine_imported, bool) + + # Converters should just delegate/format without external deps + class DummySample: + @property + def as_cif(self): + return 'data_x' + + # _convert_sample_model_to_cryspy_cif returns input as_cif + assert calc._convert_sample_model_to_cryspy_cif(DummySample()) == 'data_x' diff --git a/tests/unit/easydiffraction/analysis/calculators/test_factory.py b/tests/unit/easydiffraction/analysis/calculators/test_factory.py new file mode 100644 index 00000000..2fed1816 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_factory.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_list_and_show_supported_calculators_do_not_crash(capsys, monkeypatch): + from easydiffraction.analysis.calculators.factory import CalculatorFactory + + # Simulate no engines available by forcing engine_imported to False + class DummyCalc: + def __call__(self): + return self + + @property + def engine_imported(self): + return False + + monkeypatch = monkeypatch # keep name + monkeypatch.setitem( + CalculatorFactory._potential_calculators, + 'dummy', + { + 'description': 'Dummy calc', + 'class': DummyCalc, + }, + ) + + lst = CalculatorFactory.list_supported_calculators() + assert isinstance(lst, list) + + CalculatorFactory.show_supported_calculators() + out = capsys.readouterr().out + # Should print the paragraph title + assert 'Supported calculators' in out + + +def test_create_calculator_unknown_returns_none(capsys): + from easydiffraction.analysis.calculators.factory import CalculatorFactory + + obj = CalculatorFactory.create_calculator('this_is_unknown') + assert obj is None + out = capsys.readouterr().out + assert 'Unknown calculator' in out diff --git a/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py new file mode 100644 index 00000000..fdb7783e --- /dev/null +++ b/tests/unit/easydiffraction/analysis/calculators/test_pdffit.py @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.calculators.pdffit as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.calculators.pdffit' + + +def test_pdffit_engine_flag_and_hkl_message(capsys): + from easydiffraction.analysis.calculators.pdffit import PdffitCalculator + + calc = PdffitCalculator() + assert isinstance(calc.engine_imported, bool) + # calculate_structure_factors prints fixed message and returns [] by contract + out = calc.calculate_structure_factors(sample_models=None, experiments=None) + assert out == [] + # The method prints a note + printed = capsys.readouterr().out + assert 'HKLs (not applicable)' in printed + + +def test_pdffit_cif_v2_to_v1_regex_behavior(monkeypatch): + # Exercise the regex conversion path indirectly by providing minimal objects + from easydiffraction.analysis.calculators.pdffit import PdffitCalculator + + class DummyParam: + def __init__(self, v): + self.value = v + + class DummyPeak: + # provide required attributes used in calculation + def __init__(self): + self.sharp_delta_1 = DummyParam(0.0) + self.sharp_delta_2 = DummyParam(0.0) + self.damp_particle_diameter = DummyParam(0.0) + self.cutoff_q = DummyParam(1.0) + self.damp_q = DummyParam(0.0) + self.broad_q = DummyParam(0.0) + + class DummyLinkedPhases(dict): + def __getitem__(self, k): + return type('LP', (), {'scale': DummyParam(1.0)})() + + class DummyExperiment: + def __init__(self): + self.name = 'E' + self.peak = DummyPeak() + self.datastore = type('D', (), {'x': np.linspace(0.0, 1.0, 5)})() + self.type = type('T', (), {'radiation_probe': type('P', (), {'value': 'neutron'})()})() + self.linked_phases = DummyLinkedPhases() + + class DummySampleModel: + name = 'PhaseA' + + @property + def as_cif(self): + # CIF v2-like tags with dots between letters + return '_atom.site.label A1\n_cell.length_a 1.0' + + # Monkeypatch PdfFit and parser to avoid real engine usage + import easydiffraction.analysis.calculators.pdffit as mod + + class FakePdf: + def add_structure(self, s): + pass + + def setvar(self, *a, **k): + pass + + def read_data_lists(self, *a, **k): + pass + + def calc(self): + pass + + def getpdf_fit(self): + return [0.0, 0.0, 0.0, 0.0, 0.0] + + class FakeParser: + def parse(self, text): + # Ensure the dot between letters is converted to underscore + assert '_atom_site_label' in text or '_atom.site.label' not in text + return object() + + monkeypatch.setattr(mod, 'PdfFit', FakePdf) + monkeypatch.setattr(mod, 'pdffit_cif_parser', lambda: FakeParser()) + monkeypatch.setattr(mod, 'redirect_stdout', lambda *a, **k: None) + monkeypatch.setattr(mod, '_pdffit_devnull', None, raising=False) + + calc = PdffitCalculator() + pattern = calc._calculate_single_model_pattern( + DummySampleModel(), DummyExperiment(), called_by_minimizer=False + ) + assert isinstance(pattern, np.ndarray) and pattern.shape[0] == 5 diff --git a/tests/unit/easydiffraction/analysis/categories/test_aliases.py b/tests/unit/easydiffraction/analysis/categories/test_aliases.py new file mode 100644 index 00000000..1971ee73 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/test_aliases.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.analysis.categories.aliases import Alias +from easydiffraction.analysis.categories.aliases import Aliases + + +def test_alias_creation_and_collection(): + a = Alias(label='x', param_uid='p1') + assert a.label.value == 'x' + coll = Aliases() + coll.add(a) + # Collections index by entry name; check via names or direct indexing + assert 'x' in coll.names + assert coll['x'].param_uid.value == 'p1' diff --git a/tests/unit/easydiffraction/analysis/categories/test_constraints.py b/tests/unit/easydiffraction/analysis/categories/test_constraints.py new file mode 100644 index 00000000..96d960ae --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/test_constraints.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.analysis.categories.constraints import Constraint +from easydiffraction.analysis.categories.constraints import Constraints + + +def test_constraint_creation_and_collection(): + c = Constraint(lhs_alias='a', rhs_expr='b + c') + assert c.lhs_alias.value == 'a' + coll = Constraints() + coll.add(c) + assert 'a' in coll.names + assert coll['a'].rhs_expr.value == 'b + c' diff --git a/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py b/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py new file mode 100644 index 00000000..cf706705 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/categories/test_joint_fit_experiments.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiment +from easydiffraction.analysis.categories.joint_fit_experiments import JointFitExperiments + + +def test_joint_fit_experiment_and_collection(): + j = JointFitExperiment(id='ex1', weight=0.5) + assert j.id.value == 'ex1' + assert j.weight.value == 0.5 + coll = JointFitExperiments() + coll.add(j) + assert 'ex1' in coll.names + assert coll['ex1'].weight.value == 0.5 diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py new file mode 100644 index 00000000..184dad92 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_metrics.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_calculate_r_metrics_and_chi_square(): + from easydiffraction.analysis.fit_helpers import metrics as M + + y_obs = np.array([1.0, 2.0, 3.0]) + y_calc = np.array([1.1, 1.9, 2.8]) + weights = np.array([1.0, 2.0, 3.0]) + residuals = y_obs - y_calc + + r = M.calculate_r_factor(y_obs, y_calc) + rb = M.calculate_rb_factor(y_obs, y_calc) + rw = M.calculate_weighted_r_factor(y_obs, y_calc, weights) + r2 = M.calculate_r_factor_squared(y_obs, y_calc) + chi2 = M.calculate_reduced_chi_square(residuals, num_parameters=1) + + assert 0 <= r <= 1 and np.isfinite(r) + assert np.isclose(r, rb) + assert np.isfinite(rw) + assert np.isfinite(r2) + assert np.isfinite(chi2) + + +def test_get_reliability_inputs_collects_arrays_with_default_su(): + from easydiffraction.analysis.fit_helpers import metrics as M + + # Minimal fakes for experiments + class DS: + def __init__(self): + self.meas = np.array([1.0, 2.0]) + self.meas_su = None # triggers default ones + self.calc = np.array([1.1, 1.9]) + + class Expt: + def __init__(self): + self.datastore = DS() + + class Expts(dict): + def values(self): + return [Expt()] + + class SampleModels(dict): + pass + + class Calc: + def calculate_pattern(self, sample_models, experiment): + # no-op; calc already set in DS + return None + + y_obs, y_calc, y_err = M.get_reliability_inputs(SampleModels(), Expts(), Calc()) + assert y_obs.shape == (2,) and y_calc.shape == (2,) and y_err.shape == (2,) + assert np.allclose(y_err, 1.0) diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py new file mode 100644 index 00000000..3ac4c1c9 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_reporting.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.fit_helpers.reporting as MUT + + expected_module_name = 'easydiffraction.analysis.fit_helpers.reporting' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_fitresults_display_results_prints_and_table(capsys, monkeypatch): + # Arrange: build a minimal fake parameter object with required attributes + class Identity: + def __init__(self): + self.datablock_entry_name = 'db' + self.category_code = 'cat' + self.category_entry_name = 'entry' + + class Param: + def __init__(self, start, value, uncertainty, name='p', units='u'): + self._identity = Identity() + self._fit_start_value = start + self.value = value + self.uncertainty = uncertainty + self.name = name + self.units = units + + from easydiffraction.analysis.fit_helpers.reporting import FitResults + + params = [Param(start=1.0, value=1.2, uncertainty=0.05, name='a', units='arb')] + + # Act: create results and display with all metrics available + fr = FitResults( + success=True, + parameters=params, + reduced_chi_square=1.2345, + fitting_time=0.9876, + ) + + y_obs = [10.0, 20.0] + y_calc = [9.5, 19.5] + y_err = [1.0, 1.0] + f_obs = [5.0, 6.0] + f_calc = [5.1, 5.9] + + fr.display_results(y_obs=y_obs, y_calc=y_calc, y_err=y_err, f_obs=f_obs, f_calc=f_calc) + + # Assert: key lines printed and a table rendered + out = capsys.readouterr().out + assert 'Fit results' in out + assert 'Success: True' in out + assert 'reduced χ²' in out + assert 'R-factor (Rf)' in out + assert 'R-factor squared (Rf²)' in out + assert 'Weighted R-factor (wR)' in out + assert 'Bragg R-factor (BR)' in out + assert 'Fitted parameters:' in out + # Table border from tabulate fancy_outline + assert '╒' in out or '+' in out diff --git a/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py b/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py new file mode 100644 index 00000000..2c9495c1 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/fit_helpers/test_tracking.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.fit_helpers.tracking as MUT + + expected_module_name = 'easydiffraction.analysis.fit_helpers.tracking' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_tracker_terminal_flow_prints_and_updates_best(monkeypatch, capsys): + import easydiffraction.analysis.fit_helpers.tracking as tracking_mod + from easydiffraction.analysis.fit_helpers.tracking import FitProgressTracker + + # Force terminal branch (not notebook) + monkeypatch.setattr(tracking_mod, 'is_notebook', lambda: False) + + tracker = FitProgressTracker() + tracker.start_tracking('dummy') + tracker.start_timer() + + # First iteration sets previous and best + res1 = np.array([2.0, 1.0]) # chi2 = 5, dof depends on num params but relative change only + tracker.track(res1, parameters=[1]) + + # Second iteration small change below threshold -> no row emitted + out1 = capsys.readouterr().out + assert 'Goodness-of-fit' in out1 + + res2 = np.array([1.9, 1.0]) + tracker.track(res2, parameters=[1]) + + # Third iteration large improvement -> row emitted + res3 = np.array([0.1, 0.1]) + tracker.track(res3, parameters=[1]) + + tracker.stop_timer() + tracker.finish_tracking() + out2 = capsys.readouterr().out + assert 'Best goodness-of-fit' in out2 + assert tracker.best_iteration is not None diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_base.py b/tests/unit/easydiffraction/analysis/minimizers/test_base.py new file mode 100644 index 00000000..d608bdc2 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/minimizers/test_base.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.minimizers.base as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.minimizers.base' + + +def test_minimizer_base_fit_flow_and_finalize(): + from easydiffraction.analysis.minimizers.base import MinimizerBase + + class DummyParam: + def __init__(self, v): + self.value = v + + class DummyResult: + def __init__(self, success=True): + self.success = success + + class DummyMinimizer(MinimizerBase): + def __init__(self): + super().__init__(name='dummy', method='m', max_iterations=5) + self.synced = False + + def _prepare_solver_args(self, parameters): + # Make sure parameters list is received + assert isinstance(parameters, list) + return {'engine_parameters': {'ok': True}} + + def _run_solver(self, objective_function, **kwargs): + # Exercise calling of the provided objective + residuals = objective_function(kwargs.get('engine_parameters')) + # Update tracker so finish_tracking has valid metrics + self.tracker.track(residuals=np.array(residuals), parameters=[1]) + return DummyResult(success=True) + + def _sync_result_to_parameters(self, parameters, raw_result): + # Mark that syncing happened and tweak a parameter + self.synced = True + if parameters: + parameters[0].value = 42 + + def _check_success(self, raw_result): + return getattr(raw_result, 'success', False) + + # Provide residuals implementation used by _objective_function + def _compute_residuals( + self, engine_params, parameters, sample_models, experiments, calculator + ): + # Minimal residuals; verify engine params passed through + assert engine_params == {'ok': True} + return np.array([0.0, 0.0]) + + minim = DummyMinimizer() + + params = [DummyParam(1.0), DummyParam(2.0)] + + # Wrap minimizer's objective creator to simulate higher-level usage + objective = minim._create_objective_function( + parameters=params, + sample_models=None, + experiments=None, + calculator=None, + ) + + result = minim.fit(parameters=params, objective_function=objective) + + # Assertions: finalize populated, sync occurred, tracker captured time + assert result.success is True + assert minim.synced is True + assert isinstance(result.parameters, list) and result.parameters[0].value == 42 + # Fitting time should be a positive float + assert minim.tracker.fitting_time is not None and minim.tracker.fitting_time >= 0.0 + + +def test_minimizer_base_create_objective_function_uses_compute_residuals(): + from easydiffraction.analysis.minimizers.base import MinimizerBase + + class M(MinimizerBase): + def _prepare_solver_args(self, parameters): + return {} + + def _run_solver(self, objective_function, **kwargs): + return None + + def _sync_result_to_parameters(self, parameters, raw_result): + pass + + def _check_success(self, raw_result): + return True + + def _compute_residuals( + self, engine_params, parameters, sample_models, experiments, calculator + ): + # Return a deterministic vector to assert against + return np.array([1.0, 2.0, 3.0]) + + m = M() + f = m._create_objective_function( + parameters=[], sample_models=None, experiments=None, calculator=None + ) + out = f({}) + assert np.allclose(out, np.array([1.0, 2.0, 3.0])) diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py new file mode 100644 index 00000000..b3bb4e03 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/minimizers/test_dfols.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.minimizers.dfols as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.minimizers.dfols' + + +def test_dfols_prepare_run_and_sync(monkeypatch): + from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer + + class P: + def __init__(self, v, lo=-np.inf, hi=np.inf): + self.value = v + self.fit_min = lo + self.fit_max = hi + self.uncertainty = None + + class FakeRes: + EXIT_SUCCESS = 0 + + def __init__(self): + self.x = np.array([3.0, 4.0]) + self.flag = 0 + + # Patch dfols.solve to return our FakeRes + import easydiffraction.analysis.minimizers.dfols as mod + + def fake_solve(fun, x0, bounds, maxfun): + # Verify we pass reasonable arguments + assert isinstance(x0, np.ndarray) and x0.shape[0] == 2 + assert isinstance(bounds, tuple) and all(isinstance(b, np.ndarray) for b in bounds) + return FakeRes() + + monkeypatch.setattr(mod, 'solve', fake_solve) + + minim = DfolsMinimizer(max_iterations=10) + params = [P(1.0, lo=0.0, hi=5.0), P(2.0, lo=1.0, hi=6.0)] + kwargs = minim._prepare_solver_args(params) + assert set(kwargs.keys()) == {'x0', 'bounds'} + res = minim._run_solver(lambda p: np.array([0.0]), **kwargs) + # Sync back values and check success flag handling + minim._sync_result_to_parameters(params, res) + assert params[0].value == 3.0 and params[1].value == 4.0 + assert params[0].uncertainty is None and params[1].uncertainty is None + assert minim._check_success(res) is True diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_factory.py b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py new file mode 100644 index 00000000..a429342d --- /dev/null +++ b/tests/unit/easydiffraction/analysis/minimizers/test_factory.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_minimizer_factory_list_and_show(capsys): + from easydiffraction.analysis.minimizers.factory import MinimizerFactory + + lst = MinimizerFactory.list_available_minimizers() + assert isinstance(lst, list) and len(lst) >= 1 + MinimizerFactory.show_available_minimizers() + out = capsys.readouterr().out + assert 'Supported minimizers' in out + + +def test_minimizer_factory_unknown_raises(): + from easydiffraction.analysis.minimizers.factory import MinimizerFactory + + try: + MinimizerFactory.create_minimizer('___unknown___') + except ValueError as e: + assert 'Unknown minimizer' in str(e) + else: + assert False, 'Expected ValueError' + + +def test_minimizer_factory_create_known_and_register(monkeypatch): + from easydiffraction.analysis.minimizers.base import MinimizerBase + from easydiffraction.analysis.minimizers.factory import MinimizerFactory + + # Create a known minimizer instance (lmfit (leastsq) exists) + m = MinimizerFactory.create_minimizer('lmfit (leastsq)') + assert isinstance(m, MinimizerBase) + + # Register a custom minimizer and create it + class Custom(MinimizerBase): + def _prepare_solver_args(self, parameters): + return {} + + def _run_solver(self, objective_function, **kwargs): + return None + + def _sync_result_to_parameters(self, raw_result, parameters): + pass + + def _check_success(self, raw_result): + return True + + MinimizerFactory.register_minimizer( + name='custom-test', minimizer_cls=Custom, method=None, description='x' + ) + created = MinimizerFactory.create_minimizer('custom-test') + assert isinstance(created, Custom) diff --git a/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py new file mode 100644 index 00000000..e7322bcd --- /dev/null +++ b/tests/unit/easydiffraction/analysis/minimizers/test_lmfit.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import types + +import numpy as np + + +def test_module_import(): + import easydiffraction.analysis.minimizers.lmfit as MUT + + assert MUT.__name__ == 'easydiffraction.analysis.minimizers.lmfit' + + +def test_lmfit_prepare_and_sync(monkeypatch): + from easydiffraction.analysis.minimizers.lmfit import LmfitMinimizer + + class P: + def __init__(self, name, value, free=True, lo=-np.inf, hi=np.inf): + self._minimizer_uid = name + self._value = value + self.free = free + self.fit_min = lo + self.fit_max = hi + self.uncertainty = None + + @property + def value(self): + return self._value + + @value.setter + def value(self, v): + self._value = v + + # Fake lmfit.Parameters and result structure + class FakeParam: + def __init__(self, value, stderr=None): + self.value = value + self.stderr = stderr + + class FakeParams(dict): + def add(self, name, value, vary, min, max): + self[name] = types.SimpleNamespace(value=value, vary=vary, min=min, max=max) + + class FakeResult: + def __init__(self): + self.params = {'p1': FakeParam(10.0, stderr=0.5), 'p2': FakeParam(20.0, stderr=1.0)} + self.success = True + + # Monkeypatch lmfit in module namespace + import easydiffraction.analysis.minimizers.lmfit as lm + + monkeypatch.setattr( + lm, + 'lmfit', + types.SimpleNamespace(Parameters=FakeParams, minimize=lambda *a, **k: FakeResult()), + ) + + minim = LmfitMinimizer() + params = [P('p1', 1.0), P('p2', 2.0)] + + # Prepare + kwargs = minim._prepare_solver_args(params) + assert isinstance(kwargs.get('engine_parameters'), FakeParams) + # Run solver calls our fake minimize and returns FakeResult + res = minim._run_solver(lambda *a, **k: np.array([0.0]), **kwargs) + assert isinstance(res, FakeResult) + + # Sync back updates parameter values and uncertainties + minim._sync_result_to_parameters(params, res) + assert params[0].value == 10.0 and params[0].uncertainty == 0.5 + assert params[1].value == 20.0 and params[1].uncertainty == 1.0 + assert minim._check_success(res) is True diff --git a/tests/unit/easydiffraction/analysis/test_analysis.py b/tests/unit/easydiffraction/analysis/test_analysis.py new file mode 100644 index 00000000..53a2b796 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_analysis.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.analysis as MUT + + expected_module_name = 'easydiffraction.analysis.analysis' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def _make_project_with_names(names): + class ExpCol: + def __init__(self, names): + self._names = names + + @property + def names(self): + return self._names + + class P: + experiments = ExpCol(names) + sample_models = object() + _varname = 'proj' + + return P() + + +def test_show_current_calculator_and_minimizer_prints(capsys): + from easydiffraction.analysis.analysis import Analysis + + a = Analysis(project=_make_project_with_names([])) + a.show_current_calculator() + a.show_current_minimizer() + out = capsys.readouterr().out + assert 'Current calculator' in out + assert 'cryspy' in out + assert 'Current minimizer' in out + assert 'lmfit (leastsq)' in out + + +def test_current_calculator_setter_success_and_unknown(monkeypatch, capsys): + from easydiffraction.analysis import calculators as calc_pkg + from easydiffraction.analysis.analysis import Analysis + + a = Analysis(project=_make_project_with_names([])) + + # Success path + monkeypatch.setattr( + calc_pkg.factory.CalculatorFactory, + 'create_calculator', + lambda name: object(), + ) + a.current_calculator = 'pdffit' + out = capsys.readouterr().out + assert 'Current calculator changed to' in out + assert a.current_calculator == 'pdffit' + + # Unknown path (create_calculator returns None): no change + monkeypatch.setattr( + calc_pkg.factory.CalculatorFactory, + 'create_calculator', + lambda name: None, + ) + a.current_calculator = 'unknown' + assert a.current_calculator == 'pdffit' + + +def test_fit_modes_show_and_switch_to_joint(monkeypatch, capsys): + from easydiffraction.analysis.analysis import Analysis + + a = Analysis(project=_make_project_with_names(['e1', 'e2'])) + + a.show_available_fit_modes() + a.show_current_fit_mode() + out1 = capsys.readouterr().out + assert 'Available fit modes' in out1 + assert 'Current fit mode' in out1 + assert 'single' in out1 + + a.fit_mode = 'joint' + out2 = capsys.readouterr().out + assert 'Current fit mode changed to' in out2 + assert a.fit_mode == 'joint' diff --git a/tests/unit/easydiffraction/analysis/test_analysis_access_params.py b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py new file mode 100644 index 00000000..9fb8bc63 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_analysis_access_params.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_how_to_access_parameters_prints_paths_and_uids(capsys): + from easydiffraction.analysis.analysis import Analysis + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + # Build two parameters with identity metadata set directly + def make_param(db, cat, entry, name, val): + p = Parameter( + name=name, + value_spec=AttributeSpec(value=val, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=[f'_{cat}.{name}']), + ) + # Inject identity metadata (avoid parent chain) + p._identity.datablock_entry_name = lambda: db + p._identity.category_code = cat + if entry: + p._identity.category_entry_name = lambda: entry + else: + p._identity.category_entry_name = lambda: '' + return p + + p1 = make_param('db1', 'catA', '', 'alpha', 1.0) + p2 = make_param('db2', 'catB', 'row1', 'beta', 2.0) + + class Coll: + def __init__(self, params): + self.parameters = params + + class Project: + _varname = 'proj' + + def __init__(self): + self.sample_models = Coll([p1]) + self.experiments = Coll([p2]) + + a = Analysis(Project()) + a.how_to_access_parameters() + out = capsys.readouterr().out + assert 'How to access parameters' in out + # Expect code path strings + assert "proj.sample_models['db1'].catA.alpha" in out + assert "proj.experiments['db2'].catB['row1'].beta" in out + # Expect CIF uid (owner.unique_name) present for both + assert 'db1.catA.alpha' in out + assert 'db2.catB.row1.beta' in out diff --git a/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py new file mode 100644 index 00000000..473437b2 --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_analysis_show_empty.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_show_params_empty_branches(capsys): + from easydiffraction.analysis.analysis import Analysis + + class Empty: + @property + def parameters(self): + return [] + + @property + def fittable_parameters(self): + return [] + + @property + def free_parameters(self): + return [] + + class P: + sample_models = Empty() + experiments = Empty() + _varname = 'proj' + + a = Analysis(project=P()) + + # show_all_params -> warning path + a.show_all_params() + # show_fittable_params -> warning path + a.show_fittable_params() + # show_free_params -> warning path + a.show_free_params() + + out = capsys.readouterr().out + assert ( + 'No parameters found' in out + or 'No fittable parameters' in out + or 'No free parameters' in out + ) diff --git a/tests/unit/easydiffraction/analysis/test_calculation.py b/tests/unit/easydiffraction/analysis/test_calculation.py new file mode 100644 index 00000000..7e55f26f --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_calculation.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.calculation as MUT + + expected_module_name = 'easydiffraction.analysis.calculation' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_calculator_wrapper_set_and_calls(monkeypatch): + from easydiffraction.analysis.calculation import Calculator + + calls = {} + + class DummyCalc: + def calculate_structure_factors(self, sm, exps): + calls['sf'] = True + return ['hkl'] + + def calculate_pattern(self, sm, expt): + calls['pat'] = True + + class DummyFactory: + def create_calculator(self, engine): + calls['engine'] = engine + return DummyCalc() + + c = Calculator(engine='cryspy') + # Inject dummy factory + c.calculator_factory = DummyFactory() + c.set_calculator('pdffit') + assert calls['engine'] == 'pdffit' + + # Call delegates + assert c.calculate_structure_factors('sm', 'exps') == ['hkl'] + c.calculate_pattern('sm', 'expt') + assert calls['sf'] and calls['pat'] diff --git a/tests/unit/easydiffraction/analysis/test_fitting.py b/tests/unit/easydiffraction/analysis/test_fitting.py new file mode 100644 index 00000000..95ed171c --- /dev/null +++ b/tests/unit/easydiffraction/analysis/test_fitting.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.analysis.fitting as MUT + + expected_module_name = 'easydiffraction.analysis.fitting' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_fitter_early_exit_when_no_params(capsys, monkeypatch): + from easydiffraction.analysis.fitting import Fitter + + class DummyCollection: + free_parameters = [] + + def __init__(self): + self._names = ['e1'] + + @property + def names(self): + return self._names + + class DummyMin: + tracker = type('T', (), {'track': staticmethod(lambda a, b: a)})() + + def fit(self, params, obj): + return None + + f = Fitter() + # Avoid creating a real minimizer + f.minimizer = DummyMin() + f.fit(sample_models=DummyCollection(), experiments=DummyCollection(), calculator=object()) + out = capsys.readouterr().out + assert 'No parameters selected for fitting' in out diff --git a/tests/unit/easydiffraction/core/test_category.py b/tests/unit/easydiffraction/core/test_category.py new file mode 100644 index 00000000..6d4ae695 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_category.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.core.category import CategoryCollection +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import StringDescriptor +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import DataTypes +from easydiffraction.io.cif.handler import CifHandler + + +class SimpleItem(CategoryItem): + def __init__(self, entry_name): + super().__init__() + self._identity.category_code = 'simple' + self._identity.category_entry_name = entry_name + object.__setattr__( + self, + '_a', + StringDescriptor( + name='a', + description='', + value_spec=AttributeSpec(value='x', type_=DataTypes.STRING, default=''), + cif_handler=CifHandler(names=['_simple.a']), + ), + ) + object.__setattr__( + self, + '_b', + StringDescriptor( + name='b', + description='', + value_spec=AttributeSpec(value='y', type_=DataTypes.STRING, default=''), + cif_handler=CifHandler(names=['_simple.b']), + ), + ) + + @property + def a(self): + return self._a + + @property + def b(self): + return self._b + + +class SimpleCollection(CategoryCollection): + def __init__(self): + super().__init__(item_type=SimpleItem) + + +def test_category_item_str_and_properties(): + it = SimpleItem('name1') + s = str(it) + assert '<' in s and 'a=' in s and 'b=' in s + assert it.unique_name.endswith('.simple.name1') or it.unique_name == 'simple.name1' + assert len(it.parameters) == 2 + + +def test_category_collection_str_and_cif_calls(): + c = SimpleCollection() + c.add(SimpleItem('n1')) + c.add(SimpleItem('n2')) + s = str(c) + assert 'collection' in s and '2 items' in s + # as_cif delegates to serializer; should be a string (possibly empty) + assert isinstance(c.as_cif, str) diff --git a/tests/unit/easydiffraction/core/test_collection.py b/tests/unit/easydiffraction/core/test_collection.py new file mode 100644 index 00000000..b8388f68 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_collection.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_collection_add_get_delete_and_names(): + from easydiffraction.core.collection import CollectionBase + from easydiffraction.core.identity import Identity + + class Item: + def __init__(self, name): + self._identity = Identity(owner=self, category_entry=lambda: name) + + class MyCollection(CollectionBase): + @property + def parameters(self): + return [] + + @property + def as_cif(self) -> str: + return '' + + c = MyCollection(item_type=Item) + a = Item('a') + b = Item('b') + c['a'] = a + c['b'] = b + assert c['a'] is a and c['b'] is b + a2 = Item('a') + c['a'] = a2 + assert c['a'] is a2 and len(list(c.keys())) == 2 + del c['b'] + assert list(c.names) == ['a'] diff --git a/tests/unit/easydiffraction/core/test_datablock.py b/tests/unit/easydiffraction/core/test_datablock.py new file mode 100644 index 00000000..7e227c22 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_datablock.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_datablock_collection_add_and_filters_with_real_parameters(): + from easydiffraction.core.category import CategoryItem + from easydiffraction.core.datablock import DatablockCollection + from easydiffraction.core.datablock import DatablockItem + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + class Cat(CategoryItem): + def __init__(self): + super().__init__() + self._identity.category_code = 'cat' + self._identity.category_entry_name = 'e1' + # real Parameters + self._p1 = Parameter( + name='p1', + description='', + value_spec=AttributeSpec(value=1.0, type_=DataTypes.NUMERIC, default=0.0), + units='', + cif_handler=CifHandler(names=['_cat.p1']), + ) + self._p2 = Parameter( + name='p2', + description='', + value_spec=AttributeSpec(value=2.0, type_=DataTypes.NUMERIC, default=0.0), + units='', + cif_handler=CifHandler(names=['_cat.p2']), + ) + # Make p2 constrained and not free + self._p2._constrained = True + self._p2._free = False + # Mark p1 free to be included in free_parameters + self._p1.free = True + + @property + def p1(self): + return self._p1 + + @property + def p2(self): + return self._p2 + + class Block(DatablockItem): + def __init__(self, name): + super().__init__() + # set datablock entry name + self._identity.datablock_entry_name = lambda: name + # include the category as attribute so DatablockItem.parameters picks them up + self._cat = Cat() + + @property + def cat(self): + return self._cat + + coll = DatablockCollection(item_type=Block) + a = Block('A') + b = Block('B') + coll.add(a) + coll.add(b) + # parameters collection aggregates from both blocks (p1 & p2 each) + params = coll.parameters + assert len(params) == 4 + # fittable excludes constrained parameters + fittable = coll.fittable_parameters + assert all(isinstance(p, Parameter) for p in fittable) + assert len(fittable) == 2 # only p1 from each block + # free is subset of fittable where free=True (true for p1) + free_params = coll.free_parameters + assert free_params == fittable diff --git a/tests/unit/easydiffraction/core/test_diagnostic.py b/tests/unit/easydiffraction/core/test_diagnostic.py new file mode 100644 index 00000000..ffb11457 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_diagnostic.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydiffraction.core.diagnostic import Diagnostics + + +class DummyLogger: + def __init__(self): + self.last = None + + def error(self, message, exc_type): + self.last = ('error', message, exc_type) + + def debug(self, message): + self.last = ('debug', message) + + +def test_diagnostics_error_and_debug_monkeypatch(monkeypatch: pytest.MonkeyPatch): + dummy = DummyLogger() + # Patch module-level log used by Diagnostics + import easydiffraction.core.diagnostic as diag_mod + + monkeypatch.setattr(diag_mod, 'log', dummy, raising=True) + + Diagnostics.no_value('x', default=1) + assert dummy.last[0] == 'debug' + + Diagnostics.type_mismatch('x', value=3, expected_type=int) + kind, msg, exc = dummy.last + assert kind == 'error' + assert issubclass(exc, TypeError) diff --git a/tests/unit/easydiffraction/core/test_factory.py b/tests/unit/easydiffraction/core/test_factory.py new file mode 100644 index 00000000..040232ca --- /dev/null +++ b/tests/unit/easydiffraction/core/test_factory.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_module_import(): + import easydiffraction.core.factory as MUT + + expected_module_name = 'easydiffraction.core.factory' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_validate_args_valid_and_invalid(): + import easydiffraction.core.factory as MUT + + specs = [ + {'required': ['a'], 'optional': ['b']}, + {'required': ['x', 'y'], 'optional': []}, + ] + # valid: only required + MUT.FactoryBase._validate_args({'a'}, specs, 'Thing') + # valid: required + optional subset + MUT.FactoryBase._validate_args({'a', 'b'}, specs, 'Thing') + MUT.FactoryBase._validate_args({'x', 'y'}, specs, 'Thing') + # invalid: unknown key + with pytest.raises(ValueError): + MUT.FactoryBase._validate_args({'a', 'c'}, specs, 'Thing') diff --git a/tests/unit/easydiffraction/core/test_guard.py b/tests/unit/easydiffraction/core/test_guard.py new file mode 100644 index 00000000..9f9e9a90 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_guard.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_guard_allows_only_declared_public_properties_and_links_parent(monkeypatch): + from easydiffraction.core.guard import GuardedBase + + class Child(GuardedBase): + @property + def parameters(self): + return [] + + @property + def as_cif(self) -> str: + return '' + + @property + def value(self): + return getattr(self, '_value', 0) + + @value.setter + def value(self, v): + self._assign_attr('_value', v) + + class Parent(GuardedBase): + def __init__(self): + super().__init__() + self._child = Child() + + @property + def child(self): + return self._child + + @property + def parameters(self): + return [] + + @property + def as_cif(self) -> str: + return '' + + p = Parent() + # Writable property on child should set and link parent + p.child.value = 3 + assert p.child.value == 3 + # Private assign links parent automatically + assert p.child._parent is p + + # Unknown attribute should raise AttributeError under current logging mode + with pytest.raises(AttributeError): + p.child.unknown_attr = 1 diff --git a/tests/unit/easydiffraction/core/test_identity.py b/tests/unit/easydiffraction/core/test_identity.py new file mode 100644 index 00000000..1093e872 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_identity.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_identity_direct_and_parent_resolution(): + from easydiffraction.core.identity import Identity + + class Node: + def __init__(self, name=None, parent=None): + self._identity = Identity(owner=self, category_code=name) + if parent is not None: + self._parent = parent + + parent = Node(name='cat') + child = Node(parent=parent) + assert parent._identity.category_code == 'cat' + assert child._identity.category_code == 'cat' + + +def test_identity_cycle_safe_resolution(): + from easydiffraction.core.identity import Identity + + class Node: + def __init__(self): + self._identity = Identity(owner=self) + + a = Node() + b = Node() + a._parent = b + b._parent = a + assert a._identity.category_code is None diff --git a/tests/unit/easydiffraction/core/test_parameters.py b/tests/unit/easydiffraction/core/test_parameters.py new file mode 100644 index 00000000..dc5afd15 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_parameters.py @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np +import pytest + + +def test_module_import(): + import easydiffraction.core.parameters as MUT + + assert MUT.__name__ == 'easydiffraction.core.parameters' + + +def test_string_descriptor_type_override_raises_type_error(): + # Creating a StringDescriptor with a NUMERIC spec should raise via Diagnostics + from easydiffraction.core.parameters import StringDescriptor + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + with pytest.raises(TypeError): + StringDescriptor( + name='title', + value_spec=AttributeSpec(value='abc', type_=DataTypes.NUMERIC, default='x'), + description='Title text', + cif_handler=CifHandler(names=['_proj.title']), + ) + + +def test_numeric_descriptor_str_includes_units(): + from easydiffraction.core.parameters import NumericDescriptor + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + d = NumericDescriptor( + name='w', + value_spec=AttributeSpec(value=1.23, type_=DataTypes.NUMERIC, default=0.0), + units='deg', + cif_handler=CifHandler(names=['_x.w']), + ) + s = str(d) + assert s.startswith('<') and s.endswith('>') and 'deg' in s and 'w' in s + + +def test_parameter_string_repr_and_as_cif_and_flags(): + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + p = Parameter( + name='a', + value_spec=AttributeSpec(value=2.5, type_=DataTypes.NUMERIC, default=0.0), + units='A', + cif_handler=CifHandler(names=['_param.a']), + ) + # Update extra attributes + p.uncertainty = 0.1 + p.free = True + + s = str(p) + assert '± 0.1' in s and 'A' in s and '(free=True)' in s + + # CIF line is ` ` + assert p.as_cif == '_param.a 2.5' + + # CifHandler uid is owner's unique_name (parameter name here) + assert p._cif_handler.uid == p.unique_name == 'a' + + +def test_parameter_uncertainty_must_be_non_negative(): + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + p = Parameter( + name='b', + value_spec=AttributeSpec(value=1.0, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=['_param.b']), + ) + with pytest.raises(TypeError): + p.uncertainty = -0.5 + + +def test_parameter_fit_bounds_assign_and_read(): + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + p = Parameter( + name='c', + value_spec=AttributeSpec(value=0.0, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=['_param.c']), + ) + p.fit_min = -1.0 + p.fit_max = 10.0 + assert np.isclose(p.fit_min, -1.0) and np.isclose(p.fit_max, 10.0) diff --git a/tests/unit/easydiffraction/core/test_singletons.py b/tests/unit/easydiffraction/core/test_singletons.py new file mode 100644 index 00000000..b798c345 --- /dev/null +++ b/tests/unit/easydiffraction/core/test_singletons.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_uid_map_handler_singleton_and_add_and_replace_uid(): + from easydiffraction.core.parameters import NumericDescriptor + from easydiffraction.core.singletons import UidMapHandler + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.io.cif.handler import CifHandler + + h1 = UidMapHandler.get() + h2 = UidMapHandler.get() + assert h1 is h2 + + # Clean slate for test + h1.get_uid_map().clear() + + d = NumericDescriptor( + name='p', + value_spec=AttributeSpec(value=1.0, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=['_x.p']), + ) + h1.add_to_uid_map(d) + assert d.uid in h1.get_uid_map() + + # replace_uid: bad key + with pytest.raises(KeyError): + h1.replace_uid('missing', 'new') + + # replace_uid: success path + old_uid = d.uid + new_uid = old_uid + 'x' + h1.replace_uid(old_uid, new_uid) + assert new_uid in h1.get_uid_map() and old_uid not in h1.get_uid_map() + + +def test_uid_map_handler_rejects_non_descriptor(): + from easydiffraction.core.singletons import UidMapHandler + + h = UidMapHandler.get() + with pytest.raises(TypeError): + h.add_to_uid_map(object()) diff --git a/tests/unit/easydiffraction/core/test_validation.py b/tests/unit/easydiffraction/core/test_validation.py new file mode 100644 index 00000000..4a07121a --- /dev/null +++ b/tests/unit/easydiffraction/core/test_validation.py @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.core.validation as MUT + + expected_module_name = 'easydiffraction.core.validation' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_type_validator_accepts_and_rejects(monkeypatch): + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.utils.logging import log + + # So that errors do not raise in test process + log.configure(reaction=log.Reaction.WARN) + + spec = AttributeSpec(type_=DataTypes.STRING, default='abc') + # valid + expected = 'xyz' + actual = spec.validated('xyz', name='p') + assert expected == actual + # invalid -> fallback to default + expected_fallback = 'abc' + actual_fallback = spec.validated(10, name='p') + assert expected_fallback == actual_fallback + + +def test_range_validator_bounds(monkeypatch): + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.core.validation import RangeValidator + from easydiffraction.utils.logging import log + + log.configure(reaction=log.Reaction.WARN) + spec = AttributeSpec( + type_=DataTypes.NUMERIC, default=1.0, content_validator=RangeValidator(ge=0, le=2) + ) + # inside range + expected = 1.5 + actual = spec.validated(1.5, name='p') + assert expected == actual + # outside -> fallback default + expected_fallback = 1.0 + actual_fallback = spec.validated(5.0, name='p') + assert expected_fallback == actual_fallback + + +def test_membership_and_regex_validators(monkeypatch): + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import MembershipValidator + from easydiffraction.core.validation import RegexValidator + from easydiffraction.utils.logging import log + + log.configure(reaction=log.Reaction.WARN) + mspec = AttributeSpec(default='b', content_validator=MembershipValidator(['a', 'b'])) + assert mspec.validated('a', name='m') == 'a' + # reject -> fallback default + assert mspec.validated('c', name='m') == 'b' + + rspec = AttributeSpec(default='a1', content_validator=RegexValidator(r'^[a-z]\d$')) + assert rspec.validated('b2', name='r') == 'b2' + assert rspec.validated('BAD', name='r') == 'a1' diff --git a/tests/unit/easydiffraction/crystallography/test_crystallography.py b/tests/unit/easydiffraction/crystallography/test_crystallography.py new file mode 100644 index 00000000..46bb1538 --- /dev/null +++ b/tests/unit/easydiffraction/crystallography/test_crystallography.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.crystallography.crystallography as MUT + + expected_module_name = 'easydiffraction.crystallography.crystallography' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/crystallography/test_space_groups.py b/tests/unit/easydiffraction/crystallography/test_space_groups.py new file mode 100644 index 00000000..637212e3 --- /dev/null +++ b/tests/unit/easydiffraction/crystallography/test_space_groups.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.crystallography.space_groups as MUT + + expected_module_name = 'easydiffraction.crystallography.space_groups' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_base.py b/tests/unit/easydiffraction/experiments/categories/background/test_base.py new file mode 100644 index 00000000..72b656db --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_base.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_background_base_minimal_impl_and_collection_cif(): + from easydiffraction.core.category import CategoryItem + from easydiffraction.core.collection import CollectionBase + from easydiffraction.core.parameters import Parameter + from easydiffraction.core.validation import AttributeSpec + from easydiffraction.core.validation import DataTypes + from easydiffraction.experiments.categories.background.base import BackgroundBase + from easydiffraction.io.cif.handler import CifHandler + + class ConstantBackground(CategoryItem): + def __init__(self, name: str, value: float): + # CategoryItem doesn't define __init__; call GuardedBase via super() + super().__init__() + self._identity.category_code = 'background' + self._identity.category_entry_name = name + self._level = Parameter( + name='level', + value_spec=AttributeSpec(value=value, type_=DataTypes.NUMERIC, default=0.0), + cif_handler=CifHandler(names=['_bkg.level']), + ) + + def calculate(self, x_data): + return np.full_like(np.asarray(x_data), fill_value=self._level.value, dtype=float) + + def show(self): + # No-op for tests + return None + + class BackgroundCollection(BackgroundBase): + def __init__(self): + # Initialize underlying collection with the item type + CollectionBase.__init__(self, item_type=ConstantBackground) + + def calculate(self, x_data): + x = np.asarray(x_data) + total = np.zeros_like(x, dtype=float) + for item in self.values(): + total += item.calculate(x) + return total + + def show(self) -> None: # pragma: no cover - trivial + return None + + coll = BackgroundCollection() + a = ConstantBackground('a', 1.0) + b = ConstantBackground('b', 2.0) + coll.add(a) + coll.add(b) + + # calculate sums two backgrounds externally (out of scope), here just verify item.calculate + x = np.array([0.0, 1.0, 2.0]) + assert np.allclose(a.calculate(x), [1.0, 1.0, 1.0]) + + # CIF of collection is loop with header tag and two rows + cif = coll.as_cif + assert 'loop_' in cif and '_bkg.level' in cif and '1.0' in cif and '2.0' in cif diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py b/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py new file mode 100644 index 00000000..81c608a5 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_chebyshev.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_chebyshev_background_calculate_and_cif(): + from easydiffraction.experiments.categories.background.chebyshev import ( + ChebyshevPolynomialBackground, + ) + from easydiffraction.experiments.categories.background.chebyshev import PolynomialTerm + + cb = ChebyshevPolynomialBackground() + x = np.linspace(0.0, 1.0, 5) + + # Empty background -> zeros + y0 = cb.calculate(x) + assert np.allclose(y0, 0.0) + + # Add two terms and verify CIF contains expected tags + t0 = PolynomialTerm(order=0, coef=1.0) + t1 = PolynomialTerm(order=1, coef=0.5) + cb.add(t0) + cb.add(t1) + cif = cb.as_cif + assert '_pd_background.Chebyshev_order' in cif and '_pd_background.Chebyshev_coef' in cif diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_enums.py b/tests/unit/easydiffraction/experiments/categories/background/test_enums.py new file mode 100644 index 00000000..a7d3b757 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_enums.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_background_enum_default_and_descriptions(): + import easydiffraction.experiments.categories.background.enums as MUT + + assert MUT.BackgroundTypeEnum.default() == MUT.BackgroundTypeEnum.LINE_SEGMENT + assert ( + MUT.BackgroundTypeEnum.LINE_SEGMENT.description() == 'Linear interpolation between points' + ) + assert MUT.BackgroundTypeEnum.CHEBYSHEV.description() == 'Chebyshev polynomial background' diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_factory.py b/tests/unit/easydiffraction/experiments/categories/background/test_factory.py new file mode 100644 index 00000000..0a282157 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_factory.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_background_factory_default_and_errors(): + from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum + from easydiffraction.experiments.categories.background.factory import BackgroundFactory + + # Default should produce a LineSegmentBackground + obj = BackgroundFactory.create() + assert obj.__class__.__name__.endswith('LineSegmentBackground') + + # Explicit type + obj2 = BackgroundFactory.create(BackgroundTypeEnum.CHEBYSHEV) + assert obj2.__class__.__name__.endswith('ChebyshevPolynomialBackground') + + # Unsupported enum (fake) should raise ValueError + class FakeEnum: + value = 'x' + + with pytest.raises(ValueError): + BackgroundFactory.create(FakeEnum) # type: ignore[arg-type] diff --git a/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py b/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py new file mode 100644 index 00000000..c9e9b68c --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/background/test_line_segment.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_line_segment_background_calculate_and_cif(): + from easydiffraction.experiments.categories.background.line_segment import LineSegment + from easydiffraction.experiments.categories.background.line_segment import ( + LineSegmentBackground, + ) + + bkg = LineSegmentBackground() + # No points -> zeros + x = np.array([0.0, 1.0, 2.0]) + y0 = bkg.calculate(x) + assert np.allclose(y0, [0.0, 0.0, 0.0]) + + # Add two points -> linear interpolation + p1 = LineSegment(x=0.0, y=0.0) + p2 = LineSegment(x=2.0, y=4.0) + bkg.add(p1) + bkg.add(p2) + y = bkg.calculate(x) + assert np.allclose(y, [0.0, 2.0, 4.0]) + + # CIF loop has correct header and rows + cif = bkg.as_cif + assert ( + 'loop_' in cif + and '_pd_background.line_segment_X' in cif + and '_pd_background.line_segment_intensity' in cif + ) diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_base.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_base.py new file mode 100644 index 00000000..52ec4d30 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_base.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_instrument_base_sets_category_code(): + from easydiffraction.experiments.categories.instrument.base import InstrumentBase + + class DummyInstr(InstrumentBase): + def __init__(self): + super().__init__() + + d = DummyInstr() + assert d._identity.category_code == 'instrument' diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py new file mode 100644 index 00000000..9898df52 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_cwl.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.instrument.cwl import CwlInstrument + + +def test_cwl_instrument_parameters_settable(): + instr = CwlInstrument() + instr.setup_wavelength = 2.0 + instr.calib_twotheta_offset = 0.1 + assert instr.setup_wavelength.value == 2.0 + assert instr.calib_twotheta_offset.value == 0.1 diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py new file mode 100644 index 00000000..35023d8c --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_factory.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_instrument_factory_default_and_errors(): + try: + from easydiffraction.experiments.categories.instrument.factory import InstrumentFactory + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + except ImportError as e: # pragma: no cover - environment-specific circular import + pytest.skip(f'InstrumentFactory import triggers circular import in this context: {e}') + return + + inst = InstrumentFactory.create() # defaults + assert inst.__class__.__name__ in {'CwlInstrument', 'TofInstrument'} + + # Valid combinations + inst2 = InstrumentFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH) + assert inst2.__class__.__name__ == 'CwlInstrument' + inst3 = InstrumentFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.TIME_OF_FLIGHT) + assert inst3.__class__.__name__ == 'TofInstrument' + + # Invalid scattering type + class FakeST: + pass + + with pytest.raises(ValueError): + InstrumentFactory.create(FakeST, BeamModeEnum.CONSTANT_WAVELENGTH) # type: ignore[arg-type] + + # Invalid beam mode + class FakeBM: + pass + + with pytest.raises(ValueError): + InstrumentFactory.create(ScatteringTypeEnum.BRAGG, FakeBM) # type: ignore[arg-type] diff --git a/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py b/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py new file mode 100644 index 00000000..73008da4 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/instrument/test_tof.py @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_tof_instrument_defaults_and_setters_and_parameters_and_cif(): + from easydiffraction.experiments.categories.instrument.tof import TofInstrument + + inst = TofInstrument() + + # Defaults + assert np.isclose(inst.setup_twotheta_bank.value, 150.0) + assert np.isclose(inst.calib_d_to_tof_offset.value, 0.0) + assert np.isclose(inst.calib_d_to_tof_linear.value, 10000.0) + assert np.isclose(inst.calib_d_to_tof_quad.value, -0.00001) + assert np.isclose(inst.calib_d_to_tof_recip.value, 0.0) + + # Setters + inst.setup_twotheta_bank = 160.0 + inst.calib_d_to_tof_offset = 1.0 + inst.calib_d_to_tof_linear = 9000.0 + inst.calib_d_to_tof_quad = -2e-5 + inst.calib_d_to_tof_recip = 0.5 + + assert np.isclose(inst.setup_twotheta_bank.value, 160.0) + assert np.isclose(inst.calib_d_to_tof_offset.value, 1.0) + assert np.isclose(inst.calib_d_to_tof_linear.value, 9000.0) + assert np.isclose(inst.calib_d_to_tof_quad.value, -2e-5) + assert np.isclose(inst.calib_d_to_tof_recip.value, 0.5) + + # Parameters exposure via CategoryItem.parameters + names = {p.name for p in inst.parameters} + assert { + 'twotheta_bank', + 'd_to_tof_offset', + 'd_to_tof_linear', + 'd_to_tof_quad', + 'd_to_tof_recip', + }.issubset(names) + + # CIF representation of the item should include tags in separate lines + cif = inst.as_cif + assert '_instr.2theta_bank' in cif and '_instr.d_to_tof_linear' in cif diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_base.py b/tests/unit/easydiffraction/experiments/categories/peak/test_base.py new file mode 100644 index 00000000..5499fc77 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_base.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.peak.base import PeakBase + + +def test_peak_base_identity_code(): + class DummyPeak(PeakBase): + def __init__(self): + super().__init__() + + p = DummyPeak() + assert p._identity.category_code == 'peak' diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py b/tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py new file mode 100644 index 00000000..074e99f1 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_cwl.py @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_cwl_peak_classes_expose_expected_parameters_and_category(): + from easydiffraction.experiments.categories.peak.cwl import CwlPseudoVoigt + from easydiffraction.experiments.categories.peak.cwl import CwlSplitPseudoVoigt + from easydiffraction.experiments.categories.peak.cwl import CwlThompsonCoxHastings + + pv = CwlPseudoVoigt() + spv = CwlSplitPseudoVoigt() + tch = CwlThompsonCoxHastings() + + # Category code set by PeakBase + for obj in (pv, spv, tch): + assert obj._identity.category_code == 'peak' + + # Broadening parameters added by CwlBroadeningMixin + for obj in (pv, spv, tch): + names = {p.name for p in obj.parameters} + assert { + 'broad_gauss_u', + 'broad_gauss_v', + 'broad_gauss_w', + 'broad_lorentz_x', + 'broad_lorentz_y', + }.issubset(names) + + # EmpiricalAsymmetry added only for split PV + names_spv = {p.name for p in spv.parameters} + assert {'asym_empir_1', 'asym_empir_2', 'asym_empir_3', 'asym_empir_4'}.issubset(names_spv) + + # FCJ asymmetry for TCH + names_tch = {p.name for p in tch.parameters} + assert {'asym_fcj_1', 'asym_fcj_2'}.issubset(names_tch) diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py b/tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py new file mode 100644 index 00000000..99890e31 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_cwl_mixins.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.peak.cwl import CwlPseudoVoigt +from easydiffraction.experiments.categories.peak.cwl import CwlSplitPseudoVoigt +from easydiffraction.experiments.categories.peak.cwl import CwlThompsonCoxHastings + + +def test_cwl_pseudo_voigt_params_exist_and_settable(): + peak = CwlPseudoVoigt() + # Created by _add_constant_wavelength_broadening + assert peak.broad_gauss_u.name == 'broad_gauss_u' + peak.broad_gauss_u = 0.123 + assert peak.broad_gauss_u.value == 0.123 + + +def test_cwl_split_pseudo_voigt_adds_empirical_asymmetry(): + peak = CwlSplitPseudoVoigt() + # Has broadening and empirical asymmetry params + assert peak.broad_gauss_w.name == 'broad_gauss_w' + assert peak.asym_empir_1.name == 'asym_empir_1' + peak.asym_empir_2 = 0.345 + assert peak.asym_empir_2.value == 0.345 + + +def test_cwl_tch_adds_fcj_asymmetry(): + peak = CwlThompsonCoxHastings() + assert peak.asym_fcj_1.name == 'asym_fcj_1' + peak.asym_fcj_2 = 0.456 + assert peak.asym_fcj_2.value == 0.456 diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_factory.py b/tests/unit/easydiffraction/experiments/categories/peak/test_factory.py new file mode 100644 index 00000000..c9f04490 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_factory.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_peak_factory_default_and_combinations_and_errors(): + from easydiffraction.experiments.categories.peak.factory import PeakFactory + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import PeakProfileTypeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + # Defaults -> valid object for default enums + p = PeakFactory.create() + assert p._identity.category_code == 'peak' + + # Explicit valid combos + p1 = PeakFactory.create( + ScatteringTypeEnum.BRAGG, + BeamModeEnum.CONSTANT_WAVELENGTH, + PeakProfileTypeEnum.PSEUDO_VOIGT, + ) + assert p1.__class__.__name__ == 'CwlPseudoVoigt' + p2 = PeakFactory.create( + ScatteringTypeEnum.BRAGG, + BeamModeEnum.TIME_OF_FLIGHT, + PeakProfileTypeEnum.PSEUDO_VOIGT_IKEDA_CARPENTER, + ) + assert p2.__class__.__name__ == 'TofPseudoVoigtIkedaCarpenter' + p3 = PeakFactory.create( + ScatteringTypeEnum.TOTAL, + BeamModeEnum.CONSTANT_WAVELENGTH, + PeakProfileTypeEnum.GAUSSIAN_DAMPED_SINC, + ) + assert p3.__class__.__name__ == 'TotalGaussianDampedSinc' + + # Invalid scattering type + class FakeST: + pass + + with pytest.raises(ValueError): + PeakFactory.create( + FakeST, BeamModeEnum.CONSTANT_WAVELENGTH, PeakProfileTypeEnum.PSEUDO_VOIGT + ) # type: ignore[arg-type] + + # Invalid beam mode + class FakeBM: + pass + + with pytest.raises(ValueError): + PeakFactory.create(ScatteringTypeEnum.BRAGG, FakeBM, PeakProfileTypeEnum.PSEUDO_VOIGT) # type: ignore[arg-type] + + # Invalid profile type + class FakePPT: + pass + + with pytest.raises(ValueError): + PeakFactory.create(ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH, FakePPT) # type: ignore[arg-type] diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_tof.py b/tests/unit/easydiffraction/experiments/categories/peak/test_tof.py new file mode 100644 index 00000000..138c824c --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_tof.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.peak.tof import TofPseudoVoigt +from easydiffraction.experiments.categories.peak.tof import TofPseudoVoigtBackToBack +from easydiffraction.experiments.categories.peak.tof import TofPseudoVoigtIkedaCarpenter + + +def test_tof_pseudo_voigt_has_broadening_params(): + peak = TofPseudoVoigt() + assert peak.broad_gauss_sigma_0.name == 'gauss_sigma_0' + peak.broad_gauss_sigma_2 = 1.23 + assert peak.broad_gauss_sigma_2.value == 1.23 + + +def test_tof_back_to_back_adds_ikeda_carpenter(): + peak = TofPseudoVoigtBackToBack() + assert peak.asym_alpha_0.name == 'asym_alpha_0' + peak.asym_alpha_1 = 0.77 + assert peak.asym_alpha_1.value == 0.77 + + +def test_tof_ikeda_carpenter_has_mix_beta(): + peak = TofPseudoVoigtIkedaCarpenter() + assert peak.broad_mix_beta_0.name == 'mix_beta_0' diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py b/tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py new file mode 100644 index 00000000..583b8ffa --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_tof_mixins.py @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_tof_broadening_and_asymmetry_mixins(): + from easydiffraction.experiments.categories.peak.base import PeakBase + from easydiffraction.experiments.categories.peak.tof_mixins import IkedaCarpenterAsymmetryMixin + from easydiffraction.experiments.categories.peak.tof_mixins import TofBroadeningMixin + + class TofPeak(PeakBase, TofBroadeningMixin, IkedaCarpenterAsymmetryMixin): + def __init__(self): + super().__init__() + self._add_time_of_flight_broadening() + self._add_ikeda_carpenter_asymmetry() + + p = TofPeak() + names = {param.name for param in p.parameters} + # Broadening + assert { + 'gauss_sigma_0', + 'gauss_sigma_1', + 'gauss_sigma_2', + 'lorentz_gamma_0', + 'lorentz_gamma_1', + 'lorentz_gamma_2', + 'mix_beta_0', + 'mix_beta_1', + }.issubset(names) + # Asymmetry + assert {'asym_alpha_0', 'asym_alpha_1'}.issubset(names) + + # Verify setters update values + p.broad_gauss_sigma_0 = 1.0 + p.asym_alpha_1 = 0.5 + assert np.isclose(p.broad_gauss_sigma_0.value, 1.0) + assert np.isclose(p.asym_alpha_1.value, 0.5) diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_total.py b/tests/unit/easydiffraction/experiments/categories/peak/test_total.py new file mode 100644 index 00000000..741018be --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_total.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_total_gaussian_damped_sinc_parameters_and_setters(): + from easydiffraction.experiments.categories.peak.total import TotalGaussianDampedSinc + + p = TotalGaussianDampedSinc() + assert p._identity.category_code == 'peak' + names = {param.name for param in p.parameters} + assert { + 'damp_q', + 'broad_q', + 'cutoff_q', + 'sharp_delta_1', + 'sharp_delta_2', + 'damp_particle_diameter', + }.issubset(names) + + # Setters update values + p.damp_q = 0.1 + p.broad_q = 0.2 + p.cutoff_q = 30.0 + p.sharp_delta_1 = 1.0 + p.sharp_delta_2 = 2.0 + p.damp_particle_diameter = 50.0 + + vals = {param.name: param.value for param in p.parameters} + assert np.isclose(vals['damp_q'], 0.1) + assert np.isclose(vals['broad_q'], 0.2) + assert np.isclose(vals['cutoff_q'], 30.0) + assert np.isclose(vals['sharp_delta_1'], 1.0) + assert np.isclose(vals['sharp_delta_2'], 2.0) + assert np.isclose(vals['damp_particle_diameter'], 50.0) diff --git a/tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py b/tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py new file mode 100644 index 00000000..c7cd6d01 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/peak/test_total_mixins.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.experiments.categories.peak.total import TotalGaussianDampedSinc + + +def test_total_gaussian_damped_sinc_params(): + peak = TotalGaussianDampedSinc() + assert peak.damp_q.name == 'damp_q' + peak.damp_q = 0.12 + assert peak.damp_q.value == 0.12 + assert peak.broad_q.name == 'broad_q' diff --git a/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py b/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py new file mode 100644 index 00000000..536bf2d8 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/test_excluded_regions.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_excluded_regions_add_updates_datastore_and_cif(): + from types import SimpleNamespace + + from easydiffraction.experiments.categories.excluded_regions import ExcludedRegion + from easydiffraction.experiments.categories.excluded_regions import ExcludedRegions + + # Minimal fake datastore + full_x = np.array([0.0, 1.0, 2.0, 3.0]) + full_meas = np.array([10.0, 11.0, 12.0, 13.0]) + full_meas_su = np.array([1.0, 1.0, 1.0, 1.0]) + ds = SimpleNamespace( + full_x=full_x, + full_meas=full_meas, + full_meas_su=full_meas_su, + excluded=np.zeros_like(full_x, dtype=bool), + x=full_x.copy(), + meas=full_meas.copy(), + meas_su=full_meas_su.copy(), + ) + + coll = ExcludedRegions() + # stitch in a parent with datastore + object.__setattr__(coll, '_parent', SimpleNamespace(datastore=ds)) + + r = ExcludedRegion(start=1.0, end=2.0) + coll.add(r) + + # Second and third points excluded + assert np.all(ds.excluded == np.array([False, True, True, False])) + assert np.all(ds.x == np.array([0.0, 3.0])) + assert np.all(ds.meas == np.array([10.0, 13.0])) + + # CIF loop includes header tags + cif = coll.as_cif + assert 'loop_' in cif and '_excluded_region.start' in cif and '_excluded_region.end' in cif diff --git a/tests/unit/easydiffraction/experiments/categories/test_experiment_type.py b/tests/unit/easydiffraction/experiments/categories/test_experiment_type.py new file mode 100644 index 00000000..8a78444c --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/test_experiment_type.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.categories.experiment_type as MUT + + expected_module_name = 'easydiffraction.experiments.categories.experiment_type' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_experiment_type_properties_and_validation(monkeypatch): + from easydiffraction.experiments.categories.experiment_type import ExperimentType + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import RadiationProbeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + from easydiffraction.utils.logging import log + + log.configure(reaction=log.Reaction.WARN) + + et = ExperimentType( + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + # getters nominal + assert et.sample_form.value == SampleFormEnum.POWDER.value + assert et.beam_mode.value == BeamModeEnum.CONSTANT_WAVELENGTH.value + assert et.radiation_probe.value == RadiationProbeEnum.NEUTRON.value + assert et.scattering_type.value == ScatteringTypeEnum.BRAGG.value + + # try invalid value should fall back to previous (membership validator) + et.sample_form = 'invalid' + assert et.sample_form.value == SampleFormEnum.POWDER.value diff --git a/tests/unit/easydiffraction/experiments/categories/test_linked_phases.py b/tests/unit/easydiffraction/experiments/categories/test_linked_phases.py new file mode 100644 index 00000000..298d3b91 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/categories/test_linked_phases.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_linked_phases_add_and_cif_headers(): + from easydiffraction.experiments.categories.linked_phases import LinkedPhase + from easydiffraction.experiments.categories.linked_phases import LinkedPhases + + lp = LinkedPhase(id='Si', scale=2.0) + assert lp.id.value == 'Si' and lp.scale.value == 2.0 + + coll = LinkedPhases() + coll.add(lp) + + # CIF loop header presence + cif = coll.as_cif + assert 'loop_' in cif and '_pd_phase_block.id' in cif and '_pd_phase_block.scale' in cif diff --git a/tests/unit/easydiffraction/experiments/datastore/test_base.py b/tests/unit/easydiffraction/experiments/datastore/test_base.py new file mode 100644 index 00000000..04e22171 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/datastore/test_base.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.datastore.base as MUT + + expected_module_name = 'easydiffraction.experiments.datastore.base' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/datastore/test_factory.py b/tests/unit/easydiffraction/experiments/datastore/test_factory.py new file mode 100644 index 00000000..0bb9325f --- /dev/null +++ b/tests/unit/easydiffraction/experiments/datastore/test_factory.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +# Deterministic import order to avoid a circular import in isolation: +# Pre-import the experiment enums so that when DatastoreFactory pulls in +# PdDatastore -> enums, the module is already initialized and doesn't re-enter +# the experiment package initialization. +import easydiffraction.experiments.experiment.enums as _exp_enums # noqa: F401 +from easydiffraction.experiments.datastore.factory import DatastoreFactory + + +def test_create_powder_and_sc_datastores(): + ds_pd = DatastoreFactory.create(sample_form='powder', beam_mode='constant wavelength') + assert hasattr(ds_pd, 'beam_mode') + + ds_sc = DatastoreFactory.create(sample_form='single crystal', beam_mode='constant wavelength') + assert not hasattr(ds_sc, 'beam_mode') + + +def test_create_invalid_sample_form_raises(): + with pytest.raises(ValueError): + DatastoreFactory.create(sample_form='unknown', beam_mode='constant wavelength') diff --git a/tests/unit/easydiffraction/experiments/datastore/test_pd.py b/tests/unit/easydiffraction/experiments/datastore/test_pd.py new file mode 100644 index 00000000..3ecaa288 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/datastore/test_pd.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.datastore.pd as MUT + + expected_module_name = 'easydiffraction.experiments.datastore.pd' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/datastore/test_sc.py b/tests/unit/easydiffraction/experiments/datastore/test_sc.py new file mode 100644 index 00000000..35bae2c3 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/datastore/test_sc.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.datastore.sc as MUT + + expected_module_name = 'easydiffraction.experiments.datastore.sc' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/experiment/test_base.py b/tests/unit/easydiffraction/experiments/experiment/test_base.py new file mode 100644 index 00000000..bd0f93c9 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_base.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + + + +def test_module_import(): + import easydiffraction.experiments.experiment.base as MUT + + expected_module_name = 'easydiffraction.experiments.experiment.base' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_pd_experiment_peak_profile_type_switch(capsys): + from easydiffraction.experiments.categories.experiment_type import ExperimentType + from easydiffraction.experiments.experiment.base import PdExperimentBase + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import PeakProfileTypeEnum + from easydiffraction.experiments.experiment.enums import RadiationProbeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + class ConcretePd(PdExperimentBase): + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + pass + + et = ExperimentType( + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + ex = ConcretePd(name='ex1', type=et) + # valid switch using enum + ex.peak_profile_type = PeakProfileTypeEnum.PSEUDO_VOIGT + assert ex.peak_profile_type == PeakProfileTypeEnum.PSEUDO_VOIGT + # invalid string should warn and keep previous + ex.peak_profile_type = 'non-existent' + captured = capsys.readouterr().out + assert 'Unsupported' in captured or 'Unknown' in captured diff --git a/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py b/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py new file mode 100644 index 00000000..cdc13f97 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_bragg_pd.py @@ -0,0 +1,72 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np +import pytest + +from easydiffraction.experiments.categories.background.enums import BackgroundTypeEnum +from easydiffraction.experiments.categories.experiment_type import ExperimentType +from easydiffraction.experiments.experiment.bragg_pd import BraggPdExperiment +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + +def _mk_type_powder_cwl_bragg(): + return ExperimentType( + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + + +def test_background_defaults_and_change(): + expt = BraggPdExperiment(name='e1', type=_mk_type_powder_cwl_bragg()) + # default background type + assert expt.background_type == BackgroundTypeEnum.default() + + # change to a supported type + expt.background_type = BackgroundTypeEnum.CHEBYSHEV + assert expt.background_type == BackgroundTypeEnum.CHEBYSHEV + + # unknown type keeps previous type and prints warnings (no raise) + expt.background_type = 'not-a-type' # invalid string + assert expt.background_type == BackgroundTypeEnum.CHEBYSHEV + + +def test_load_ascii_data_rounds_and_defaults_sy(tmp_path: pytest.TempPathFactory): + expt = BraggPdExperiment(name='e1', type=_mk_type_powder_cwl_bragg()) + + # Case 1: provide only two columns -> sy defaults to sqrt(y) and min clipped to 1.0 + p = tmp_path / 'data2col.dat' + x = np.array([1.123456, 2.987654, 3.5]) + y = np.array([0.0, 4.0, 9.0]) + data = np.column_stack([x, y]) + np.savetxt(p, data) + + expt._load_ascii_data_to_experiment(str(p)) + + # x rounded to 4 decimals + assert np.allclose(expt.datastore.x, np.round(x, 4)) + # sy = sqrt(y) with values < 1e-4 replaced by 1.0 + expected_sy = np.sqrt(y) + expected_sy = np.where(expected_sy < 1e-4, 1.0, expected_sy) + assert np.allclose(expt.datastore.meas_su, expected_sy) + assert expt.datastore.excluded.shape == expt.datastore.x.shape + + # Case 2: three columns provided -> sy taken from file and clipped + p3 = tmp_path / 'data3col.dat' + sy = np.array([0.0, 1e-5, 0.2]) # first two should clip to 1.0 + data3 = np.column_stack([x, y, sy]) + np.savetxt(p3, data3) + expt._load_ascii_data_to_experiment(str(p3)) + expected_sy3 = np.where(sy < 1e-4, 1.0, sy) + assert np.allclose(expt.datastore.meas_su, expected_sy3) + + # Case 3: invalid shape -> currently triggers an exception (IndexError on shape[1]) + pinv = tmp_path / 'invalid.dat' + np.savetxt(pinv, np.ones((5, 1))) + with pytest.raises(Exception): + expt._load_ascii_data_to_experiment(str(pinv)) diff --git a/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py b/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py new file mode 100644 index 00000000..b4567ead --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_bragg_sc.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydiffraction.experiments.categories.experiment_type import ExperimentType +from easydiffraction.experiments.experiment.bragg_sc import BraggScExperiment +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum +from easydiffraction.utils.logging import Logger + + +def _mk_type_sc_bragg(): + return ExperimentType( + sample_form=SampleFormEnum.SINGLE_CRYSTAL.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + + +class _ConcreteBraggSc(BraggScExperiment): + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + # Not used in this test + pass + + +def test_init_and_placeholder_no_crash(monkeypatch: pytest.MonkeyPatch): + # Prevent logger from raising on attribute errors inside __init__ + monkeypatch.setattr(Logger, '_reaction', Logger.Reaction.WARN, raising=True) + expt = _ConcreteBraggSc(name='sc1', type=_mk_type_sc_bragg()) + # show_meas_chart just prints placeholder text; ensure no exception + expt.show_meas_chart() diff --git a/tests/unit/easydiffraction/experiments/experiment/test_enums.py b/tests/unit/easydiffraction/experiments/experiment/test_enums.py new file mode 100644 index 00000000..e8514b51 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_enums.py @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + + + +def test_module_import(): + import easydiffraction.experiments.experiment.enums as MUT + + expected_module_name = 'easydiffraction.experiments.experiment.enums' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_default_enums_consistency(): + import easydiffraction.experiments.experiment.enums as MUT + + assert MUT.SampleFormEnum.default() in list(MUT.SampleFormEnum) + assert MUT.ScatteringTypeEnum.default() in list(MUT.ScatteringTypeEnum) + assert MUT.RadiationProbeEnum.default() in list(MUT.RadiationProbeEnum) + assert MUT.BeamModeEnum.default() in list(MUT.BeamModeEnum) diff --git a/tests/unit/easydiffraction/experiments/experiment/test_factory.py b/tests/unit/easydiffraction/experiments/experiment/test_factory.py new file mode 100644 index 00000000..61492265 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_factory.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + + +def test_module_import(): + import easydiffraction.experiments.experiment.factory as MUT + + expected_module_name = 'easydiffraction.experiments.experiment.factory' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_experiment_factory_create_without_data_and_invalid_combo(): + import easydiffraction.experiments.experiment.factory as EF + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import RadiationProbeEnum + from easydiffraction.experiments.experiment.enums import SampleFormEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + ex = EF.ExperimentFactory.create( + name='ex1', + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.BRAGG.value, + ) + # Instance should be created (BraggPdExperiment) + assert hasattr(ex, 'type') and ex.type.sample_form.value == SampleFormEnum.POWDER.value + + # invalid combination: unexpected key + with pytest.raises(ValueError): + EF.ExperimentFactory.create(name='ex2', unexpected=True) diff --git a/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py b/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py new file mode 100644 index 00000000..b0ecdc49 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_instrument_mixin.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + + + +def test_module_import(): + import easydiffraction.experiments.experiment.instrument_mixin as MUT + + expected_module_name = 'easydiffraction.experiments.experiment.instrument_mixin' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py b/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py new file mode 100644 index 00000000..c008e579 --- /dev/null +++ b/tests/unit/easydiffraction/experiments/experiment/test_total_pd.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np +import pytest + +from easydiffraction.experiments.categories.experiment_type import ExperimentType +from easydiffraction.experiments.experiment.enums import BeamModeEnum +from easydiffraction.experiments.experiment.enums import RadiationProbeEnum +from easydiffraction.experiments.experiment.enums import SampleFormEnum +from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum +from easydiffraction.experiments.experiment.total_pd import TotalPdExperiment + + +def _mk_type_powder_total(): + return ExperimentType( + sample_form=SampleFormEnum.POWDER.value, + beam_mode=BeamModeEnum.CONSTANT_WAVELENGTH.value, + radiation_probe=RadiationProbeEnum.NEUTRON.value, + scattering_type=ScatteringTypeEnum.TOTAL.value, + ) + + +def test_load_ascii_data_pdf(tmp_path: pytest.TempPathFactory): + expt = TotalPdExperiment(name='pdf1', type=_mk_type_powder_total()) + + # Mock diffpy.utils.parsers.loaddata.loadData by creating a small parser module on sys.path + data = np.column_stack([ + np.array([0.0, 1.0, 2.0]), + np.array([10.0, 11.0, 12.0]), + np.array([0.01, 0.02, 0.03]), + ]) + f = tmp_path / 'g.dat' + np.savetxt(f, data) + + # Try to import loadData; if diffpy isn't installed, expect ImportError + try: + has_diffpy = True + except Exception: + has_diffpy = False + + if not has_diffpy: + with pytest.raises(ImportError): + expt._load_ascii_data_to_experiment(str(f)) + return + + # With diffpy available, load should succeed + expt._load_ascii_data_to_experiment(str(f)) + assert np.allclose(expt.datastore.x, data[:, 0]) + assert np.allclose(expt.datastore.meas, data[:, 1]) + assert np.allclose(expt.datastore.meas_su, data[:, 2]) diff --git a/tests/unit/easydiffraction/experiments/test_experiments.py b/tests/unit/easydiffraction/experiments/test_experiments.py new file mode 100644 index 00000000..3ffa982b --- /dev/null +++ b/tests/unit/easydiffraction/experiments/test_experiments.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.experiments.experiments as MUT + + expected_module_name = 'easydiffraction.experiments.experiments' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_experiments_show_and_remove(monkeypatch, capsys): + from easydiffraction.experiments.experiment.base import ExperimentBase + from easydiffraction.experiments.experiments import Experiments + + class DummyType: + def __init__(self): + self.sample_form = type('E', (), {'value': 'powder'}) + self.beam_mode = type('E', (), {'value': 'constant wavelength'}) + + class DummyExp(ExperimentBase): + def __init__(self, name='e1'): + super().__init__(name=name, type=DummyType()) + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + pass + + exps = Experiments() + exps.add(DummyExp('a')) + exps.add(DummyExp('b')) + exps.show_names() + out = capsys.readouterr().out + assert 'Defined experiments' in out + + # Remove by name should not raise + exps.remove('a') + # Still can show names + exps.show_names() + out2 = capsys.readouterr().out + assert 'Defined experiments' in out2 diff --git a/tests/unit/easydiffraction/io/cif/test_handler.py b/tests/unit/easydiffraction/io/cif/test_handler.py new file mode 100644 index 00000000..a5bf7de2 --- /dev/null +++ b/tests/unit/easydiffraction/io/cif/test_handler.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_cif_handler_names_and_uid(): + import easydiffraction.io.cif.handler as H + + names = ['_cell.length_a', '_cell.length_b'] + h = H.CifHandler(names=names) + assert h.names == names + assert h.uid is None + + class Owner: + unique_name = 'db.cat.entry.param' + + h.attach(Owner()) + assert h.uid == 'db.cat.entry.param' diff --git a/tests/unit/easydiffraction/io/cif/test_serialize.py b/tests/unit/easydiffraction/io/cif/test_serialize.py new file mode 100644 index 00000000..83d09978 --- /dev/null +++ b/tests/unit/easydiffraction/io/cif/test_serialize.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.io.cif.serialize as MUT + + expected_module_name = 'easydiffraction.io.cif.serialize' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_format_value_quotes_whitespace_strings(): + import easydiffraction.io.cif.serialize as MUT + + assert MUT.format_value('a b') == '"a b"' + assert MUT.format_value('ab') == 'ab' + + +def test_param_to_cif_minimal(): + import easydiffraction.io.cif.serialize as MUT + from easydiffraction.io.cif.handler import CifHandler + + class P: + def __init__(self): + self._cif_handler = CifHandler(names=['_x.y']) # noqa: SLF001 for tests + self.value = 3 + + p = P() + assert MUT.param_to_cif(p) == '_x.y 3' + + +def test_category_collection_to_cif_empty_and_one_row(): + import easydiffraction.io.cif.serialize as MUT + from easydiffraction.core.category import CategoryCollection + from easydiffraction.core.category import CategoryItem + from easydiffraction.io.cif.handler import CifHandler + + class Item(CategoryItem): + def __init__(self, name, value): + super().__init__() + self._identity.category_entry_name = name + self._p = type('P', (), {})() + self._p._cif_handler = CifHandler(names=['_x']) # noqa: SLF001 + self._p.value = value + + @property + def parameters(self): + return [self._p] + + @property + def as_cif(self) -> str: + return MUT.category_item_to_cif(self) + + coll = CategoryCollection(item_type=Item) + assert MUT.category_collection_to_cif(coll) == '' + i = Item('n1', 5) + coll['n1'] = i + out = MUT.category_collection_to_cif(coll) + assert 'loop_' in out and '_x' in out and '5' in out + + +def test_project_to_cif_assembles_present_sections(): + import easydiffraction.io.cif.serialize as MUT + + class Obj: + def __init__(self, text): + self._text = text + + @property + def as_cif(self): + return self._text + + class Project: + def __init__(self): + self.info = Obj('I') + self.sample_models = None + self.experiments = Obj('E') + self.analysis = None + self.summary = None + + p = Project() + out = MUT.project_to_cif(p) + assert out == 'I\n\nE' diff --git a/tests/unit/easydiffraction/io/cif/test_serialize_more.py b/tests/unit/easydiffraction/io/cif/test_serialize_more.py new file mode 100644 index 00000000..004bd84e --- /dev/null +++ b/tests/unit/easydiffraction/io/cif/test_serialize_more.py @@ -0,0 +1,181 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_datastore_to_cif_empty_returns_empty_string(): + import easydiffraction.io.cif.serialize as MUT + + class DS: + def _cif_mapping(self): + return {'x': '_x', 'y': '_y'} + + # x, y absent or empty should yield empty CIF + x = np.array([]) + y = np.array([]) + + out = MUT.datastore_to_cif(DS()) + assert out == '' + + +def test_datastore_to_cif_writes_rows_and_respects_max_points(): + import easydiffraction.io.cif.serialize as MUT + + class DS: + def __init__(self, n): + self.x = np.arange(n) + self.y = np.arange(n) + 100 + + def _cif_mapping(self): + return {'x': '_x', 'y': '_y'} + + # Small dataset: no ellipsis + ds_small = DS(3) + out_small = MUT.datastore_to_cif(ds_small) + assert out_small.splitlines()[0] == 'loop_' + assert '_x' in out_small and '_y' in out_small + assert '0 100' in out_small and '2 102' in out_small + + # Larger dataset with max_points enforced + ds_large = DS(10) + out_large = MUT.datastore_to_cif(ds_large, max_points=2) + lines = out_large.splitlines() + assert '...' in lines + # First two rows and last two rows present + assert '0 100' in out_large and '1 101' in out_large + assert '8 108' in out_large and '9 109' in out_large + + +def test_datablock_item_to_cif_includes_item_and_collection(): + import easydiffraction.io.cif.serialize as MUT + from easydiffraction.core.category import CategoryCollection + from easydiffraction.core.category import CategoryItem + from easydiffraction.io.cif.handler import CifHandler + + class Item(CategoryItem): + def __init__(self, val): + super().__init__() + self._p = type('P', (), {})() + self._p._cif_handler = CifHandler(names=['_aa']) # noqa: SLF001 + self._p.value = val + + @property + def parameters(self): + return [self._p] + + @property + def as_cif(self) -> str: + return MUT.category_item_to_cif(self) + + class DB: + def __init__(self): + self._identity = type('I', (), {'datablock_entry_name': 'block1'})() + # one CategoryItem-like + self.item = Item(42) + # one CategoryCollection-like + self.coll = CategoryCollection(item_type=Item) + self.coll['row1'] = Item(7) + + out = MUT.datablock_item_to_cif(DB()) + assert out.startswith('data_block1') + assert '_aa 42' in out + assert 'loop_' in out and '_aa' in out and '7' in out + + +def test_datablock_collection_to_cif_concatenates_blocks(): + import easydiffraction.io.cif.serialize as MUT + + class B: + def __init__(self, t): + self._t = t + + @property + def as_cif(self): + return self._t + + coll = {'a': B('A'), 'b': B('B')} + out = MUT.datablock_collection_to_cif(coll) + assert out == 'A\n\nB' + + +def test_project_info_to_cif_contains_core_fields(): + import easydiffraction.io.cif.serialize as MUT + from easydiffraction.project.project_info import ProjectInfo + + info = ProjectInfo(name='p1', title='My Title', description='Some description text') + out = MUT.project_info_to_cif(info) + assert '_project.id p1' in out + assert '_project.title' in out and 'My Title' in out + assert '_project.description' in out + assert '_project.created' in out and '_project.last_modified' in out + + +def test_experiment_to_cif_with_and_without_data(): + import easydiffraction.io.cif.serialize as MUT + + class DS: + def __init__(self, text): + self._text = text + + @property + def as_cif(self): + return self._text + + class Exp: + def __init__(self, data_text): + self._identity = type('I', (), {'datablock_entry_name': 'expA'})() + self.datastore = DS(data_text) + # Minimal CategoryItem to be picked up by datablock_item_to_cif + from easydiffraction.core.category import CategoryItem + from easydiffraction.io.cif.handler import CifHandler + + class Item(CategoryItem): + def __init__(self): + super().__init__() + self._p = type('P', (), {})() + self._p._cif_handler = CifHandler(names=['_k']) # noqa: SLF001 + self._p.value = 1 + + @property + def parameters(self): + return [self._p] + + @property + def as_cif(self): + return MUT.category_item_to_cif(self) + + self.item = Item() + + out_with = MUT.experiment_to_cif(Exp('loop_\n_x\n1')) + assert out_with.startswith('data_expA') and 'loop_' in out_with + + out_without = MUT.experiment_to_cif(Exp('')) + assert out_without.startswith('data_expA') and out_without.endswith('_k 1') + + +def test_analysis_to_cif_renders_all_sections(): + import easydiffraction.io.cif.serialize as MUT + + class Obj: + def __init__(self, t): + self._t = t + + @property + def as_cif(self): + return self._t + + class A: + current_calculator = 'cryspy engine' + current_minimizer = 'lmfit (leastsq)' + fit_mode = 'single' + aliases = Obj('ALIASES') + constraints = Obj('CONSTRAINTS') + + out = MUT.analysis_to_cif(A()) + lines = out.splitlines() + assert lines[0].startswith('_analysis.calculator_engine') + assert '"cryspy engine"' in lines[0] + assert lines[1].startswith('_analysis.fitting_engine') and '"lmfit (leastsq)"' in lines[1] + assert lines[2].startswith('_analysis.fit_mode') and 'single' in lines[2] + assert 'ALIASES' in out and 'CONSTRAINTS' in out diff --git a/tests/unit/easydiffraction/plotting/plotters/test_plotter_ascii.py b/tests/unit/easydiffraction/plotting/plotters/test_plotter_ascii.py new file mode 100644 index 00000000..ab68b4d8 --- /dev/null +++ b/tests/unit/easydiffraction/plotting/plotters/test_plotter_ascii.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_module_import(): + import easydiffraction.plotting.plotters.plotter_ascii as MUT + + expected_module_name = 'easydiffraction.plotting.plotters.plotter_ascii' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_ascii_plotter_plot_minimal(capsys): + from easydiffraction.plotting.plotters.plotter_ascii import AsciiPlotter + + x = np.array([0.0, 1.0, 2.0]) + y = np.array([1.0, 2.0, 3.0]) + p = AsciiPlotter() + p.plot(x=x, y_series=[y], labels=['meas'], axes_labels=['x', 'y'], title='T', height=5) + out = capsys.readouterr().out + assert 'Displaying data for selected x-range' in out diff --git a/tests/unit/easydiffraction/plotting/plotters/test_plotter_base.py b/tests/unit/easydiffraction/plotting/plotters/test_plotter_base.py new file mode 100644 index 00000000..71e52753 --- /dev/null +++ b/tests/unit/easydiffraction/plotting/plotters/test_plotter_base.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import importlib + + +def test_module_import(): + import easydiffraction.plotting.plotters.plotter_base as MUT + + expected_module_name = 'easydiffraction.plotting.plotters.plotter_base' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_default_engine_switches_with_notebook(monkeypatch): + # Force is_notebook() to True, then reload module + import easydiffraction.plotting.plotters.plotter_base as pb + import easydiffraction.utils.utils as utils + + monkeypatch.setattr(utils, 'is_notebook', lambda: True) + pb2 = importlib.reload(pb) + assert pb2.DEFAULT_ENGINE == 'plotly' + + # Now force False + monkeypatch.setattr(utils, 'is_notebook', lambda: False) + pb3 = importlib.reload(pb) + assert pb3.DEFAULT_ENGINE == 'asciichartpy' + + +def test_default_axes_labels_keys_present(): + import easydiffraction.plotting.plotters.plotter_base as pb + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + + assert (ScatteringTypeEnum.BRAGG, BeamModeEnum.CONSTANT_WAVELENGTH) in pb.DEFAULT_AXES_LABELS + assert (ScatteringTypeEnum.BRAGG, BeamModeEnum.TIME_OF_FLIGHT) in pb.DEFAULT_AXES_LABELS diff --git a/tests/unit/easydiffraction/plotting/plotters/test_plotter_plotly.py b/tests/unit/easydiffraction/plotting/plotters/test_plotter_plotly.py new file mode 100644 index 00000000..bc9270f1 --- /dev/null +++ b/tests/unit/easydiffraction/plotting/plotters/test_plotter_plotly.py @@ -0,0 +1,87 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.plotting.plotters.plotter_plotly as MUT + + expected_module_name = 'easydiffraction.plotting.plotters.plotter_plotly' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_get_trace_and_plot(monkeypatch): + import easydiffraction.plotting.plotters.plotter_plotly as pp + + # Arrange: force non-PyCharm branch and stub fig.show/HTML/display so nothing opens + monkeypatch.setattr(pp, 'is_pycharm', lambda: False) + + shown = {'count': 0} + + class DummyFig: + def update_xaxes(self, **kwargs): + pass + + def update_yaxes(self, **kwargs): + pass + + def show(self, **kwargs): + shown['count'] += 1 + + # Patch go.Scatter and go.Figure to minimal dummies + class DummyScatter: + def __init__(self, **kwargs): + self.kwargs = kwargs + + class DummyGO: + class Scatter(DummyScatter): + pass + + class Figure(DummyFig): + def __init__(self, data=None, layout=None): + self.data = data + self.layout = layout + + class Layout: + def __init__(self, **kwargs): + self.kwargs = kwargs + + class DummyPIO: + @staticmethod + def to_html(fig, include_plotlyjs=None, full_html=None, config=None): + return '
plot
' + + dummy_display_calls = {'count': 0} + + def dummy_display(obj): + dummy_display_calls['count'] += 1 + + class DummyHTML: + def __init__(self, html): + self.html = html + + monkeypatch.setattr(pp, 'go', DummyGO) + monkeypatch.setattr(pp, 'pio', DummyPIO) + monkeypatch.setattr(pp, 'display', dummy_display) + monkeypatch.setattr(pp, 'HTML', DummyHTML) + + plotter = pp.PlotlyPlotter() + + # Exercise _get_trace + x = [0, 1, 2] + y = [1, 2, 3] + trace = plotter._get_trace(x, y, label='calc') + assert hasattr(trace, 'kwargs') + assert trace.kwargs['x'] == x and trace.kwargs['y'] == y + + # Exercise plot (non-PyCharm, display path) + plotter.plot( + x, + y_series=[y], + labels=['calc'], + axes_labels=['x', 'y'], + title='t', + height=None, + ) + + # One HTML display call expected + assert dummy_display_calls['count'] == 1 or shown['count'] == 1 diff --git a/tests/unit/easydiffraction/plotting/test_plotting.py b/tests/unit/easydiffraction/plotting/test_plotting.py new file mode 100644 index 00000000..64987638 --- /dev/null +++ b/tests/unit/easydiffraction/plotting/test_plotting.py @@ -0,0 +1,138 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.plotting.plotting as MUT + + expected_module_name = 'easydiffraction.plotting.plotting' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_plotter_configuration_and_engine_switch(capsys): + from easydiffraction.plotting.plotting import Plotter + + p = Plotter() + # show config prints a table + p.show_config() + out1 = capsys.readouterr().out + assert 'Current plotter configuration' in out1 + + # show supported engines prints a table + p.show_supported_engines() + out2 = capsys.readouterr().out + assert 'Supported plotter engines' in out2 + + # Switch engine to its current value (no-op, but exercise setter) + cur = p.engine + p.engine = cur + # And to an unsupported engine (prints error and leaves engine unchanged) + p.engine = '___not_supported___' + assert p.engine == cur + + # Supported engines include both known backends + p.show_supported_engines() + out3 = capsys.readouterr().out + assert 'asciichartpy' in out3 or 'plotly' in out3 + + +def test_plotter_factory_unsupported(capsys): + from easydiffraction.plotting.plotting import PlotterFactory + + obj = PlotterFactory.create_plotter('nope') + assert obj is None + out = capsys.readouterr().out + assert 'Unsupported plotting engine' in out + + +def test_plotter_error_paths_and_filtering(capsys): + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + from easydiffraction.plotting.plotting import Plotter + + class Ptn: + def __init__(self, x=None, meas=None, calc=None, d=None): + self.x = x + self.meas = meas + self.calc = calc + self.d = d if d is not None else x + + class ExptType: + def __init__(self): + self.scattering_type = type('S', (), {'value': ScatteringTypeEnum.BRAGG}) + self.beam_mode = type('B', (), {'value': BeamModeEnum.CONSTANT_WAVELENGTH}) + + p = Plotter() + + # Error paths + p.plot_meas(Ptn(x=None, meas=None), 'E', ExptType()) + out = capsys.readouterr().out + # plot_meas uses formatting.error(...) without printing -> no stdout + assert out == '' + + p.plot_meas(Ptn(x=[1], meas=None), 'E', ExptType()) + out = capsys.readouterr().out + # Same here: no print, so no stdout + assert out == '' + + p.plot_calc(Ptn(x=None, calc=None), 'E', ExptType()) + out = capsys.readouterr().out + # plot_calc also uses formatting.error(...) without printing for x is None + assert out == '' + + p.plot_calc(Ptn(x=[1], calc=None), 'E', ExptType()) + out = capsys.readouterr().out + assert 'No calculated data available' in out or 'No calculated data' in out + + p.plot_meas_vs_calc(Ptn(x=None), 'E', ExptType()) + out = capsys.readouterr().out + assert 'No data available' in out + p.plot_meas_vs_calc(Ptn(x=[1], meas=None, calc=[1]), 'E', ExptType()) + out = capsys.readouterr().out + assert 'No measured data available' in out + p.plot_meas_vs_calc(Ptn(x=[1], meas=[1], calc=None), 'E', ExptType()) + out = capsys.readouterr().out + assert 'No calculated data available' in out + + # Filtering + import numpy as np + + p.x_min, p.x_max = 0.5, 1.5 + arr = np.array([0.0, 1.0, 2.0]) + filt = p._filtered_y_array(arr, arr, None, None) + assert np.allclose(filt, np.array([1.0])) + + +def test_plotter_routes_to_ascii_plotter(monkeypatch): + import numpy as np + + import easydiffraction.plotting.plotters.plotter_ascii as ascii_mod + from easydiffraction.experiments.experiment.enums import BeamModeEnum + from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum + from easydiffraction.plotting.plotting import Plotter + + called = {} + + def fake_plot(self, x, y_series, labels, axes_labels, title, height=None): + called['labels'] = tuple(labels) + called['axes'] = tuple(axes_labels) + called['title'] = title + + monkeypatch.setattr(ascii_mod.AsciiPlotter, 'plot', fake_plot) + + class Ptn: + def __init__(self): + self.x = np.array([0.0, 1.0]) + self.meas = np.array([1.0, 2.0]) + self.d = self.x + + class ExptType: + def __init__(self): + self.scattering_type = type('S', (), {'value': ScatteringTypeEnum.BRAGG}) + self.beam_mode = type('B', (), {'value': BeamModeEnum.CONSTANT_WAVELENGTH}) + + p = Plotter() + p.engine = 'asciichartpy' # ensure AsciiPlotter + p.plot_meas(Ptn(), 'E', ExptType()) + assert called['labels'] == ('meas',) + assert 'Measured data' in called['title'] diff --git a/tests/unit/easydiffraction/project/test_project.py b/tests/unit/easydiffraction/project/test_project.py new file mode 100644 index 00000000..7fb6e8d6 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.project.project as MUT + + expected_module_name = 'easydiffraction.project.project' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/project/test_project_d_spacing.py b/tests/unit/easydiffraction/project/test_project_d_spacing.py new file mode 100644 index 00000000..121bc905 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project_d_spacing.py @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np + + +def test_update_pattern_d_spacing_branches(monkeypatch, capsys): + # Arrange minimal experiment/collection using real Experiments + from easydiffraction.experiments.experiment.base import PdExperimentBase + from easydiffraction.experiments.experiment.instrument_mixin import InstrumentMixin + from easydiffraction.experiments.experiments import Experiments + + class DS: + def __init__(self, x): + self.x = x + self.d = None + + class Instr: + def __init__(self): + self.calib_d_to_tof_offset = type('P', (), {'value': 1.0}) + self.calib_d_to_tof_linear = type('P', (), {'value': 2.0}) + self.calib_d_to_tof_quad = type('P', (), {'value': 0.0}) + self.setup_wavelength = type('P', (), {'value': 1.54}) + + class TypeObj: + def __init__(self, beam_mode_value): + self.beam_mode = type('E', (), {'value': beam_mode_value}) + self.sample_form = type('E', (), {'value': 'powder'}) + self.scattering_type = type('E', (), {'value': 'bragg'}) + + class DummyExp(InstrumentMixin, PdExperimentBase): + def __init__(self, name, beam_mode_value): + super().__init__(name=name, type=TypeObj(beam_mode_value)) + # Replace with controlled datastore/instrument for test + self._datastore = DS(x=np.array([1.0, 2.0, 3.0])) + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + pass + + exps = Experiments() + tof_exp = DummyExp('e_tof', 'time-of-flight') + cwl_exp = DummyExp('e_cwl', 'constant wavelength') + exps.add(tof_exp) + exps.add(cwl_exp) + + from easydiffraction.project.project import Project + + proj = Project() + proj.experiments = exps + + # Act TOF + proj.update_pattern_d_spacing('e_tof') + # Act CWL + proj.update_pattern_d_spacing('e_cwl') + + # Assert: d arrays were computed + assert isinstance(tof_exp.datastore.d, np.ndarray) + assert isinstance(cwl_exp.datastore.d, np.ndarray) + + +def test_update_pattern_d_spacing_unsupported_prints(monkeypatch, capsys): + # Use real Experiments and flip the mode to unsupported post-init + from easydiffraction.experiments.experiment.base import PdExperimentBase + from easydiffraction.experiments.experiment.instrument_mixin import InstrumentMixin + from easydiffraction.experiments.experiments import Experiments + + class DS: + def __init__(self): + self.x = np.array([1.0]) + self.d = None + + class TypeObj: + def __init__(self, beam_mode_value): + self.beam_mode = type('E', (), {'value': beam_mode_value}) + self.sample_form = type('E', (), {'value': 'powder'}) + self.scattering_type = type('E', (), {'value': 'bragg'}) + + class DummyExp(InstrumentMixin, PdExperimentBase): + def __init__(self, name, beam_mode_value): + super().__init__(name=name, type=TypeObj(beam_mode_value)) + self._datastore = DS() + + def _load_ascii_data_to_experiment(self, data_path: str) -> None: + pass + + exps = Experiments() + exp = DummyExp('e1', 'constant wavelength') + # Flip to unsupported value after init to avoid factory issues + exp.type.beam_mode = type('E', (), {'value': 'unsupported'}) + exps.add(exp) + + from easydiffraction.project.project import Project + + p = Project() + p.experiments = exps + + # Act + p.update_pattern_d_spacing('e1') + + # Assert warning printed + out = capsys.readouterr().out + assert 'Unsupported beam mode' in out diff --git a/tests/unit/easydiffraction/project/test_project_info.py b/tests/unit/easydiffraction/project/test_project_info.py new file mode 100644 index 00000000..88116d81 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project_info.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_module_import(): + import easydiffraction.project.project_info as MUT + + expected_module_name = 'easydiffraction.project.project_info' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py new file mode 100644 index 00000000..8753b775 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project_load_and_summary_wrap.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_project_load_prints_and_sets_path(tmp_path, capsys): + from easydiffraction.project.project import Project + + p = Project() + dir_path = tmp_path / 'pdir' + p.load(str(dir_path)) + out = capsys.readouterr().out + assert 'Loading project' in out and str(dir_path) in out + # Path should be set on ProjectInfo + assert p.info.path == dir_path + + +def test_summary_show_project_info_wraps_description(capsys): + from easydiffraction.summary.summary import Summary + + long_desc = ' '.join(['desc'] * 50) # long text to trigger wrapping + + class Info: + title = 'T' + description = long_desc + + class Project: + def __init__(self): + self.info = Info() + + s = Summary(Project()) + s.show_project_info() + out = capsys.readouterr().out + # Title and Description paragraph headers present + assert 'PROJECT INFO' in out + assert 'Title' in out + assert 'Description' in out + # Ensure multiple lines of description were printed (wrapped) + # Keep the exact word count and verify the presence of line breaks in the description block + assert out.count('desc') == 50 # all words are present exactly once + assert '\ndesc ' in out or ' desc\n' in out # wrapped across lines diff --git a/tests/unit/easydiffraction/project/test_project_save.py b/tests/unit/easydiffraction/project/test_project_save.py new file mode 100644 index 00000000..47941f27 --- /dev/null +++ b/tests/unit/easydiffraction/project/test_project_save.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_project_save_uses_cwd_when_no_explicit_path(monkeypatch, tmp_path, capsys): + # Default ProjectInfo.path is cwd; ensure save writes into a temp cwd, not repo root + from easydiffraction.project.project import Project + + monkeypatch.chdir(tmp_path) + p = Project() + p.save() + out = capsys.readouterr().out + # It should announce saving and create the three core files in cwd + assert 'Saving project' in out + assert (tmp_path / 'project.cif').exists() + assert (tmp_path / 'analysis.cif').exists() + assert (tmp_path / 'summary.cif').exists() + + +def test_project_save_as_writes_core_files(tmp_path, monkeypatch): + from easydiffraction.analysis.analysis import Analysis + from easydiffraction.project.project import Project + from easydiffraction.project.project_info import ProjectInfo + from easydiffraction.summary.summary import Summary + + # Monkeypatch as_cif producers to avoid heavy internals + monkeypatch.setattr(ProjectInfo, 'as_cif', lambda self: 'info') + monkeypatch.setattr(Analysis, 'as_cif', lambda self: 'analysis') + monkeypatch.setattr(Summary, 'as_cif', lambda self: 'summary') + + p = Project(name='p1') + target = tmp_path / 'proj_dir' + p.save_as(str(target)) + + # Assert expected files/dirs exist + assert (target / 'project.cif').is_file() + assert (target / 'analysis.cif').is_file() + assert (target / 'summary.cif').is_file() + assert (target / 'sample_models').is_dir() + assert (target / 'experiments').is_dir() diff --git a/tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py b/tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py new file mode 100644 index 00000000..b182fe38 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/categories/test_atom_sites.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.sample_models.categories.atom_sites import AtomSite +from easydiffraction.sample_models.categories.atom_sites import AtomSites + + +def test_atom_site_defaults_and_setters(): + a = AtomSite(label='Si1', type_symbol='Si') + a.fract_x = 0.1 + a.fract_y = 0.2 + a.fract_z = 0.3 + a.occupancy = 0.9 + a.b_iso = 1.5 + a.adp_type = 'Biso' + assert a.label.value == 'Si1' + assert a.type_symbol.value == 'Si' + assert (a.fract_x.value, a.fract_y.value, a.fract_z.value) == (0.1, 0.2, 0.3) + assert a.occupancy.value == 0.9 + assert a.b_iso.value == 1.5 + assert a.adp_type.value == 'Biso' + + +def test_atom_sites_collection_adds_by_label(): + sites = AtomSites() + a = AtomSite(label='O1', type_symbol='O') + sites.add(a) + assert 'O1' in sites.names + assert sites['O1'].type_symbol.value == 'O' diff --git a/tests/unit/easydiffraction/sample_models/categories/test_cell.py b/tests/unit/easydiffraction/sample_models/categories/test_cell.py new file mode 100644 index 00000000..aade56db --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/categories/test_cell.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +def test_cell_defaults_and_overrides(): + from easydiffraction.sample_models.categories.cell import Cell + + c = Cell() + # Defaults from AttributeSpec in implementation + assert pytest.approx(c.length_a.value) == 10.0 + assert pytest.approx(c.length_b.value) == 10.0 + assert pytest.approx(c.length_c.value) == 10.0 + assert pytest.approx(c.angle_alpha.value) == 90.0 + assert pytest.approx(c.angle_beta.value) == 90.0 + assert pytest.approx(c.angle_gamma.value) == 90.0 + + # Override through constructor + c2 = Cell(length_a=12.3, angle_beta=100.0) + assert pytest.approx(c2.length_a.value) == 12.3 + assert pytest.approx(c2.angle_beta.value) == 100.0 + + +def test_cell_setters_apply_validation_and_units(): + from easydiffraction.sample_models.categories.cell import Cell + + c = Cell() + # Set valid values within range + c.length_a = 5.5 + c.angle_gamma = 120.0 + assert pytest.approx(c.length_a.value) == 5.5 + assert pytest.approx(c.angle_gamma.value) == 120.0 + # Check units are preserved on parameter objects + assert c.length_a.units == 'Å' + assert c.angle_gamma.units == 'deg' + + +def test_cell_identity_category_code(): + from easydiffraction.sample_models.categories.cell import Cell + + c = Cell() + assert c._identity.category_code == 'cell' diff --git a/tests/unit/easydiffraction/sample_models/categories/test_space_group.py b/tests/unit/easydiffraction/sample_models/categories/test_space_group.py new file mode 100644 index 00000000..02136c14 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/categories/test_space_group.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.sample_models.categories.space_group import SpaceGroup + + +def test_space_group_name_updates_it_code(): + sg = SpaceGroup() + # default name 'P 1' should set code to the first available + default_code = sg.it_coordinate_system_code.value + sg.name_h_m = 'P 1' + assert sg.it_coordinate_system_code.value == sg._it_coordinate_system_code_allowed_values[0] + # changing name resets the code again + sg.name_h_m = 'P -1' + assert sg.it_coordinate_system_code.value == sg._it_coordinate_system_code_allowed_values[0] diff --git a/tests/unit/easydiffraction/sample_models/sample_model/test_base.py b/tests/unit/easydiffraction/sample_models/sample_model/test_base.py new file mode 100644 index 00000000..941e0af9 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/sample_model/test_base.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.sample_models.sample_model.base import SampleModelBase + + +def test_sample_model_base_str_and_properties(): + m = SampleModelBase(name='m1') + m.name = 'm2' + assert m.name == 'm2' + s = str(m) + assert 'SampleModelBase' in s or '<' in s diff --git a/tests/unit/easydiffraction/sample_models/sample_model/test_factory.py b/tests/unit/easydiffraction/sample_models/sample_model/test_factory.py new file mode 100644 index 00000000..f6d7e694 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/sample_model/test_factory.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + +from easydiffraction.sample_models.sample_model.factory import SampleModelFactory + + +def test_create_minimal_by_name(): + m = SampleModelFactory.create(name='abc') + assert m.name == 'abc' + + +def test_invalid_arg_combo_raises(): + with pytest.raises(ValueError): + SampleModelFactory.create(name=None, cif_path=None) diff --git a/tests/unit/easydiffraction/sample_models/test_sample_models.py b/tests/unit/easydiffraction/sample_models/test_sample_models.py new file mode 100644 index 00000000..21870518 --- /dev/null +++ b/tests/unit/easydiffraction/sample_models/test_sample_models.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from easydiffraction.sample_models.sample_models import SampleModels + + +def test_add_minimal_and_remove(): + models = SampleModels() + models.add_minimal('m1') + assert 'm1' in models.names + models.remove('m1') + assert 'm1' not in models diff --git a/tests/unit/easydiffraction/summary/test_summary.py b/tests/unit/easydiffraction/summary/test_summary.py new file mode 100644 index 00000000..6a25c3ff --- /dev/null +++ b/tests/unit/easydiffraction/summary/test_summary.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_summary_as_cif_returns_placeholder_string(): + from easydiffraction.summary.summary import Summary + + class P: + pass + + s = Summary(P()) + out = s.as_cif() + assert isinstance(out, str) + assert 'To be added' in out + + +def test_summary_show_report_prints_sections(capsys): + from easydiffraction.summary.summary import Summary + + class Info: + title = 'T' + description = '' + + class Project: + def __init__(self): + self.info = Info() + self.sample_models = {} # empty mapping to exercise loops safely + self.experiments = {} # empty mapping to exercise loops safely + + class A: + current_calculator = 'cryspy' + current_minimizer = 'lmfit' + + class R: + reduced_chi_square = 0.0 + + fit_results = R() + + self.analysis = A() + + s = Summary(Project()) + s.show_report() + out = capsys.readouterr().out + # Verify that all top-level sections appear (titles are uppercased by formatter) + assert 'PROJECT INFO' in out + assert 'CRYSTALLOGRAPHIC DATA' in out + assert 'EXPERIMENTS' in out + assert 'FITTING' in out + + + + + + + +def test_module_import(): + import easydiffraction.summary.summary as MUT + + expected_module_name = 'easydiffraction.summary.summary' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name diff --git a/tests/unit/easydiffraction/summary/test_summary_details.py b/tests/unit/easydiffraction/summary/test_summary_details.py new file mode 100644 index 00000000..fb32eafc --- /dev/null +++ b/tests/unit/easydiffraction/summary/test_summary_details.py @@ -0,0 +1,135 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +def test_summary_crystallographic_and_experimental_sections(capsys): + from easydiffraction.summary.summary import Summary + + # Build a minimal sample model stub that exposes required attributes + class Val: + def __init__(self, v): + self.value = v + + class CellParam: + def __init__(self, name, value): + self.name = name + self.value = value + + class Model: + def __init__(self): + self.name = 'phaseA' + self.space_group = type('SG', (), {'name_h_m': Val('P 1')})() + + class Cell: + @property + def parameters(self_inner): + return [ + CellParam('length_a', 5.4321), + CellParam('angle_alpha', 90.0), + ] + + self.cell = Cell() + + class Site: + def __init__(self, label, typ, x, y, z, occ, biso): + self.label = Val(label) + self.type_symbol = Val(typ) + self.fract_x = Val(x) + self.fract_y = Val(y) + self.fract_z = Val(z) + self.occupancy = Val(occ) + self.b_iso = Val(biso) + + self.atom_sites = [Site('Na1', 'Na', 0.1, 0.2, 0.3, 1.0, 0.5)] + + # Minimal experiment stub with instrument and peak info + class Expt: + def __init__(self): + self.name = 'exp1' + typ = type( + 'T', + (), + { + 'sample_form': Val('powder'), + 'radiation_probe': Val('neutron'), + 'beam_mode': Val('constant wavelength'), + }, + ) + self.type = typ() + + class Instr: + def __init__(self): + self.setup_wavelength = Val(1.23456) + self.calib_twotheta_offset = Val(0.12345) + + def _public_attrs(self): + return ['setup_wavelength', 'calib_twotheta_offset'] + + self.instrument = Instr() + self.peak_profile_type = 'pseudo-Voigt' + + class Peak: + def __init__(self): + self.broad_gauss_u = Val(0.1) + self.broad_gauss_v = Val(0.2) + self.broad_gauss_w = Val(0.3) + self.broad_lorentz_x = Val(0.4) + self.broad_lorentz_y = Val(0.5) + + def _public_attrs(self): + return [ + 'broad_gauss_u', + 'broad_gauss_v', + 'broad_gauss_w', + 'broad_lorentz_x', + 'broad_lorentz_y', + ] + + self.peak = Peak() + + def _public_attrs(self): + return ['instrument', 'peak_profile_type', 'peak'] + + class Info: + title = 'T' + description = '' + + class Project: + def __init__(self): + self.info = Info() + self.sample_models = {'phaseA': Model()} + self.experiments = {'exp1': Expt()} + + class A: + current_calculator = 'cryspy' + current_minimizer = 'lmfit' + + class R: + reduced_chi_square = 1.23 + + fit_results = R() + + self.analysis = A() + + s = Summary(Project()) + # Run both sections separately for targeted assertions + s.show_crystallographic_data() + s.show_experimental_data() + out = capsys.readouterr().out + + # Crystallographic section + assert 'CRYSTALLOGRAPHIC DATA' in out + assert '🧩 phaseA' in out + assert 'Space group' in out and 'P 1' in out + # Cell parameter names are shortened by the implementation (e.g., 'length_a' -> 'a') + assert 'Cell parameters' in out and ' a ' in out and ' alpha ' in out + assert 'Atom sites' in out and 'Na1' in out and 'Na' in out + + # Experimental section + assert 'EXPERIMENTS' in out + assert '🔬 exp1' in out + assert 'powder' in out and 'neutron' in out and 'constant wavelength' in out + assert 'Wavelength' in out and '1.23456'[:6] in out + assert '2θ offset' in out and '0.12345'[:6] in out + assert 'Profile type' in out and 'pseudo-Voigt' in out + assert 'Peak broadening (Gaussian)' in out + assert 'Peak broadening (Lorentzian)' in out diff --git a/tests/unit/easydiffraction/test___init__.py b/tests/unit/easydiffraction/test___init__.py new file mode 100644 index 00000000..5a0b82d4 --- /dev/null +++ b/tests/unit/easydiffraction/test___init__.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +# Focused tests for package __init__: lazy attributes and error path +import importlib + +import pytest + + +def test_lazy_attributes_resolve_and_are_accessible(): + import easydiffraction as ed + + # Access a few lazy attributes; just ensure they exist and are callable/class-like + assert hasattr(ed, 'Project') + assert hasattr(ed, 'ExperimentFactory') + assert hasattr(ed, 'SampleModelFactory') + + # Access utility functions from utils via lazy getattr + assert callable(ed.show_version) + assert callable(ed.get_value_from_xye_header) + + # Import once to exercise __getattr__; subsequent access should be cached by Python + _ = ed.Project + _ = ed.ExperimentFactory + + +def test___getattr__unknown_raises_attribute_error(): + ed = importlib.import_module('easydiffraction') + with pytest.raises(AttributeError): + ed.DefinitelyUnknownAttribute + + +def test_lazy_functions_execute_with_monkeypatch(monkeypatch, capsys, tmp_path): + import easydiffraction as ed + import easydiffraction.utils.utils as utils + + # 1) list_tutorials uses utils.fetch_tutorial_list → monkeypatch there + monkeypatch.setattr(utils, 'fetch_tutorial_list', lambda: ['a.ipynb', 'b.ipynb']) + ed.list_tutorials() # calls into utils.list_tutorials + out = capsys.readouterr().out + assert 'Tutorials available for easydiffraction' in out + + # 2) download_from_repository should call pooch.retrieve; avoid network + import easydiffraction.utils.utils as utils + + calls = {} + monkeypatch.setattr(utils.pooch, 'retrieve', lambda **kw: calls.setdefault('ok', True)) + utils.download_from_repository( + 'dummy.txt', branch='main', destination=str(tmp_path), overwrite=True + ) + assert calls.get('ok') is True diff --git a/tests/unit/easydiffraction/test___main__.py b/tests/unit/easydiffraction/test___main__.py new file mode 100644 index 00000000..6db1dc35 --- /dev/null +++ b/tests/unit/easydiffraction/test___main__.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +from typer.testing import CliRunner + +runner = CliRunner() + + +def test_module_import(): + import easydiffraction.__main__ as MUT + + expected_module_name = 'easydiffraction.__main__' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_cli_version_invokes_show_version(monkeypatch, capsys): + import easydiffraction as ed + import easydiffraction.__main__ as main_mod + + called = {'ok': False} + + def fake_show_version(): + print('VERSION_OK') + called['ok'] = True + + monkeypatch.setattr(ed, 'show_version', fake_show_version) + result = runner.invoke(main_mod.app, ['--version']) + assert result.exit_code == 0 + assert called['ok'] + assert 'VERSION_OK' in result.stdout + + +def test_cli_help_shows_and_exits_zero(): + import easydiffraction.__main__ as main_mod + + result = runner.invoke(main_mod.app, ['--help']) + assert result.exit_code == 0 + assert 'EasyDiffraction command-line interface' in result.stdout + + +def test_cli_subcommands_call_utils(monkeypatch): + import easydiffraction as ed + import easydiffraction.__main__ as main_mod + + logs = [] + monkeypatch.setattr(ed, 'list_tutorials', lambda: logs.append('LIST')) + monkeypatch.setattr(ed, 'fetch_tutorials', lambda: logs.append('FETCH')) + + res1 = runner.invoke(main_mod.app, ['list-tutorials']) + res2 = runner.invoke(main_mod.app, ['fetch-tutorials']) + + assert res1.exit_code == 0 and res2.exit_code == 0 + assert logs == ['LIST', 'FETCH'] diff --git a/tests/unit/easydiffraction/utils/test_formatting.py b/tests/unit/easydiffraction/utils/test_formatting.py new file mode 100644 index 00000000..3aebb21b --- /dev/null +++ b/tests/unit/easydiffraction/utils/test_formatting.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import re + + +def _strip_ansi(s: str) -> str: + return re.sub(r'\x1b\[[0-9;]*m', '', s) + + +def test_chapter_uppercase_and_length(): + import easydiffraction.utils.formatting as F + + title = 'Intro' + s = _strip_ansi(F.chapter(title)) + # chapter uses box drawing SYMBOL = '═' + assert '═' in s and title.upper() in s + + +def test_section_formatting_contains_markers(): + import easydiffraction.utils.formatting as F + + s = _strip_ansi(F.section('part')) + assert '*** PART ***' in s.upper() + + +def test_paragraph_preserves_quotes(): + import easydiffraction.utils.formatting as F + + s = _strip_ansi(F.paragraph("Hello 'World'")) + assert "'World'" in s + + +def test_error_warning_info_prefixes(): + import easydiffraction.utils.formatting as F + + assert 'Error' in _strip_ansi(F.error('x')) + assert 'Warning' in _strip_ansi(F.warning('x')) + assert 'Info' in _strip_ansi(F.info('x')) diff --git a/tests/unit/easydiffraction/utils/test_logging.py b/tests/unit/easydiffraction/utils/test_logging.py new file mode 100644 index 00000000..a77d9297 --- /dev/null +++ b/tests/unit/easydiffraction/utils/test_logging.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + + + +def test_module_import(): + import easydiffraction.utils.logging as MUT + + expected_module_name = 'easydiffraction.utils.logging' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_logger_configure_and_warn_reaction(): + import easydiffraction.utils.logging as MUT + + # configure to WARN so .error produces warnings and not exceptions + MUT.log.configure(reaction=MUT.log.Reaction.WARN) + MUT.log.debug('d') + MUT.log.info('i') + MUT.log.warning('w') + MUT.log.error('e') + # switch mode/level + MUT.log.set_level(MUT.log.Level.INFO) + MUT.log.set_mode(MUT.log.Mode.VERBOSE) + # nothing to assert; absence of exception is success + assert True diff --git a/tests/unit/easydiffraction/utils/test_utils.py b/tests/unit/easydiffraction/utils/test_utils.py new file mode 100644 index 00000000..9b209f64 --- /dev/null +++ b/tests/unit/easydiffraction/utils/test_utils.py @@ -0,0 +1,198 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +import numpy as np +import pytest + + + +def test_module_import(): + import easydiffraction.utils.utils as MUT + + expected_module_name = 'easydiffraction.utils.utils' + actual_module_name = MUT.__name__ + assert expected_module_name == actual_module_name + + +def test_twotheta_to_d_scalar_and_array(): + import easydiffraction.utils.utils as MUT + + wavelength = 1.54 + # scalar + expected_scalar = wavelength / (2 * np.sin(np.radians(30 / 2))) + actual_scalar = MUT.twotheta_to_d(30.0, wavelength) + assert np.isclose(expected_scalar, actual_scalar) + # array + twotheta = np.array([10.0, 20.0, 40.0]) + expected_array = wavelength / (2 * np.sin(np.radians(twotheta / 2))) + actual_array = MUT.twotheta_to_d(twotheta, wavelength) + assert np.allclose(expected_array, actual_array) + + +def test_tof_to_d_linear_case(): + import easydiffraction.utils.utils as MUT + + tof = np.array([10.0, 20.0, 30.0]) + offset, linear, quad = 2.0, 4.0, 0.0 + expected = (tof - offset) / linear + actual = MUT.tof_to_d(tof, offset, linear, quad) + assert np.allclose(expected, actual) + + +def test_tof_to_d_quadratic_case_smallest_positive_root(): + import easydiffraction.utils.utils as MUT + + # Model: TOF = quad * d^2, with offset=linear=0 + quad = 2.0 + tof = np.array([2.0, 8.0, 18.0]) # roots: sqrt(tof/quad) + expected = np.sqrt(tof / quad) + actual = MUT.tof_to_d(tof, offset=0.0, linear=0.0, quad=quad) + assert np.allclose(expected, actual, equal_nan=False) + + +def test_str_to_ufloat_parsing_nominal_and_esd(): + import easydiffraction.utils.utils as MUT + + u = MUT.str_to_ufloat('3.566(2)') + expected = np.array([3.566, 0.002]) + actual = np.array([u.nominal_value, u.std_dev]) + assert np.allclose(expected, actual) + + +def test_str_to_ufloat_no_esd_defaults_nan(): + import easydiffraction.utils.utils as MUT + + u = MUT.str_to_ufloat('1.23') + expected_value = 1.23 + actual_value = u.nominal_value + # uncertainty is NaN when not specified + assert np.isclose(expected_value, actual_value) and np.isnan(u.std_dev) + + +def test_get_value_from_xye_header(tmp_path): + import easydiffraction.utils.utils as MUT + + text = 'DIFC = 123.45 two_theta = 67.89\nrest of file\n' + p = tmp_path / 'file.xye' + p.write_text(text) + expected_difc = 123.45 + expected_two_theta = 67.89 + actual = np.array([ + MUT.get_value_from_xye_header(p, 'DIFC'), + MUT.get_value_from_xye_header(p, 'two_theta'), + ]) + expected = np.array([expected_difc, expected_two_theta]) + assert np.allclose(expected, actual) + + +def test_validate_url_rejects_non_http_https(): + import easydiffraction.utils.utils as MUT + + with pytest.raises(ValueError): + MUT._validate_url('ftp://example.com/file') + + +def test_is_github_ci_env_true(monkeypatch): + import easydiffraction.utils.utils as MUT + + monkeypatch.setenv('GITHUB_ACTIONS', 'true') + expected = True + actual = MUT.is_github_ci() + assert expected == actual + + +def test_package_version_missing_package_returns_none(): + import easydiffraction.utils.utils as MUT + + expected = None + actual = MUT.package_version('__definitely_not_installed__') + assert expected == actual + + +def test_is_notebook_false_in_plain_env(monkeypatch): + import easydiffraction.utils.utils as MUT + + # Ensure no IPython and not PyCharm + monkeypatch.setattr(MUT, 'IPython', None) + monkeypatch.setenv('PYCHARM_HOSTED', '', prepend=False) + assert MUT.is_notebook() is False + + +def test_is_pycharm_and_is_colab(monkeypatch): + import easydiffraction.utils.utils as MUT + + # PyCharm + monkeypatch.setenv('PYCHARM_HOSTED', '1') + assert MUT.is_pycharm() is True + # Colab detection when module is absent -> False + assert MUT.is_colab() is False + + +def test_render_table_terminal_branch(capsys, monkeypatch): + import easydiffraction.utils.utils as MUT + + monkeypatch.setattr(MUT, 'is_notebook', lambda: False) + MUT.render_table(columns_data=[[1, 2], [3, 4]], columns_alignment=['left', 'left']) + out = capsys.readouterr().out + # fancy_outline uses box-drawing characters; accept a couple of expected ones + assert ('╒' in out and '╕' in out) or ('┌' in out and '┐' in out) + + +def test_fetch_tutorial_list_handles_missing_release(monkeypatch): + import easydiffraction.utils.utils as MUT + + # Force _get_release_info to return None twice (tag & latest) → empty list + monkeypatch.setattr(MUT, '_get_release_info', lambda tag: None) + notebooks = MUT.fetch_tutorial_list() + assert notebooks == [] + + +def test_fetch_tutorial_list_no_asset(monkeypatch): + import easydiffraction.utils.utils as MUT + + release_info = {'assets': []} + monkeypatch.setattr(MUT, '_get_release_info', lambda tag: release_info) + notebooks = MUT.fetch_tutorial_list() + assert notebooks == [] + + +def test_show_version_prints(capsys, monkeypatch): + import easydiffraction.utils.utils as MUT + + monkeypatch.setattr(MUT, 'package_version', lambda name: '1.2.3+abc') + MUT.show_version() + out = capsys.readouterr().out + assert '1.2.3+abc' in out + + +def test_extract_notebooks_from_asset_with_inmemory_zip(monkeypatch): + import io + import zipfile + + import easydiffraction.utils.utils as MUT + + # Build an in-memory zip with .ipynb files + mem = io.BytesIO() + with zipfile.ZipFile(mem, 'w') as zf: + zf.writestr('dir/a.ipynb', '{}') + zf.writestr('b.ipynb', '{}') + zf.writestr('c.txt', 'x') + data = mem.getvalue() + + class DummyResp: + def __init__(self, b): + self._b = b + + def read(self): + return self._b + + def __enter__(self): + return self + + def __exit__(self, *args): + return False + + monkeypatch.setattr(MUT, '_safe_urlopen', lambda url: DummyResp(data)) + out = MUT._extract_notebooks_from_asset('https://example.com/tut.zip') + # returns sorted basenames of .ipynb + assert out == ['a.ipynb', 'b.ipynb'] diff --git a/tests/unit/experiments/collections/test_background.py b/tests/unit/experiments/collections/test_background.py deleted file mode 100644 index 0e6fc527..00000000 --- a/tests/unit/experiments/collections/test_background.py +++ /dev/null @@ -1,106 +0,0 @@ -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.experiments.collections.background import BackgroundFactory -from easydiffraction.experiments.collections.background import ChebyshevPolynomialBackground -from easydiffraction.experiments.collections.background import LineSegmentBackground -from easydiffraction.experiments.collections.background import Point -from easydiffraction.experiments.collections.background import PolynomialTerm - - -def test_point_initialization(): - point = Point(x=1.0, y=2.0) - assert point.x.value == 1.0 - assert point.y.value == 2.0 - assert point.cif_category_key == 'pd_background' - assert point.category_key == 'background' - assert point._entry_id == '1.0' - - -def test_polynomial_term_initialization(): - term = PolynomialTerm(order=2, coef=3.0) - assert term.order.value == 2 - assert term.coef.value == 3.0 - assert term.cif_category_key == 'pd_background' - assert term.category_key == 'background' - assert term._entry_id == '2' - - -def test_line_segment_background_add_and_calculate(): - background = LineSegmentBackground() - background.add(1.0, 2.0) - background.add(3.0, 4.0) - - x_data = np.array([1.0, 2.0, 3.0]) - y_data = background.calculate(x_data) - - assert np.array_equal(y_data, np.array([2.0, 3.0, 4.0])) - - -def test_line_segment_background_calculate_no_points(): - background = LineSegmentBackground() - x_data = np.array([1.0, 2.0, 3.0]) - - with patch('builtins.print') as mock_print: - y_data = background.calculate(x_data) - assert np.array_equal(y_data, np.zeros_like(x_data)) - assert 'No background points found. Setting background to zero.' in str(mock_print.call_args.args[0]) - - -def test_line_segment_background_show(capsys): - background = LineSegmentBackground() - background.add(1.0, 2.0) - background.add(3.0, 4.0) - - background.show() - captured = capsys.readouterr() - assert 'Line-segment background points' in captured.out - - -def test_chebyshev_polynomial_background_add_and_calculate(): - background = ChebyshevPolynomialBackground() - background.add(order=0, coef=1.0) - background.add(order=1, coef=2.0) - - x_data = np.array([0.0, 0.5, 1.0]) - y_data = background.calculate(x_data) - - # Expected values are calculated using the Chebyshev polynomial formula - u = (x_data - x_data.min()) / (x_data.max() - x_data.min()) * 2 - 1 - expected_y = 1.0 + 2.0 * u - assert np.allclose(y_data, expected_y) - - -def test_chebyshev_polynomial_background_calculate_no_terms(): - background = ChebyshevPolynomialBackground() - x_data = np.array([0.0, 0.5, 1.0]) - - with patch('builtins.print') as mock_print: - y_data = background.calculate(x_data) - assert np.array_equal(y_data, np.zeros_like(x_data)) - assert 'No background points found. Setting background to zero.' in str(mock_print.call_args.args[0]) - - -def test_chebyshev_polynomial_background_show(capsys): - background = ChebyshevPolynomialBackground() - background.add(order=0, coef=1.0) - background.add(order=1, coef=2.0) - - background.show() - captured = capsys.readouterr() - assert 'Chebyshev polynomial background terms' in captured.out - - -def test_background_factory_create_supported_types(): - line_segment_background = BackgroundFactory.create('line-segment') - assert isinstance(line_segment_background, LineSegmentBackground) - - chebyshev_background = BackgroundFactory.create('chebyshev polynomial') - assert isinstance(chebyshev_background, ChebyshevPolynomialBackground) - - -def test_background_factory_create_unsupported_type(): - with pytest.raises(ValueError, match="Unsupported background type: 'unsupported'.*"): - BackgroundFactory.create('unsupported') diff --git a/tests/unit/experiments/collections/test_datastore.py b/tests/unit/experiments/collections/test_datastore.py deleted file mode 100644 index ab1b870a..00000000 --- a/tests/unit/experiments/collections/test_datastore.py +++ /dev/null @@ -1,181 +0,0 @@ -import numpy as np -import pytest - -from easydiffraction.experiments.datastore import DatastoreFactory -from easydiffraction.experiments.datastore import PowderDatastore -from easydiffraction.experiments.datastore import SingleCrystalDatastore - - -def test_powder_datastore_init(): - ds = PowderDatastore(beam_mode='constant wavelength') - assert ds.meas is None - assert ds.meas_su is None - assert ds.calc is None - - assert ds.beam_mode == 'constant wavelength' - assert ds.x is None - assert ds.d is None - assert ds.bkg is None - - -def test_powder_datastore_calc(): - ds = PowderDatastore() - with pytest.raises(TypeError): - ds.calc = [1, 2, 3] # Should raise TypeError because list is not allowed - arr = np.array([1, 2, 3]) - ds.calc = arr - assert np.array_equal(ds.calc, arr) - - -def test_single_crystal_datastore_init(): - ds = SingleCrystalDatastore() - assert ds.meas is None - assert ds.meas_su is None - assert ds.calc is None - - assert ds.sin_theta_over_lambda is None - assert ds.index_h is None - assert ds.index_k is None - assert ds.index_l is None - - -def test_datastore_factory_create_powder(): - ds = DatastoreFactory.create(sample_form='powder') - assert isinstance(ds, PowderDatastore) - - -def test_datastore_factory_create_single_crystal(): - ds = DatastoreFactory.create(sample_form='single crystal') - assert isinstance(ds, SingleCrystalDatastore) - - -def test_datastore_factory_create_powder_time_of_flight(): - ds = DatastoreFactory.create(sample_form='powder', beam_mode='time-of-flight') - assert isinstance(ds, PowderDatastore) - assert ds.beam_mode == 'time-of-flight' - - -def test_datastore_factory_create_powder_constant_wavelength(): - ds = DatastoreFactory.create(sample_form='powder', beam_mode='constant wavelength') - assert isinstance(ds, PowderDatastore) - assert ds.beam_mode == 'constant wavelength' - - -def test_datastore_factory_create_invalid_sample_form(): - with pytest.raises(ValueError, match="Unsupported sample form: 'invalid'"): - DatastoreFactory.create(sample_form='invalid') - - -def test_datastore_factory_create_invalid_beam_mode(): - with pytest.raises(ValueError, match="Unsupported beam mode: 'invalid'"): - DatastoreFactory.create(beam_mode='invalid') - - -def test_datastore_factory_cif_mapping_powder_time_of_flight(): - ds = DatastoreFactory.create( - sample_form='powder', - beam_mode='time-of-flight', - ) - desired = { - 'x': '_pd_meas.time_of_flight', - 'meas': '_pd_meas.intensity_total', - 'meas_su': '_pd_meas.intensity_total_su', - } - actual = ds._cif_mapping() - assert actual == desired - - -def test_datastore_factory_cif_mapping_powder_constant_wavelength(): - ds = DatastoreFactory.create( - sample_form='powder', - beam_mode='constant wavelength', - ) - desired = { - 'x': '_pd_meas.2theta_scan', - 'meas': '_pd_meas.intensity_total', - 'meas_su': '_pd_meas.intensity_total_su', - } - actual = ds._cif_mapping() - assert actual == desired - - -def test_datastore_factory_cif_mapping_single_crystal(): - ds = DatastoreFactory.create( - sample_form='single crystal', - beam_mode='constant wavelength', - ) - desired = { - 'index_h': '_refln.index_h', - 'index_k': '_refln.index_k', - 'index_l': '_refln.index_l', - 'meas': '_refln.intensity_meas', - 'meas_su': '_refln.intensity_meas_su', - } - actual = ds._cif_mapping() - assert actual == desired - - -def test_powder_as_cif_constant_wavelength(): - ds = PowderDatastore(beam_mode='constant wavelength') - ds.x = np.array([1.0, 2.0, 3.0]) - ds.meas = np.array([10.0, 20.0, 30.0]) - ds.meas_su = np.array([0.1, 0.2, 0.3]) - ds.bkg = np.array([0.5, 0.5, 0.5]) - cif = ds.as_cif() - assert '_pd_meas.2theta_scan' in cif - assert '_pd_meas.intensity_total' in cif - assert '_pd_meas.intensity_total_su' in cif - - -def test_powder_as_cif_time_of_flight(): - ds = PowderDatastore(beam_mode='time-of-flight') - ds.x = np.array([0.5, 1.0, 1.5]) - ds.meas = np.array([15.0, 25.0, 35.0]) - ds.meas_su = np.array([0.15, 0.25, 0.35]) - ds.bkg = np.array([0.4, 0.4, 0.4]) - cif = ds.as_cif() - assert '_pd_meas.time_of_flight' in cif - assert '_pd_meas.intensity_total' in cif - assert '_pd_meas.intensity_total_su' in cif - - -def test_single_crystal_as_cif(): - ds = SingleCrystalDatastore() - ds.sin_theta_over_lambda = np.array([0.1, 0.2]) - ds.index_h = np.array([1, 0]) - ds.index_k = np.array([0, 1]) - ds.index_l = np.array([0, 0]) - ds.meas = np.array([100, 200]) - ds.meas_su = np.array([10, 20]) - cif = ds.as_cif() - assert '_refln.index_h' in cif - assert '_refln.index_k' in cif - assert '_refln.index_l' in cif - assert '_refln.intensity_meas' in cif - assert '_refln.intensity_meas_su' in cif - - -def test_as_cif_truncation(): - ds = PowderDatastore() - ds.x = np.arange(10) - ds.meas = np.arange(10) * 10 - ds.meas_su = np.arange(10) * 0.1 - ds.bkg = np.arange(10) * 0.5 - - cif = ds.as_cif(max_points=2) - - # It should contain CIF headers - assert '_pd_meas.2theta_scan' in cif - assert '_pd_meas.intensity_total' in cif - assert '_pd_meas.intensity_total_su' in cif - - # It should contain first 2 and last 2 rows, but not the middle - assert '0 0 0.0' in cif - assert '1 10 0.1' in cif - assert '3 20 0.2' not in cif - assert '7 70 0.7' not in cif - assert '8 80 0.8' in cif - assert '9 90 0.9' in cif - - # Ellipsis should indicate truncation - assert '...' in cif diff --git a/tests/unit/experiments/collections/test_linked_phases.py b/tests/unit/experiments/collections/test_linked_phases.py deleted file mode 100644 index 367e083f..00000000 --- a/tests/unit/experiments/collections/test_linked_phases.py +++ /dev/null @@ -1,46 +0,0 @@ -from easydiffraction.experiments.collections.linked_phases import LinkedPhase -from easydiffraction.experiments.collections.linked_phases import LinkedPhases - - -def test_linked_phase_category_key(): - lp = LinkedPhase(id='phase1', scale=1.0) - assert lp.category_key == 'linked_phases' - - -def test_linked_phase_cif_category_key(): - lp = LinkedPhase(id='phase1', scale=1.0) - assert lp.cif_category_key == 'pd_phase_block' - - -def test_linked_phase_init(): - lp = LinkedPhase(id='phase1', scale=1.23) - assert lp.id.value == 'phase1' - assert lp.id.name == 'id' - assert lp.id.cif_name == 'id' - assert lp.scale.value == 1.23 - assert lp.scale.name == 'scale' - assert lp.scale.cif_name == 'scale' - - -def test_linked_phases_type(): - lps = LinkedPhases() - assert lps._type == 'category' - - -def test_linked_phases_child_class(): - lps = LinkedPhases() - assert lps._child_class is LinkedPhase - - -def test_linked_phases_init_empty(): - lps = LinkedPhases() - assert len(lps._items) == 0 - - -def test_linked_phases_add(): - lps = LinkedPhases() - lps.add(id='phase1', scale=1.0) - lps.add(id='phase2', scale=2.0) - assert len(lps._items) == 2 - assert lps['phase1'].scale.value == 1.0 - assert lps['phase2'].scale.value == 2.0 diff --git a/tests/unit/experiments/components/test_experiment_type.py b/tests/unit/experiments/components/test_experiment_type.py deleted file mode 100644 index 68fac38f..00000000 --- a/tests/unit/experiments/components/test_experiment_type.py +++ /dev/null @@ -1,54 +0,0 @@ -from easydiffraction.core.objects import Descriptor -from easydiffraction.experiments.components.experiment_type import ExperimentType - - -def test_experiment_type_initialization(): - experiment_type = ExperimentType( - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='neutron', - scattering_type='bragg', - ) - - assert isinstance(experiment_type.sample_form, Descriptor) - assert experiment_type.sample_form.value == 'powder' - assert experiment_type.sample_form.name == 'sample_form' - assert experiment_type.sample_form.cif_name == 'sample_form' - - assert isinstance(experiment_type.beam_mode, Descriptor) - assert experiment_type.beam_mode.value == 'constant wavelength' - assert experiment_type.beam_mode.name == 'beam_mode' - assert experiment_type.beam_mode.cif_name == 'beam_mode' - - assert isinstance(experiment_type.radiation_probe, Descriptor) - assert experiment_type.radiation_probe.value == 'neutron' - assert experiment_type.radiation_probe.name == 'radiation_probe' - assert experiment_type.radiation_probe.cif_name == 'radiation_probe' - - -def test_experiment_type_properties(): - experiment_type = ExperimentType( - sample_form='single crystal', - beam_mode='time-of-flight', - radiation_probe='xray', - scattering_type='bragg', - ) - - assert experiment_type.category_key == 'expt_type' - assert experiment_type.cif_category_key == 'expt_type' - assert experiment_type.datablock_id is None - assert experiment_type.entry_id is None - assert experiment_type._locked is True - - -def no_test_experiment_type_locking_attributes(): - # TODO: hmm this doesn't work as expected. - experiment_type = ExperimentType( - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='neutron', - scattering_type='bragg', - ) - experiment_type._locked = True # Disallow adding new attributes - experiment_type.new_attribute = 'value' - assert not hasattr(experiment_type, 'new_attribute') diff --git a/tests/unit/experiments/components/test_instrument.py b/tests/unit/experiments/components/test_instrument.py deleted file mode 100644 index 084dc41e..00000000 --- a/tests/unit/experiments/components/test_instrument.py +++ /dev/null @@ -1,85 +0,0 @@ -import pytest - -from easydiffraction.core.objects import Parameter -from easydiffraction.experiments.components.instrument import ConstantWavelengthInstrument -from easydiffraction.experiments.components.instrument import InstrumentBase -from easydiffraction.experiments.components.instrument import InstrumentFactory -from easydiffraction.experiments.components.instrument import TimeOfFlightInstrument - - -def test_instrument_base_properties(): - instrument = InstrumentBase() - assert instrument.category_key == 'instrument' - assert instrument.cif_category_key == 'instr' - assert instrument._entry_id is None - - -def test_constant_wavelength_instrument_initialization(): - instrument = ConstantWavelengthInstrument(setup_wavelength=1.5406, calib_twotheta_offset=0.1) - - assert isinstance(instrument.setup_wavelength, Parameter) - assert instrument.setup_wavelength.value == 1.5406 - assert instrument.setup_wavelength.name == 'wavelength' - assert instrument.setup_wavelength.cif_name == 'wavelength' - assert instrument.setup_wavelength.units == 'Å' - - assert isinstance(instrument.calib_twotheta_offset, Parameter) - assert instrument.calib_twotheta_offset.value == 0.1 - assert instrument.calib_twotheta_offset.name == 'twotheta_offset' - assert instrument.calib_twotheta_offset.cif_name == '2theta_offset' - assert instrument.calib_twotheta_offset.units == 'deg' - - -def test_time_of_flight_instrument_initialization(): - instrument = TimeOfFlightInstrument( - setup_twotheta_bank=150.0, - calib_d_to_tof_offset=0.5, - calib_d_to_tof_linear=10000.0, - calib_d_to_tof_quad=-1.0, - calib_d_to_tof_recip=0.1, - ) - - assert isinstance(instrument.setup_twotheta_bank, Parameter) - assert instrument.setup_twotheta_bank.value == 150.0 - assert instrument.setup_twotheta_bank.name == 'twotheta_bank' - assert instrument.setup_twotheta_bank.cif_name == '2theta_bank' - assert instrument.setup_twotheta_bank.units == 'deg' - - assert isinstance(instrument.calib_d_to_tof_offset, Parameter) - assert instrument.calib_d_to_tof_offset.value == 0.5 - assert instrument.calib_d_to_tof_offset.name == 'd_to_tof_offset' - assert instrument.calib_d_to_tof_offset.cif_name == 'd_to_tof_offset' - assert instrument.calib_d_to_tof_offset.units == 'µs' - - assert isinstance(instrument.calib_d_to_tof_linear, Parameter) - assert instrument.calib_d_to_tof_linear.value == 10000.0 - assert instrument.calib_d_to_tof_linear.name == 'd_to_tof_linear' - assert instrument.calib_d_to_tof_linear.cif_name == 'd_to_tof_linear' - assert instrument.calib_d_to_tof_linear.units == 'µs/Å' - - assert isinstance(instrument.calib_d_to_tof_quad, Parameter) - assert instrument.calib_d_to_tof_quad.value == -1.0 - assert instrument.calib_d_to_tof_quad.name == 'd_to_tof_quad' - assert instrument.calib_d_to_tof_quad.cif_name == 'd_to_tof_quad' - assert instrument.calib_d_to_tof_quad.units == 'µs/Ų' - - assert isinstance(instrument.calib_d_to_tof_recip, Parameter) - assert instrument.calib_d_to_tof_recip.value == 0.1 - assert instrument.calib_d_to_tof_recip.name == 'd_to_tof_recip' - assert instrument.calib_d_to_tof_recip.cif_name == 'd_to_tof_recip' - assert instrument.calib_d_to_tof_recip.units == 'µs·Å' - - -def test_instrument_factory_create_constant_wavelength(): - instrument = InstrumentFactory.create(beam_mode='constant wavelength') - assert isinstance(instrument, ConstantWavelengthInstrument) - - -def test_instrument_factory_create_time_of_flight(): - instrument = InstrumentFactory.create(beam_mode='time-of-flight') - assert isinstance(instrument, TimeOfFlightInstrument) - - -def test_instrument_factory_create_invalid_beam_mode(): - with pytest.raises(ValueError, match="Unsupported beam mode: 'invalid'.*"): - InstrumentFactory.create(beam_mode='invalid') diff --git a/tests/unit/experiments/components/test_peak.py b/tests/unit/experiments/components/test_peak.py deleted file mode 100644 index 1de1c15d..00000000 --- a/tests/unit/experiments/components/test_peak.py +++ /dev/null @@ -1,145 +0,0 @@ -import pytest - -from easydiffraction.core.objects import Parameter -from easydiffraction.experiments.components.peak import ConstantWavelengthBroadeningMixin -from easydiffraction.experiments.components.peak import ConstantWavelengthPseudoVoigt -from easydiffraction.experiments.components.peak import ConstantWavelengthSplitPseudoVoigt -from easydiffraction.experiments.components.peak import ConstantWavelengthThompsonCoxHastings -from easydiffraction.experiments.components.peak import EmpiricalAsymmetryMixin -from easydiffraction.experiments.components.peak import FcjAsymmetryMixin -from easydiffraction.experiments.components.peak import IkedaCarpenterAsymmetryMixin -from easydiffraction.experiments.components.peak import PeakBase -from easydiffraction.experiments.components.peak import PeakFactory -from easydiffraction.experiments.components.peak import TimeOfFlightBroadeningMixin -from easydiffraction.experiments.components.peak import TimeOfFlightPseudoVoigt -from easydiffraction.experiments.components.peak import TimeOfFlightPseudoVoigtBackToBack -from easydiffraction.experiments.components.peak import TimeOfFlightPseudoVoigtIkedaCarpenter - - -# --- Tests for Mixins --- -def test_constant_wavelength_broadening_mixin(): - class TestClass(ConstantWavelengthBroadeningMixin): - def __init__(self): - self._add_constant_wavelength_broadening() - - obj = TestClass() - assert isinstance(obj.broad_gauss_u, Parameter) - assert obj.broad_gauss_u.value == 0.01 - assert obj.broad_gauss_v.value == -0.01 - assert obj.broad_gauss_w.value == 0.02 - assert obj.broad_lorentz_x.value == 0.0 - assert obj.broad_lorentz_y.value == 0.0 - - -def test_time_of_flight_broadening_mixin(): - class TestClass(TimeOfFlightBroadeningMixin): - def __init__(self): - self._add_time_of_flight_broadening() - - obj = TestClass() - assert isinstance(obj.broad_gauss_sigma_0, Parameter) - assert obj.broad_gauss_sigma_0.value == 0.0 - assert obj.broad_gauss_sigma_1.value == 0.0 - assert obj.broad_gauss_sigma_2.value == 0.0 - assert obj.broad_lorentz_gamma_0.value == 0.0 - assert obj.broad_lorentz_gamma_1.value == 0.0 - assert obj.broad_lorentz_gamma_2.value == 0.0 - assert obj.broad_mix_beta_0.value == 0.0 - assert obj.broad_mix_beta_1.value == 0.0 - - -def test_empirical_asymmetry_mixin(): - class TestClass(EmpiricalAsymmetryMixin): - def __init__(self): - self._add_empirical_asymmetry() - - obj = TestClass() - assert isinstance(obj.asym_empir_1, Parameter) - assert obj.asym_empir_1.value == 0.1 - assert obj.asym_empir_2.value == 0.2 - assert obj.asym_empir_3.value == 0.3 - assert obj.asym_empir_4.value == 0.4 - - -def test_fcj_asymmetry_mixin(): - class TestClass(FcjAsymmetryMixin): - def __init__(self): - self._add_fcj_asymmetry() - - obj = TestClass() - assert isinstance(obj.asym_fcj_1, Parameter) - assert obj.asym_fcj_1.value == 0.01 - assert obj.asym_fcj_2.value == 0.02 - - -def test_ikeda_carpenter_asymmetry_mixin(): - class TestClass(IkedaCarpenterAsymmetryMixin): - def __init__(self): - self._add_ikeda_carpenter_asymmetry() - - obj = TestClass() - assert isinstance(obj.asym_alpha_0, Parameter) - assert obj.asym_alpha_0.value == 0.01 - assert obj.asym_alpha_1.value == 0.02 - - -# --- Tests for Base and Derived Peak Classes --- -def test_peak_base_properties(): - peak = PeakBase() - assert peak.cif_category_key == 'peak' - assert peak.category_key == 'peak' - assert peak._entry_id is None - - -def test_constant_wavelength_pseudo_voigt_initialization(): - peak = ConstantWavelengthPseudoVoigt() - assert isinstance(peak.broad_gauss_u, Parameter) - assert peak.broad_gauss_u.value == 0.01 - - -def test_constant_wavelength_split_pseudo_voigt_initialization(): - peak = ConstantWavelengthSplitPseudoVoigt() - assert isinstance(peak.broad_gauss_u, Parameter) - assert isinstance(peak.asym_empir_1, Parameter) - assert peak.asym_empir_1.value == 0.1 - - -def test_constant_wavelength_thompson_cox_hastings_initialization(): - peak = ConstantWavelengthThompsonCoxHastings() - assert isinstance(peak.broad_gauss_u, Parameter) - assert isinstance(peak.asym_fcj_1, Parameter) - assert peak.asym_fcj_1.value == 0.01 - - -def test_time_of_flight_pseudo_voigt_initialization(): - peak = TimeOfFlightPseudoVoigt() - assert isinstance(peak.broad_gauss_sigma_0, Parameter) - assert peak.broad_gauss_sigma_0.value == 0.0 - - -def test_time_of_flight_pseudo_voigt_ikeda_carpenter_initialization(): - peak = TimeOfFlightPseudoVoigtIkedaCarpenter() - assert isinstance(peak.broad_gauss_sigma_0, Parameter) - assert isinstance(peak.asym_alpha_0, Parameter) - - -def test_time_of_flight_pseudo_voigt_back_to_back_exponential_initialization(): - peak = TimeOfFlightPseudoVoigtBackToBack() - assert isinstance(peak.broad_gauss_sigma_0, Parameter) - assert isinstance(peak.asym_alpha_0, Parameter) - - -# --- Tests for PeakFactory --- -def test_peak_factory_create_constant_wavelength_pseudo_voigt(): - peak = PeakFactory.create(beam_mode='constant wavelength', profile_type='pseudo-voigt') - assert isinstance(peak, ConstantWavelengthPseudoVoigt) - - -def test_peak_factory_create_invalid_beam_mode(): - with pytest.raises(ValueError, match="Unsupported beam mode: 'invalid'.*"): - PeakFactory.create(beam_mode='invalid', profile_type='pseudo-voigt') - - -def test_peak_factory_create_invalid_profile_type(): - with pytest.raises(ValueError, match="Unsupported profile type 'invalid' for beam mode 'constant wavelength'.*"): - PeakFactory.create(beam_mode='constant wavelength', profile_type='invalid') diff --git a/tests/unit/experiments/test_experiment.py b/tests/unit/experiments/test_experiment.py deleted file mode 100644 index 8345a92d..00000000 --- a/tests/unit/experiments/test_experiment.py +++ /dev/null @@ -1,154 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import numpy as np -import pytest - -from easydiffraction.experiments.components.experiment_type import BeamModeEnum -from easydiffraction.experiments.components.experiment_type import ExperimentType -from easydiffraction.experiments.components.experiment_type import RadiationProbeEnum -from easydiffraction.experiments.components.experiment_type import SampleFormEnum -from easydiffraction.experiments.components.experiment_type import ScatteringTypeEnum -from easydiffraction.experiments.experiment import BaseExperiment -from easydiffraction.experiments.experiment import Experiment -from easydiffraction.experiments.experiment import ExperimentFactory -from easydiffraction.experiments.experiment import PowderExperiment -from easydiffraction.experiments.experiment import SingleCrystalExperiment - - -@pytest.fixture -def expt_type(): - return ExperimentType( - sample_form=SampleFormEnum.default(), - beam_mode=BeamModeEnum.default(), - radiation_probe='xray', - scattering_type='bragg', - ) - - -class ConcreteBaseExperiment(BaseExperiment): - """Concrete implementation of BaseExperiment for testing.""" - - def _load_ascii_data_to_experiment(self, data_path): - pass - - def show_meas_chart(self, x_min=None, x_max=None): - pass - - -class ConcreteSingleCrystalExperiment(SingleCrystalExperiment): - """Concrete implementation of SingleCrystalExperiment for - testing.""" - - def _load_ascii_data_to_experiment(self, data_path): - pass - - -def test_base_experiment_initialization(expt_type): - experiment = ConcreteBaseExperiment(name='TestExperiment', type=expt_type) - assert experiment.name == 'TestExperiment' - assert experiment.type == expt_type - - -def test_powder_experiment_initialization(expt_type): - experiment = PowderExperiment(name='PowderTest', type=expt_type) - assert experiment.name == 'PowderTest' - assert experiment.type == expt_type - assert experiment.background is not None - assert experiment.peak is not None - assert experiment.linked_phases is not None - - -def test_powder_experiment_load_ascii_data(expt_type): - experiment = PowderExperiment(name='PowderTest', type=expt_type) - experiment.datastore = MagicMock() - mock_data = np.array([[1.0, 2.0, 0.1], [2.0, 3.0, 0.2]]) - with patch('numpy.loadtxt', return_value=mock_data): - experiment._load_ascii_data_to_experiment('mock_path') - assert np.array_equal(experiment.datastore.x, mock_data[:, 0]) - assert np.array_equal(experiment.datastore.meas, mock_data[:, 1]) - assert np.array_equal(experiment.datastore.meas_su, mock_data[:, 2]) - - -def test_single_crystal_experiment_initialization(expt_type): - experiment = ConcreteSingleCrystalExperiment(name='SingleCrystalTest', type=expt_type) - assert experiment.name == 'SingleCrystalTest' - assert experiment.type == expt_type - assert experiment.linked_crystal is None - - -def test_single_crystal_experiment_show_meas_chart(expt_type): - experiment = ConcreteSingleCrystalExperiment(name='SingleCrystalTest', type=expt_type) - with patch('builtins.print') as mock_print: - experiment.show_meas_chart() - mock_print.assert_called_once_with('Showing measured data chart is not implemented yet.') - - -def test_experiment_factory_create_powder(): - experiment = ExperimentFactory.create( - name='PowderTest', - sample_form=SampleFormEnum.POWDER.value, - beam_mode=BeamModeEnum.default().value, - radiation_probe=RadiationProbeEnum.default().value, - scattering_type=ScatteringTypeEnum.default().value, - ) - assert isinstance(experiment, PowderExperiment) - assert experiment.name == 'PowderTest' - - -# to be added once single crystal works -def no_test_experiment_factory_create_single_crystal(): - experiment = ExperimentFactory.create( - name='SingleCrystalTest', - sample_form=SampleFormEnum.SINGLE_CRYSTAL.value, - beam_mode=BeamModeEnum.default().value, - radiation_probe=RadiationProbeEnum.default().value, - ) - assert isinstance(experiment, SingleCrystalExperiment) - assert experiment.name == 'SingleCrystalTest' - - -def test_experiment_method(): - mock_data = np.array([[1.0, 2.0, 0.1], [2.0, 3.0, 0.2]]) - with patch('numpy.loadtxt', return_value=mock_data): - experiment = Experiment( - name='ExperimentTest', - sample_form='powder', - beam_mode=BeamModeEnum.default().value, - radiation_probe=RadiationProbeEnum.default().value, - data_path='mock_path', - ) - assert isinstance(experiment, PowderExperiment) - assert experiment.name == 'ExperimentTest' - assert np.array_equal(experiment.datastore.x, mock_data[:, 0]) - assert np.array_equal(experiment.datastore.meas, mock_data[:, 1]) - assert np.array_equal(experiment.datastore.meas_su, mock_data[:, 2]) - - -def test_experiment_factory_invalid_args_missing_required(): - # Missing required 'name' - with pytest.raises(ValueError, match='Invalid argument combination'): - ExperimentFactory.create( - sample_form=SampleFormEnum.POWDER.value, - beam_mode=BeamModeEnum.default().value, - radiation_probe=RadiationProbeEnum.default().value, - scattering_type=ScatteringTypeEnum.default().value, - ) - - -def test_experiment_factory_conflicting_args_cif_and_name(): - # Conflicting: 'cif_path' with 'name' - with pytest.raises(ValueError, match='Invalid argument combination'): - ExperimentFactory.create(name='ConflictTest', cif_path='path/to/file.cif') - - -def test_experiment_factory_conflicting_args_data_and_cif(): - # Conflicting: multiple conflicting input sources - with pytest.raises(ValueError, match='Invalid argument combination'): - ExperimentFactory.create(name='ConflictTest', data_path='mock_path', cif_str='cif content') - - -def test_experiment_factory_invalid_args_unsupported_key(): - # Unsupported keyword - with pytest.raises(ValueError, match='Invalid argument combination'): - ExperimentFactory.create(foo='bar') diff --git a/tests/unit/experiments/test_experiments.py b/tests/unit/experiments/test_experiments.py deleted file mode 100644 index 7c346d0a..00000000 --- a/tests/unit/experiments/test_experiments.py +++ /dev/null @@ -1,86 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -from easydiffraction.experiments.experiment import BaseExperiment -from easydiffraction.experiments.experiments import Experiments - - -class ConcreteBaseExperiment(BaseExperiment): - """Concrete implementation of BaseExperiment for testing.""" - - def _load_ascii_data_to_experiment(self, data_path): - pass - - def show_meas_chart(self, x_min=None, x_max=None): - pass - - -def test_experiments_initialization(): - experiments = Experiments() - assert isinstance(experiments, Experiments) - assert len(experiments.ids) == 0 - - -def test_experiments_add_prebuilt_experiment(): - experiments = Experiments() - mock_experiment = MagicMock(spec=BaseExperiment) - mock_experiment.name = 'TestExperiment' - - experiments.add(experiment=mock_experiment) - assert 'TestExperiment' in experiments.ids - assert experiments._experiments['TestExperiment'] == mock_experiment - - -def test_experiments_add_from_data_path(): - experiments = Experiments() - mock_experiment = MagicMock(spec=ConcreteBaseExperiment) - mock_experiment.name = 'TestExperiment' - - with patch('easydiffraction.experiments.experiment.ExperimentFactory.create', return_value=mock_experiment): - experiments.add_from_data_path( - name='TestExperiment', - sample_form='powder', - beam_mode='constant wavelength', - radiation_probe='xray', - data_path='mock_path', - ) - - assert 'TestExperiment' in experiments.ids - assert experiments['TestExperiment'] == mock_experiment - - -def test_experiments_remove(): - experiments = Experiments() - mock_experiment = MagicMock(spec=BaseExperiment) - mock_experiment.name = 'TestExperiment' - - experiments.add(experiment=mock_experiment) - assert 'TestExperiment' in experiments.ids - - experiments.remove('TestExperiment') - assert 'TestExperiment' not in experiments.ids - - -def test_experiments_show_names(capsys): - experiments = Experiments() - mock_experiment = MagicMock(spec=BaseExperiment) - mock_experiment.name = 'TestExperiment' - - experiments.add(experiment=mock_experiment) - experiments.show_names() - - captured = capsys.readouterr() - assert 'Defined experiments 🔬' in captured.out - assert 'TestExperiment' in captured.out - - -def test_experiments_as_cif(): - experiments = Experiments() - mock_experiment = MagicMock(spec=BaseExperiment) - mock_experiment.name = 'TestExperiment' - mock_experiment.as_cif.return_value = 'mock_cif_content' - - experiments.add(experiment=mock_experiment) - cif_output = experiments.as_cif() - - assert 'mock_cif_content' in cif_output diff --git a/tests/unit/sample_models/collections/test_atom_sites.py b/tests/unit/sample_models/collections/test_atom_sites.py deleted file mode 100644 index 5514e5b6..00000000 --- a/tests/unit/sample_models/collections/test_atom_sites.py +++ /dev/null @@ -1,86 +0,0 @@ -import pytest - -from easydiffraction.sample_models.collections.atom_sites import AtomSite -from easydiffraction.sample_models.collections.atom_sites import AtomSites - - -def test_atom_site_initialization(): - atom_site = AtomSite( - label='O1', - type_symbol='O', - fract_x=0.1, - fract_y=0.2, - fract_z=0.3, - wyckoff_letter='a', - occupancy=0.8, - b_iso=1.2, - adp_type='Biso', - ) - - # Assertions - assert atom_site.label.value == 'O1' - assert atom_site.type_symbol.value == 'O' - assert atom_site.fract_x.value == 0.1 - assert atom_site.fract_y.value == 0.2 - assert atom_site.fract_z.value == 0.3 - assert atom_site.wyckoff_letter.value == 'a' - assert atom_site.occupancy.value == 0.8 - assert atom_site.b_iso.value == 1.2 - assert atom_site.adp_type.value == 'Biso' - - -def test_atom_site_properties(): - atom_site = AtomSite(label='O1', type_symbol='O', fract_x=0.1, fract_y=0.2, fract_z=0.3) - - # Assertions - assert atom_site.cif_category_key == 'atom_site' - assert atom_site.category_key == 'atom_sites' - assert atom_site._entry_id == 'O1' - - -@pytest.fixture -def atom_sites_collection(): - return AtomSites() - - -def test_atom_sites_add(atom_sites_collection): - atom_sites_collection.add( - label='O1', - type_symbol='O', - fract_x=0.1, - fract_y=0.2, - fract_z=0.3, - wyckoff_letter='a', - occupancy=0.8, - b_iso=1.2, - adp_type='Biso', - ) - - # Assertions - assert 'O1' in atom_sites_collection._items - atom_site = atom_sites_collection._items['O1'] - assert isinstance(atom_site, AtomSite) - assert atom_site.label.value == 'O1' - assert atom_site.type_symbol.value == 'O' - assert atom_site.fract_x.value == 0.1 - assert atom_site.fract_y.value == 0.2 - assert atom_site.fract_z.value == 0.3 - assert atom_site.wyckoff_letter.value == 'a' - assert atom_site.occupancy.value == 0.8 - assert atom_site.b_iso.value == 1.2 - assert atom_site.adp_type.value == 'Biso' - - -def test_atom_sites_add_multiple(atom_sites_collection): - atom_sites_collection.add(label='O1', type_symbol='O', fract_x=0.1, fract_y=0.2, fract_z=0.3) - atom_sites_collection.add(label='C1', type_symbol='C', fract_x=0.4, fract_y=0.5, fract_z=0.6) - - # Assertions - assert 'O1' in atom_sites_collection._items - assert 'C1' in atom_sites_collection._items - assert len(atom_sites_collection._items) == 2 - - -def test_atom_sites_type(atom_sites_collection): - # Assertions - assert atom_sites_collection._type == 'category' diff --git a/tests/unit/sample_models/components/test_cell.py b/tests/unit/sample_models/components/test_cell.py deleted file mode 100644 index a7cdf2f8..00000000 --- a/tests/unit/sample_models/components/test_cell.py +++ /dev/null @@ -1,37 +0,0 @@ -from easydiffraction.sample_models.components.cell import Cell - - -def test_cell_initialization(): - cell = Cell(length_a=5.0, length_b=6.0, length_c=7.0, angle_alpha=80.0, angle_beta=85.0, angle_gamma=95.0) - - # Assertions - assert cell.length_a.value == 5.0 - assert cell.length_b.value == 6.0 - assert cell.length_c.value == 7.0 - assert cell.angle_alpha.value == 80.0 - assert cell.angle_beta.value == 85.0 - assert cell.angle_gamma.value == 95.0 - - assert cell.length_a.units == 'Å' - assert cell.angle_alpha.units == 'deg' - - -def test_cell_default_initialization(): - cell = Cell() - - # Assertions - assert cell.length_a.value == 10.0 - assert cell.length_b.value == 10.0 - assert cell.length_c.value == 10.0 - assert cell.angle_alpha.value == 90.0 - assert cell.angle_beta.value == 90.0 - assert cell.angle_gamma.value == 90.0 - - -def test_cell_properties(): - cell = Cell() - - # Assertions - assert cell.cif_category_key == 'cell' - assert cell.category_key == 'cell' - assert cell._entry_id is None diff --git a/tests/unit/sample_models/components/test_space_group.py b/tests/unit/sample_models/components/test_space_group.py deleted file mode 100644 index 5e0d6a1b..00000000 --- a/tests/unit/sample_models/components/test_space_group.py +++ /dev/null @@ -1,31 +0,0 @@ -from easydiffraction.sample_models.components.space_group import SpaceGroup - - -def test_space_group_initialization(): - space_group = SpaceGroup(name_h_m='P 2/m', it_coordinate_system_code=1) - - # Assertions - assert space_group.name_h_m.value == 'P 2/m' - assert space_group.name_h_m.name == 'name_h_m' - assert space_group.name_h_m.cif_name == 'name_H-M_alt' - - assert space_group.it_coordinate_system_code.value == 1 - assert space_group.it_coordinate_system_code.name == 'it_coordinate_system_code' - assert space_group.it_coordinate_system_code.cif_name == 'IT_coordinate_system_code' - - -def test_space_group_default_initialization(): - space_group = SpaceGroup() - - # Assertions - assert space_group.name_h_m.value == 'P 1' - assert space_group.it_coordinate_system_code.value is None - - -def test_space_group_properties(): - space_group = SpaceGroup() - - # Assertions - assert space_group.cif_category_key == 'space_group' - assert space_group.category_key == 'space_group' - assert space_group._entry_id is None diff --git a/tests/unit/sample_models/test_sample_models.py b/tests/unit/sample_models/test_sample_models.py deleted file mode 100644 index ce1f363b..00000000 --- a/tests/unit/sample_models/test_sample_models.py +++ /dev/null @@ -1,80 +0,0 @@ -from unittest.mock import MagicMock -from unittest.mock import patch - -import pytest - -from easydiffraction.sample_models.sample_models import SampleModel -from easydiffraction.sample_models.sample_models import SampleModels - - -@pytest.fixture -def mock_sample_model(): - with ( - patch('easydiffraction.sample_models.components.space_group.SpaceGroup') as mock_space_group, - patch('easydiffraction.sample_models.components.cell.Cell') as mock_cell, - patch('easydiffraction.sample_models.collections.atom_sites.AtomSites') as mock_atom_sites, - ): - space_group = mock_space_group.return_value - cell = mock_cell.return_value - atom_sites = mock_atom_sites.return_value - - # Mock attributes - space_group.name_h_m.value = 'P 1' - space_group.it_coordinate_system_code.value = 1 - cell.length_a.value = 1.0 - cell.length_b.value = 2.0 - cell.length_c.value = 3.0 - cell.angle_alpha.value = 90.0 - cell.angle_beta.value = 90.0 - cell.angle_gamma.value = 90.0 - atom_sites.__iter__.return_value = [] - - return SampleModel(name='test_model') - - -@pytest.fixture -def mock_sample_models(): - return SampleModels() - - -def test_sample_models_add(mock_sample_models, mock_sample_model): - mock_sample_models.add(model=mock_sample_model) - - # Assertions - assert 'test_model' in mock_sample_models.get_ids() - - -def test_sample_models_remove(mock_sample_models, mock_sample_model): - mock_sample_models.add(model=mock_sample_model) - mock_sample_models.remove('test_model') - - # Assertions - assert 'test_model' not in mock_sample_models.get_ids() - - -def test_sample_models_as_cif(mock_sample_models, mock_sample_model): - mock_sample_model.as_cif = MagicMock(return_value='data_test_model') - mock_sample_models.add(model=mock_sample_model) - - cif = mock_sample_models.as_cif() - - # Assertions - assert 'data_test_model' in cif - - -@patch('builtins.print') -def test_sample_models_show_names(mock_print, mock_sample_models, mock_sample_model): - mock_sample_models.add(model=mock_sample_model) - mock_sample_models.show_names() - - # Assertions - mock_print.assert_called_with(['test_model']) - - -@patch.object(SampleModel, 'show_params', autospec=True) -def test_sample_models_show_params(mock_show_params, mock_sample_models, mock_sample_model): - mock_sample_models.add(model=mock_sample_model) - mock_sample_models.show_params() - - # Assertions - mock_show_params.assert_called_once_with(mock_sample_model) diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py deleted file mode 100644 index ba2f15b9..00000000 --- a/tests/unit/test_project.py +++ /dev/null @@ -1,212 +0,0 @@ -import datetime -import os -import pathlib -import time -from unittest.mock import MagicMock, ANY -from unittest.mock import patch - -from easydiffraction.analysis.analysis import Analysis -from easydiffraction.experiments.experiments import Experiments -from easydiffraction.project import Project -from easydiffraction.project import ProjectInfo -from easydiffraction.sample_models.sample_models import SampleModels -from easydiffraction.summary import Summary - - -def _normalize_posix(p: pathlib.Path) -> str: - """Return a normalized POSIX-style path string with a leading '/'. - - This makes tests robust across Windows and POSIX when using paths like - '/test/path' that, on Windows, become '\\test\\path'. We avoid relying on - drive letters or platform-specific semantics for these synthetic test paths. - """ - s = str(p).replace('\\', '/') - if not s.startswith('/'): - s = '/' + s.lstrip('/') - return s - -# ------------------------------------------ -# Tests for ProjectInfo -# ------------------------------------------ - - -def test_project_info_initialization(): - project_info = ProjectInfo() - - # Assertions - assert project_info.name == 'untitled_project' - assert project_info.title == 'Untitled Project' - assert project_info.description == '' - assert project_info.path == pathlib.Path.cwd() - assert isinstance(project_info.created, datetime.datetime) - assert isinstance(project_info.last_modified, datetime.datetime) - - -def test_project_info_setters(): - project_info = ProjectInfo() - - # Set values - project_info.name = 'test_project' - project_info.title = 'Test Project' - project_info.description = 'This is a test project.' - project_info.path = '/test/path' - - # Assertions - assert project_info.name == 'test_project' - assert project_info.title == 'Test Project' - assert project_info.description == 'This is a test project.' - # Use POSIX form for cross-platform consistency (Windows vs POSIX separators) - assert _normalize_posix(project_info.path) == '/test/path' - - -def test_project_info_update_last_modified(): - project_info = ProjectInfo() - initial_last_modified = project_info.last_modified - - # Add a small delay to ensure the timestamps differ - time.sleep(0.001) - project_info.update_last_modified() - - # Assertions - assert project_info.last_modified > initial_last_modified - - -def test_project_info_as_cif(): - project_info = ProjectInfo() - project_info.name = 'test_project' - project_info.title = 'Test Project' - project_info.description = 'This is a test project.' - - cif = project_info.as_cif() - - # Assertions - assert '_project.id test_project' in cif - assert "_project.title 'Test Project'" in cif - assert "_project.description 'This is a test project.'" in cif - - -@patch('builtins.print') -def test_project_info_show_as_cif(mock_print): - project_info = ProjectInfo() - project_info.name = 'test_project' - project_info.title = 'Test Project' - project_info.description = 'This is a test project.' - - project_info.show_as_cif() - - # Assertions - mock_print.assert_called() - - -# ------------------------------------------ -# Tests for Project -# ------------------------------------------ - - -def test_project_initialization(): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - # Assertions - assert project.name == 'untitled_project' - assert isinstance(project.sample_models, SampleModels) - assert isinstance(project.experiments, Experiments) - assert isinstance(project.analysis, Analysis) - assert isinstance(project.summary, Summary) - - -@patch('builtins.print') -def test_project_load(mock_print): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - project.load('/test/path') - - # Assertions - # path is normalised/stored as a pathlib.Path - assert _normalize_posix(project.info.path) == '/test/path' - assert 'Loading project 📦 from /test/path' in mock_print.call_args_list[0][0][0] - - -@patch('builtins.print') -@patch('pathlib.Path.open', new_callable=MagicMock) -@patch('pathlib.Path.mkdir') -def test_project_save(mock_mkdir, mock_open, mock_print): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - project.info.path = '/test/path' - project.save() - - # Assertions - # Bound Path.mkdir call does not pass the path object itself as first arg - mock_mkdir.assert_any_call(parents=True, exist_ok=True) - # Bound Path.open receives only the mode (path is bound); ensure at least one write call - mock_open.assert_any_call('w') - - -@patch('builtins.print') -@patch('pathlib.Path.open', new_callable=MagicMock) -@patch('pathlib.Path.mkdir') -def test_project_save_as(mock_mkdir, mock_open, mock_print): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - project.save_as('new_project_path') - - # Assertions - assert project.info.path.name == 'new_project_path' - mock_mkdir.assert_any_call(parents=True, exist_ok=True) - mock_open.assert_any_call('w') - - -def test_project_set_sample_models(): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - sample_models = MagicMock() - project.set_sample_models(sample_models) - - # Assertions - assert project.sample_models == sample_models - - -def test_project_set_experiments(): - with ( - patch('easydiffraction.sample_models.sample_models.SampleModels'), - patch('easydiffraction.experiments.experiments.Experiments'), - patch('easydiffraction.analysis.analysis.Analysis'), - patch('easydiffraction.summary.Summary'), - ): - project = Project() # Directly assign the instance to a variable - - experiments = MagicMock() - project.set_experiments(experiments) - - # Assertions - assert project.experiments == experiments diff --git a/tests/unit/test_symmetry_lookup_table.py b/tests/unit/test_symmetry_lookup_table.py deleted file mode 100644 index 8286eb73..00000000 --- a/tests/unit/test_symmetry_lookup_table.py +++ /dev/null @@ -1,129 +0,0 @@ -import numpy as np -import pytest - -from easydiffraction.crystallography.space_groups import SPACE_GROUPS - - -def test_lookup_table_consistency(): - # Ensure that the space group numbers and system codes in the - # key are the same as in the actual entries. - for (it_number, it_code), entry in SPACE_GROUPS.items(): - assert entry['IT_number'] == it_number - assert entry['IT_coordinate_system_code'] == it_code - - -@pytest.mark.parametrize( - 'key, expected', - [ - ( - (62, 'cab'), - { - 'IT_number': 62, - 'setting': 2, - 'IT_coordinate_system_code': 'cab', - 'name_H-M_alt': 'P b n m', - 'crystal_system': 'orthorhombic', - 'Wyckoff_positions': { - 'd': { - 'multiplicity': 8, - 'site_symmetry': '1', - 'coords_xyz': [ - '(x,y,z)', - '(x+1/2,-y+1/2,-z)', - '(-x,-y,z+1/2)', - '(-x+1/2,y+1/2,-z+1/2)', - '(-x,-y,-z)', - '(-x+1/2,y+1/2,z)', - '(x,y,-z+1/2)', - '(x+1/2,-y+1/2,z+1/2)', - ], - }, - 'c': { - 'multiplicity': 4, - 'site_symmetry': '.m.', - 'coords_xyz': ['(x,y,1/4)', '(x+1/2,-y+1/2,3/4)', '(-x,-y,3/4)', '(-x+1/2,y+1/2,1/4)'], - }, - 'b': { - 'multiplicity': 4, - 'site_symmetry': '-1', - 'coords_xyz': ['(1/2,0,0)', '(0,1/2,0)', '(1/2,0,1/2)', '(0,1/2,1/2)'], - }, - 'a': { - 'multiplicity': 4, - 'site_symmetry': '-1', - 'coords_xyz': ['(0,0,0)', '(1/2,1/2,0)', '(0,0,1/2)', '(1/2,1/2,1/2)'], - }, - }, - }, - ), - ( - (199, '1'), - { - 'IT_number': 199, - 'setting': 0, - 'IT_coordinate_system_code': '1', - 'name_H-M_alt': 'I 21 3', - 'crystal_system': 'cubic', - 'Wyckoff_positions': { - 'c': { - 'multiplicity': 24, - 'site_symmetry': '1', - 'coords_xyz': [ - '(x,y,z)', - '(-x+1/2,-y,z+1/2)', - '(-x,y+1/2,-z+1/2)', - '(x+1/2,-y+1/2,-z)', - '(z,x,y)', - '(z+1/2,-x+1/2,-y)', - '(-z+1/2,-x,y+1/2)', - '(-z,x+1/2,-y+1/2)', - '(y,z,x)', - '(-y,z+1/2,-x+1/2)', - '(y+1/2,-z+1/2,-x)', - '(-y+1/2,-z,x+1/2)', - ], - }, - 'b': { - 'multiplicity': 12, - 'site_symmetry': '2..', - 'coords_xyz': [ - '(x,0,1/4)', - '(-x+1/2,0,3/4)', - '(1/4,x,0)', - '(3/4,-x+1/2,0)', - '(0,1/4,x)', - '(0,3/4,-x+1/2)', - ], - }, - 'a': { - 'multiplicity': 8, - 'site_symmetry': '.3.', - 'coords_xyz': ['(x,x,x)', '(-x+1/2,-x,x+1/2)', '(-x,x+1/2,-x+1/2)', '(x+1/2,-x+1/2,-x)'], - }, - }, - }, - ), - ], -) -def test_space_group_lookup_table_yields_expected(key, expected): - """Check the lookup table for a few keys and check that the output - matches the expected.""" - entry = SPACE_GROUPS[key] - - # Check that the keys are the same - assert set(entry.keys()) == set(expected.keys()) - - # Check the non-nested fields first - for sub_key in expected.keys(): - if sub_key == 'Wyckoff_positions': - continue - assert expected[sub_key] == entry[sub_key] - - # Then check Wyckoff - wyckoff_entry = entry['Wyckoff_positions'] - wyckoff_expected = expected['Wyckoff_positions'] - for site in wyckoff_expected.keys(): - assert site in wyckoff_expected.keys() - assert wyckoff_entry[site]['multiplicity'] == wyckoff_expected[site]['multiplicity'] - assert wyckoff_entry[site]['site_symmetry'] == wyckoff_expected[site]['site_symmetry'] - assert np.all(wyckoff_entry[site]['coords_xyz'] == wyckoff_expected[site]['coords_xyz']) diff --git a/tmp/Untitled.ipynb b/tmp/Untitled.ipynb new file mode 100644 index 00000000..b8f6e58b --- /dev/null +++ b/tmp/Untitled.ipynb @@ -0,0 +1,492 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "2b4ff90d-5a58-4202-ac2a-874168a2c6a2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction.sample_models.categories.atom_sites import AtomSite\n", + "from easydiffraction.sample_models.categories.cell import Cell\n", + "from easydiffraction.sample_models.categories.space_group import SpaceGroup" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "1100c5b2-e00c-4513-bd2e-e30742d47e67", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction.utils.logging import Logger\n", + "\n", + "Logger.configure(\n", + " level=Logger.Level.WARNING,\n", + " mode=Logger.Mode.VERBOSE,\n", + " reaction=Logger.Reaction.WARN,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "a1a30af4-91c9-4015-9c96-a571fd1a711a", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "s1 = AtomSite(label='La', type_symbol='La')" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "5f8217f7-e8cf-4202-8369-ced7438657f2", + "metadata": {}, + "outputs": [], + "source": [ + "s1.fract_x.value = 1.234" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "4c9cf2fe-7f72-4b9d-a574-5eb8c40223c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.La.fract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'xyz'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[1;36m1.234\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x.value = 'xyz'" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "cba23ca5-a865-428b-b1c8-90e38787e593", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.La.fract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'qwe'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[1;36m1.234\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "3ba30971-177b-40e4-b477-e79a00341f87", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mfract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'uuuu'\u001b[0m\u001b[1m)\u001b[0m. Using default \u001b[1;36m0.0\u001b[0m. \n" + ] + } + ], + "source": [ + "s1 = AtomSite(label='Si', type_symbol='Si', fract_x='uuuu')" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "992966e7-6bb7-4bc7-bbff-80acfea6fd2c", + "metadata": {}, + "outputs": [], + "source": [ + "s1.fract_x.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "50ef6ebd-097d-4df2-93dc-39243bdba6fd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.Si.fract_x.free\u001b[0m\u001b[1m>\u001b[0m. Expected `bool`, got `str` \u001b[1m(\u001b[0m\u001b[32m'abc'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[3;92mTrue\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x.free = 'abc'" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2c46e9ca-f68d-4b71-b783-6660f357322c", + "metadata": {}, + "outputs": [], + "source": [ + "c = Cell()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "8e3fee6f-dc71-49a0-bf67-6f85f4ba83cd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mlength_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-8.8\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Using default \u001b[1;36m10.0\u001b[0m. \n" + ] + } + ], + "source": [ + "c = Cell(length_b=-8.8)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "749e3f57-1097-4939-b853-4c67148fb831", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mlength_b\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'7.7'\u001b[0m\u001b[1m)\u001b[0m. Using default \u001b[1;36m10.0\u001b[0m. \n" + ] + } + ], + "source": [ + "c = Cell(length_b='7.7')" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "f06c5fa7-372c-4d76-b749-634d92fe6d11", + "metadata": {}, + "outputs": [], + "source": [ + "c = Cell(length_b=6.6)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "70fd1bf8-e7a3-4576-bed2-b60edf8a8097", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-5.5\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m6.6\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.value = -5.5" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b1277593-dea1-44c9-ac7e-d113f4ee3fda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-4.4\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m6.6\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b = -4.4" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "e174a5cf-2653-431b-a665-f88a8e4423f0", + "metadata": {}, + "outputs": [], + "source": [ + "c.length_b = 3.3" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "9b429e1d-eabd-4e35-a3b7-1a9b752bb4f1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m2222.2\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m3.3\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b = 2222.2" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "65d2c0fb-8e35-493c-a3e3-24421e9326bb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b.free\u001b[0m\u001b[1m>\u001b[0m. Expected `bool`, got `str` \u001b[1m(\u001b[0m\u001b[32m'qwe'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[3;91mFalse\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.free = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4475ed9b-74d1-4080-8e57-0099b35e94f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'fre'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Did you mean \u001b[32m'free'\u001b[0m? \n" + ] + } + ], + "source": [ + "c.length_b.fre = 'fre'" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "e3d37a6d-9ec8-4d96-8f95-ce7b67e65f36", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'qwe'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Allowed writable: \u001b[32m'fit_max'\u001b[0m, \u001b[32m'fit_min'\u001b[0m, \u001b[32m'free'\u001b[0m, \u001b[32m'uncertainty'\u001b[0m, \n", + " \u001b[32m'value'\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.qwe = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f108a56d-775c-4d4e-aedf-ecc4ead28178", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Cannot modify read-only attribute \u001b[32m'description'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.description = 'desc'" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "382d6221-0074-4ec8-84a8-ec51e117420a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'qwe'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell\u001b[0m\u001b[1m>\u001b[0m. Allowed writable: \u001b[32m'angle_alpha'\u001b[0m, \u001b[32m'angle_beta'\u001b[0m, \u001b[32m'angle_gamma'\u001b[0m, \u001b[32m'length_a'\u001b[0m, \n", + " \u001b[32m'length_b'\u001b[0m, \u001b[32m'length_c'\u001b[0m. \n" + ] + } + ], + "source": [ + "c.qwe = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "7bf7bac1-79ce-45df-a2a8-c4b090359292", + "metadata": {}, + "outputs": [], + "source": [ + "sg = SpaceGroup()" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "5c30d269-5167-4598-ac57-66715673d9b0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mname_h_m\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'qwe'\u001b[0m is unknown. \u001b[1m(\u001b[0m\u001b[1;36m230\u001b[0m allowed values not listed here\u001b[1m)\u001b[0m. Using default \n", + " \u001b[32m'P 1'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='qwe')" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "c99fcf63-9536-4ea6-a30a-96367bb4aacb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mname_h_m\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'P n m'\u001b[0m is unknown. \u001b[1m(\u001b[0m\u001b[1;36m230\u001b[0m allowed values not listed here\u001b[1m)\u001b[0m. Using default\n", + " \u001b[32m'P 1'\u001b[0m. \n", + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mit_coordinate_system_code\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'cab'\u001b[0m is unknown. Allowed values: \u001b[32m''\u001b[0m. Using default \n", + " \u001b[32m''\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='P n m', it_coordinate_system_code='cab')" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "c2d7509e-a49b-4c2b-aa45-76a97de0761e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mit_coordinate_system_code\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'cabd'\u001b[0m is unknown. Allowed values: \u001b[32m'-cba'\u001b[0m, \u001b[32m'a-cb'\u001b[0m, \n", + " \u001b[32m'abc'\u001b[0m, \u001b[32m'ba-c'\u001b[0m, \u001b[32m'bca'\u001b[0m, \u001b[32m'cab'\u001b[0m. Using default \u001b[32m'abc'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='P n m a', it_coordinate_system_code='cabd')" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "07db2ef2-58f3-4b46-8223-a2067254db51", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mspace_group.name_h_m\u001b[0m\u001b[1m>\u001b[0m. Expected `string`, got `float` \u001b[1m(\u001b[0m\u001b[1;36m34.9\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[32m'P n m a'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg.name_h_m = 34.9" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6934d72d-01a7-41e3-8684-5a1c6106e933", + "metadata": {}, + "outputs": [], + "source": [ + "sg.name_h_m = 'P 1'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8a152ec-5a11-4c38-bca4-7a4230218f34", + "metadata": {}, + "outputs": [], + "source": [ + "sg.name_h_m = 'P n m a'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6626a960-6390-4e76-8f27-dc2cebbdd123", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb299acb-0920-43f3-be25-c432544e1198", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (Pixi)", + "language": "python", + "name": "pixi-kernel-python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tmp/Untitled1.ipynb b/tmp/Untitled1.ipynb new file mode 100644 index 00000000..6ebdcb97 --- /dev/null +++ b/tmp/Untitled1.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "e7e00e10-ee5c-48cb-87f0-a34f8162a39d", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction import SampleModelFactory" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8aadcc06-0b34-40ea-a921-b326d2aa83e1", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction.utils.logging import Logger\n", + "\n", + "Logger.configure(\n", + " level=Logger.Level.WARNING,\n", + " mode=Logger.Mode.VERBOSE,\n", + " reaction=Logger.Reaction.WARN,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "26c42a06-7efc-468a-9c57-640447900d83", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n",
+       " in <module>:1                                                                                    \n",
+       "                                                                                                  \n",
+       " 1 model = SampleModelFactory.create(name='name', cif_path='path')                              \n",
+       "   2                                                                                              \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/samp \n",
+       " le_models/sample_model/factory.py:28 in create                                                   \n",
+       "                                                                                                  \n",
+       "    25 │   │   \"\"\"                                                                                \n",
+       "    26 │   │   # Check for valid argument combinations                                            \n",
+       "    27 │   │   user_args = {k for k, v in kwargs.items() if v is not None}                        \n",
+       "  28 │   │   cls._validate_args(user_args, cls._ALLOWED_ARG_SPECS, cls.__name__)                \n",
+       "    29 │   │                                                                                      \n",
+       "    30 │   │   if 'cif_path' in kwargs:                                                           \n",
+       "    31 │   │   │   return cls._create_from_cif_path(kwargs['cif_path'])                           \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/core \n",
+       " /factory.py:29 in _validate_args                                                                 \n",
+       "                                                                                                  \n",
+       "   26 │   │   │   │   combos.append(f'({req}[, {opt}])')                                          \n",
+       "   27 │   │   │   else:                                                                           \n",
+       "   28 │   │   │   │   combos.append(f'({req})')                                                   \n",
+       " 29 │   │   raise ValueError(                                                                   \n",
+       "   30 │   │   │   f'Invalid argument combination for {factory_name} creation.\\n'                  \n",
+       "   31 │   │   │   f'Provided: {sorted(present)}\\n'                                                \n",
+       "   32 │   │   │   f'Allowed combinations:\\n  ' + '\\n  '.join(combos)                              \n",
+       "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
+       "ValueError: Invalid argument combination for SampleModelFactory creation.\n",
+       "Provided: ['cif_path', 'name']\n",
+       "Allowed combinations:\n",
+       "  (name)\n",
+       "  (cif_path)\n",
+       "  (cif_str)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[31m╭─\u001b[0m\u001b[31m──────────────────────────────\u001b[0m\u001b[31m \u001b[0m\u001b[1;31mTraceback \u001b[0m\u001b[1;2;31m(most recent call last)\u001b[0m\u001b[31m \u001b[0m\u001b[31m───────────────────────────────\u001b[0m\u001b[31m─╮\u001b[0m\n", + "\u001b[31m│\u001b[0m in :1 \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m1 model = \u001b[1;4mSampleModelFactory.create(name=\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mname\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4m, cif_path=\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mpath\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4m)\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m2 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/samp\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2mle_models/sample_model/\u001b[0m\u001b[1mfactory.py\u001b[0m:28 in create \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 25 \u001b[0m\u001b[2;33m│ │ \u001b[0m\u001b[33m\"\"\"\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 26 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# Check for valid argument combinations\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 27 \u001b[0m\u001b[2m│ │ \u001b[0muser_args = {k \u001b[94mfor\u001b[0m k, v \u001b[95min\u001b[0m kwargs.items() \u001b[94mif\u001b[0m v \u001b[95mis\u001b[0m \u001b[95mnot\u001b[0m \u001b[94mNone\u001b[0m} \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 28 \u001b[2m│ │ \u001b[0m\u001b[1;4;96mcls\u001b[0m\u001b[1;4m._validate_args(user_args, \u001b[0m\u001b[1;4;96mcls\u001b[0m\u001b[1;4m._ALLOWED_ARG_SPECS, \u001b[0m\u001b[1;4;96mcls\u001b[0m\u001b[1;4m.\u001b[0m\u001b[1;4;91m__name__\u001b[0m\u001b[1;4m)\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 29 \u001b[0m\u001b[2m│ │ \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 30 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mif\u001b[0m \u001b[33m'\u001b[0m\u001b[33mcif_path\u001b[0m\u001b[33m'\u001b[0m \u001b[95min\u001b[0m kwargs: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 31 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[96mcls\u001b[0m._create_from_cif_path(kwargs[\u001b[33m'\u001b[0m\u001b[33mcif_path\u001b[0m\u001b[33m'\u001b[0m]) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/core\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/\u001b[0m\u001b[1mfactory.py\u001b[0m:29 in _validate_args \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m26 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mcombos.append(\u001b[33mf\u001b[0m\u001b[33m'\u001b[0m\u001b[33m(\u001b[0m\u001b[33m{\u001b[0mreq\u001b[33m}\u001b[0m\u001b[33m[, \u001b[0m\u001b[33m{\u001b[0mopt\u001b[33m}\u001b[0m\u001b[33m])\u001b[0m\u001b[33m'\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m27 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94melse\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m28 \u001b[0m\u001b[2m│ │ │ │ \u001b[0mcombos.append(\u001b[33mf\u001b[0m\u001b[33m'\u001b[0m\u001b[33m(\u001b[0m\u001b[33m{\u001b[0mreq\u001b[33m}\u001b[0m\u001b[33m)\u001b[0m\u001b[33m'\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m29 \u001b[2m│ │ \u001b[0m\u001b[1;4;94mraise\u001b[0m\u001b[1;4m \u001b[0m\u001b[1;4;96mValueError\u001b[0m\u001b[1;4m(\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m30 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4;33mf\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mInvalid argument combination for \u001b[0m\u001b[1;4;33m{\u001b[0m\u001b[1;4mfactory_name\u001b[0m\u001b[1;4;33m}\u001b[0m\u001b[1;4;33m creation.\u001b[0m\u001b[1;4;33m\\n\u001b[0m\u001b[1;4;33m'\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m31 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4;33mf\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mProvided: \u001b[0m\u001b[1;4;33m{\u001b[0m\u001b[1;4;96msorted\u001b[0m\u001b[1;4m(present)\u001b[0m\u001b[1;4;33m}\u001b[0m\u001b[1;4;33m\\n\u001b[0m\u001b[1;4;33m'\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m32 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4;33mf\u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33mAllowed combinations:\u001b[0m\u001b[1;4;33m\\n\u001b[0m\u001b[1;4;33m \u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4m + \u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4;33m\\n\u001b[0m\u001b[1;4;33m \u001b[0m\u001b[1;4;33m'\u001b[0m\u001b[1;4m.join(combos)\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", + "\u001b[1;91mValueError: \u001b[0mInvalid argument combination for SampleModelFactory creation.\n", + "Provided: \u001b[1m[\u001b[0m\u001b[32m'cif_path'\u001b[0m, \u001b[32m'name'\u001b[0m\u001b[1m]\u001b[0m\n", + "Allowed combinations:\n", + " \u001b[1m(\u001b[0mname\u001b[1m)\u001b[0m\n", + " \u001b[1m(\u001b[0mcif_path\u001b[1m)\u001b[0m\n", + " \u001b[1m(\u001b[0mcif_str\u001b[1m)\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "model = SampleModelFactory.create(name='name', cif_path='path')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6b0cec6-220d-476b-b918-b66e8322a3e9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (Pixi)", + "language": "python", + "name": "pixi-kernel-python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials-drafts/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py b/tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py similarity index 71% rename from tutorials-drafts/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py rename to tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py index 3a5c8174..b4ee7e15 100644 --- a/tutorials-drafts/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py +++ b/tmp/cryst-struct_pd-neut-tof_multiphase-BSFTO-HRPT.py @@ -41,17 +41,17 @@ # #### Set Atom Sites # %% -model_1.atom_sites.add('Bi1', 'Bi', 0.702, 0.114, 0, wyckoff_letter='g', b_iso=0.0, occupancy=0.88) -model_1.atom_sites.add('Sm1', 'Sm', 0.702, 0.114, 0, wyckoff_letter='g', b_iso=0.0, occupancy=0.12) -model_1.atom_sites.add('Bi2', 'Bi', 0.751, 0.132, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=0.88) -model_1.atom_sites.add('Sm2', 'Sm', 0.751, 0.132, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=0.12) -model_1.atom_sites.add('Fe', 'Fe', 0.236, 0.121, 0.259, wyckoff_letter='i', b_iso=0.0, occupancy=0.94) -model_1.atom_sites.add('Ti', 'Ti', 0.236, 0.121, 0.259, wyckoff_letter='i', b_iso=0.0, occupancy=0.06) -model_1.atom_sites.add('O1', 'O', 0.258, 0.151, 0, wyckoff_letter='g', b_iso=0.0, occupancy=1.0) -model_1.atom_sites.add('O2', 'O', 0.316, 0.093, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=1.0) -model_1.atom_sites.add('O3', 'O', 0.002, 0.258, 0.299, wyckoff_letter='i', b_iso=0.0, occupancy=1.0) -model_1.atom_sites.add('O4', 'O', 0, 0.5, 0.264, wyckoff_letter='f', b_iso=0.0, occupancy=1.0) -model_1.atom_sites.add('O5', 'O', 0, 0, 0.198, wyckoff_letter='e', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('Bi1', 'Bi', 0.702, 0.114, 0, wyckoff_letter='g', b_iso=0.0, occupancy=0.88) +model_1.atom_sites.add('Sm1', 'Sm', 0.702, 0.114, 0, wyckoff_letter='g', b_iso=0.0, occupancy=0.12) +model_1.atom_sites.add('Bi2', 'Bi', 0.751, 0.132, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=0.88) +model_1.atom_sites.add('Sm2', 'Sm', 0.751, 0.132, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=0.12) +model_1.atom_sites.add('Fe', 'Fe', 0.236, 0.121, 0.259, wyckoff_letter='i', b_iso=0.0, occupancy=0.94) +model_1.atom_sites.add('Ti', 'Ti', 0.236, 0.121, 0.259, wyckoff_letter='i', b_iso=0.0, occupancy=0.06) +model_1.atom_sites.add('O1', 'O', 0.258, 0.151, 0, wyckoff_letter='g', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('O2', 'O', 0.316, 0.093, 0.5, wyckoff_letter='h', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('O3', 'O', 0.002, 0.258, 0.299, wyckoff_letter='i', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('O4', 'O', 0, 0.5, 0.264, wyckoff_letter='f', b_iso=0.0, occupancy=1.0) +model_1.atom_sites.add('O5', 'O', 0, 0, 0.198, wyckoff_letter='e', b_iso=0.0, occupancy=1.0) # %% [markdown] # ### Create Sample Model 2: Rhombohedral phase @@ -77,11 +77,11 @@ # #### Set Atom Sites # %% -model_2.atom_sites.add('Bi', 'Bi', 0, 0, 0, wyckoff_letter='a', b_iso=0.0, occupancy=0.88) -model_2.atom_sites.add('Sm', 'Sm', 0, 0, 0, wyckoff_letter='a', b_iso=0.0, occupancy=0.12) -model_2.atom_sites.add('Fe', 'Fe', 0, 0, 0.223, wyckoff_letter='a', b_iso=0.0, occupancy=0.94) -model_2.atom_sites.add('Ti', 'Ti', 0, 0, 0.223, wyckoff_letter='a', b_iso=0.0, occupancy=0.06) -model_2.atom_sites.add('O', 'O', 0.436, 0.022, 0.958, wyckoff_letter='b', b_iso=0.0, occupancy=1.0) +model_2.atom_sites.add('Bi', 'Bi', 0, 0, 0, wyckoff_letter='a', b_iso=0.0, occupancy=0.88) +model_2.atom_sites.add('Sm', 'Sm', 0, 0, 0, wyckoff_letter='a', b_iso=0.0, occupancy=0.12) +model_2.atom_sites.add('Fe', 'Fe', 0, 0, 0.223, wyckoff_letter='a', b_iso=0.0, occupancy=0.94) +model_2.atom_sites.add('Ti', 'Ti', 0, 0, 0.223, wyckoff_letter='a', b_iso=0.0, occupancy=0.06) +model_2.atom_sites.add('O', 'O', 0.436, 0.022, 0.958, wyckoff_letter='b', b_iso=0.0, occupancy=1.0) # %% [markdown] # ## Define Experiment @@ -92,7 +92,7 @@ # #### Download Data # %% -#download_from_repository('hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye', +# download_from_repository('hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye', # branch='develop', # destination='data') @@ -237,11 +237,11 @@ # Set sample model parameters to be optimized. # %% -#model_1.cell.length_a.free = True -#model_1.atom_sites['Co'].b_iso.free = True -#model_1.atom_sites['O'].b_iso.free = True +# model_1.cell.length_a.free = True +# model_1.atom_sites['Co'].b_iso.free = True +# model_1.atom_sites['O'].b_iso.free = True -#model_2.cell.length_a.free = True +# model_2.cell.length_a.free = True # %% [markdown] # Set experiment parameters to be optimized. diff --git a/tutorials-drafts/data/DREAM_mantle_bc240_nist_cif.xye b/tmp/data/DREAM_mantle_bc240_nist_cif.xye similarity index 100% rename from tutorials-drafts/data/DREAM_mantle_bc240_nist_cif.xye rename to tmp/data/DREAM_mantle_bc240_nist_cif.xye diff --git a/tutorials-drafts/data/DREAM_mantle_bc240_nist_cif_2.xye b/tmp/data/DREAM_mantle_bc240_nist_cif_2.xye similarity index 100% rename from tutorials-drafts/data/DREAM_mantle_bc240_nist_cif_2.xye rename to tmp/data/DREAM_mantle_bc240_nist_cif_2.xye diff --git a/tutorials-drafts/data/DREAM_mantle_bc240_nist_nc.xye b/tmp/data/DREAM_mantle_bc240_nist_nc.xye similarity index 100% rename from tutorials-drafts/data/DREAM_mantle_bc240_nist_nc.xye rename to tmp/data/DREAM_mantle_bc240_nist_nc.xye diff --git a/tutorials-drafts/data/DREAM_mantle_bc240_nist_nc_2.xye b/tmp/data/DREAM_mantle_bc240_nist_nc_2.xye similarity index 100% rename from tutorials-drafts/data/DREAM_mantle_bc240_nist_nc_2.xye rename to tmp/data/DREAM_mantle_bc240_nist_nc_2.xye diff --git a/tutorials-drafts/data/Si_mp-149_symmetrized_mcstas.cif b/tmp/data/Si_mp-149_symmetrized_mcstas.cif similarity index 100% rename from tutorials-drafts/data/Si_mp-149_symmetrized_mcstas.cif rename to tmp/data/Si_mp-149_symmetrized_mcstas.cif diff --git a/tmp/data/hrpt_lbco.xye b/tmp/data/hrpt_lbco.xye new file mode 100644 index 00000000..0b9b63e3 --- /dev/null +++ b/tmp/data/hrpt_lbco.xye @@ -0,0 +1,3099 @@ +# 2theta intensity su + 10.00 167.00 12.60 + 10.05 157.00 12.50 + 10.10 187.00 13.30 + 10.15 197.00 14.00 + 10.20 164.00 12.50 + 10.25 171.00 13.00 + 10.30 190.00 13.40 + 10.35 182.00 13.50 + 10.40 166.00 12.60 + 10.45 203.00 14.30 + 10.50 156.00 12.20 + 10.55 190.00 13.90 + 10.60 175.00 13.00 + 10.65 161.00 12.90 + 10.70 187.00 13.50 + 10.75 166.00 13.10 + 10.80 171.00 13.00 + 10.85 177.00 13.60 + 10.90 159.00 12.60 + 10.95 184.00 13.90 + 11.00 160.00 12.60 + 11.05 182.00 13.90 + 11.10 167.00 13.00 + 11.15 169.00 13.40 + 11.20 186.00 13.70 + 11.25 167.00 13.30 + 11.30 169.00 13.10 + 11.35 159.00 13.10 + 11.40 170.00 13.20 + 11.45 179.00 13.90 + 11.50 178.00 13.50 + 11.55 188.00 14.20 + 11.60 176.00 13.50 + 11.65 196.00 14.60 + 11.70 182.00 13.70 + 11.75 183.00 14.00 + 11.80 195.00 14.10 + 11.85 144.00 12.40 + 11.90 178.00 13.50 + 11.95 175.00 13.70 + 12.00 200.00 14.20 + 12.05 157.00 12.90 + 12.10 195.00 14.00 + 12.15 164.00 13.10 + 12.20 188.00 13.70 + 12.25 168.00 13.10 + 12.30 191.00 13.70 + 12.35 178.00 13.40 + 12.40 182.00 13.30 + 12.45 174.00 13.30 + 12.50 171.00 12.90 + 12.55 174.00 13.20 + 12.60 184.00 13.30 + 12.65 164.00 12.80 + 12.70 166.00 12.50 + 12.75 177.00 13.20 + 12.80 174.00 12.80 + 12.85 187.00 13.50 + 12.90 183.00 13.10 + 12.95 187.00 13.50 + 13.00 175.00 12.80 + 13.05 165.00 12.70 + 13.10 177.00 12.80 + 13.15 182.00 13.30 + 13.20 195.00 13.50 + 13.25 163.00 12.60 + 13.30 180.00 12.90 + 13.35 171.00 12.90 + 13.40 182.00 13.00 + 13.45 179.00 13.10 + 13.50 161.00 12.20 + 13.55 156.00 12.30 + 13.60 197.00 13.50 + 13.65 167.00 12.70 + 13.70 180.00 12.80 + 13.75 182.00 13.20 + 13.80 176.00 12.70 + 13.85 153.00 12.10 + 13.90 179.00 12.80 + 13.95 156.00 12.30 + 14.00 187.00 13.10 + 14.05 170.00 12.80 + 14.10 185.00 13.00 + 14.15 180.00 13.20 + 14.20 167.00 12.40 + 14.25 159.00 12.40 + 14.30 152.00 11.80 + 14.35 173.00 13.00 + 14.40 169.00 12.50 + 14.45 185.00 13.40 + 14.50 168.00 12.40 + 14.55 193.00 13.70 + 14.60 177.00 12.80 + 14.65 161.00 12.50 + 14.70 180.00 12.90 + 14.75 165.00 12.60 + 14.80 178.00 12.80 + 14.85 157.00 12.30 + 14.90 163.00 12.30 + 14.95 143.00 11.70 + 15.00 155.00 11.90 + 15.05 168.00 12.80 + 15.10 160.00 12.10 + 15.15 155.00 12.20 + 15.20 203.00 13.70 + 15.25 164.00 12.60 + 15.30 158.00 12.10 + 15.35 152.00 12.10 + 15.40 173.00 12.60 + 15.45 160.00 12.50 + 15.50 172.00 12.60 + 15.55 164.00 12.60 + 15.60 163.00 12.30 + 15.65 173.00 13.00 + 15.70 177.00 12.80 + 15.75 184.00 13.40 + 15.80 173.00 12.70 + 15.85 182.00 13.30 + 15.90 156.00 12.10 + 15.95 152.00 12.20 + 16.00 201.00 13.70 + 16.05 156.00 12.30 + 16.10 169.00 12.50 + 16.15 178.00 13.20 + 16.20 150.00 11.80 + 16.25 163.00 12.60 + 16.30 165.00 12.40 + 16.35 160.00 12.50 + 16.40 171.00 12.60 + 16.45 168.00 12.80 + 16.50 159.00 12.20 + 16.55 166.00 12.80 + 16.60 156.00 12.10 + 16.65 156.00 12.40 + 16.70 154.00 12.10 + 16.75 173.00 13.10 + 16.80 173.00 12.80 + 16.85 161.00 12.70 + 16.90 177.00 13.00 + 16.95 159.00 12.70 + 17.00 162.00 12.50 + 17.05 166.00 13.00 + 17.10 167.00 12.70 + 17.15 166.00 13.10 + 17.20 168.00 12.80 + 17.25 188.00 14.00 + 17.30 165.00 12.80 + 17.35 171.00 13.40 + 17.40 171.00 13.10 + 17.45 162.00 13.10 + 17.50 161.00 12.80 + 17.55 177.00 13.80 + 17.60 176.00 13.40 + 17.65 175.00 13.70 + 17.70 140.00 12.00 + 17.75 177.00 13.90 + 17.80 150.00 12.40 + 17.85 154.00 12.90 + 17.90 138.00 11.90 + 17.95 161.00 13.20 + 18.00 171.00 13.30 + 18.05 144.00 12.50 + 18.10 148.00 12.40 + 18.15 169.00 13.50 + 18.20 162.00 12.90 + 18.25 171.00 13.50 + 18.30 155.00 12.60 + 18.35 143.00 12.30 + 18.40 162.00 12.80 + 18.45 177.00 13.60 + 18.50 158.00 12.60 + 18.55 142.00 12.20 + 18.60 153.00 12.40 + 18.65 169.00 13.30 + 18.70 144.00 12.00 + 18.75 171.00 13.30 + 18.80 159.00 12.50 + 18.85 169.00 13.10 + 18.90 163.00 12.60 + 18.95 154.00 12.50 + 19.00 146.00 11.90 + 19.05 154.00 12.50 + 19.10 156.00 12.20 + 19.15 195.00 14.00 + 19.20 154.00 12.10 + 19.25 167.00 12.90 + 19.30 156.00 12.20 + 19.35 148.00 12.10 + 19.40 173.00 12.80 + 19.45 155.00 12.40 + 19.50 146.00 11.70 + 19.55 173.00 13.10 + 19.60 179.00 13.00 + 19.65 152.00 12.30 + 19.70 182.00 13.10 + 19.75 183.00 13.40 + 19.80 150.00 11.90 + 19.85 155.00 12.30 + 19.90 158.00 12.20 + 19.95 161.00 12.60 + 20.00 164.00 12.40 + 20.05 166.00 12.80 + 20.10 172.00 12.70 + 20.15 148.00 12.10 + 20.20 161.00 12.30 + 20.25 160.00 12.60 + 20.30 185.00 13.20 + 20.35 165.00 12.80 + 20.40 155.00 12.10 + 20.45 172.00 13.00 + 20.50 170.00 12.70 + 20.55 180.00 13.40 + 20.60 184.00 13.20 + 20.65 164.00 12.80 + 20.70 177.00 13.00 + 20.75 150.00 12.20 + 20.80 176.00 12.90 + 20.85 174.00 13.20 + 20.90 173.00 12.80 + 20.95 167.00 12.90 + 21.00 158.00 12.20 + 21.05 174.00 13.20 + 21.10 160.00 12.30 + 21.15 174.00 13.20 + 21.20 160.00 12.30 + 21.25 182.00 13.40 + 21.30 155.00 12.10 + 21.35 182.00 13.40 + 21.40 157.00 12.20 + 21.45 174.00 13.20 + 21.50 173.00 12.80 + 21.55 165.00 12.80 + 21.60 182.00 13.10 + 21.65 176.00 13.20 + 21.70 150.00 11.90 + 21.75 162.00 12.60 + 21.80 172.00 12.70 + 21.85 162.00 12.70 + 21.90 171.00 12.70 + 21.95 165.00 12.80 + 22.00 180.00 13.00 + 22.05 167.00 12.80 + 22.10 159.00 12.20 + 22.15 159.00 12.50 + 22.20 160.00 12.30 + 22.25 174.00 13.10 + 22.30 175.00 12.90 + 22.35 172.00 13.10 + 22.40 176.00 12.90 + 22.45 140.00 11.80 + 22.50 163.00 12.40 + 22.55 180.00 13.50 + 22.60 211.00 14.20 + 22.65 190.00 13.90 + 22.70 179.00 13.10 + 22.75 195.00 14.10 + 22.80 198.00 13.90 + 22.85 181.00 13.70 + 22.90 203.00 14.10 + 22.95 193.00 14.10 + 23.00 155.00 12.40 + 23.05 159.00 12.90 + 23.10 184.00 13.50 + 23.15 145.00 12.30 + 23.20 145.00 12.00 + 23.25 179.00 13.70 + 23.30 185.00 13.60 + 23.35 168.00 13.30 + 23.40 185.00 13.60 + 23.45 170.00 13.40 + 23.50 174.00 13.30 + 23.55 164.00 13.20 + 23.60 168.00 13.10 + 23.65 185.00 14.10 + 23.70 183.00 13.70 + 23.75 172.00 13.70 + 23.80 156.00 12.70 + 23.85 182.00 14.00 + 23.90 182.00 13.70 + 23.95 149.00 12.70 + 24.00 160.00 12.80 + 24.05 168.00 13.50 + 24.10 178.00 13.60 + 24.15 169.00 13.60 + 24.20 172.00 13.40 + 24.25 170.00 13.60 + 24.30 161.00 12.90 + 24.35 168.00 13.50 + 24.40 162.00 13.00 + 24.45 157.00 13.00 + 24.50 162.00 12.90 + 24.55 159.00 13.10 + 24.60 168.00 13.20 + 24.65 170.00 13.50 + 24.70 166.00 13.00 + 24.75 146.00 12.50 + 24.80 154.00 12.50 + 24.85 154.00 12.70 + 24.90 198.00 14.10 + 24.95 195.00 14.30 + 25.00 148.00 12.20 + 25.05 161.00 12.90 + 25.10 160.00 12.60 + 25.15 160.00 12.80 + 25.20 149.00 12.10 + 25.25 179.00 13.50 + 25.30 174.00 13.00 + 25.35 168.00 13.00 + 25.40 146.00 11.90 + 25.45 160.00 12.70 + 25.50 145.00 11.80 + 25.55 151.00 12.30 + 25.60 161.00 12.40 + 25.65 187.00 13.60 + 25.70 154.00 12.10 + 25.75 157.00 12.40 + 25.80 169.00 12.60 + 25.85 181.00 13.40 + 25.90 156.00 12.10 + 25.95 185.00 13.40 + 26.00 192.00 13.40 + 26.05 153.00 12.20 + 26.10 149.00 11.80 + 26.15 154.00 12.20 + 26.20 152.00 11.90 + 26.25 179.00 13.20 + 26.30 180.00 12.90 + 26.35 160.00 12.50 + 26.40 174.00 12.60 + 26.45 145.00 11.80 + 26.50 171.00 12.50 + 26.55 162.00 12.50 + 26.60 154.00 11.80 + 26.65 153.00 12.10 + 26.70 162.00 12.10 + 26.75 160.00 12.40 + 26.80 150.00 11.70 + 26.85 189.00 13.40 + 26.90 168.00 12.40 + 26.95 144.00 11.70 + 27.00 147.00 11.60 + 27.05 155.00 12.20 + 27.10 174.00 12.60 + 27.15 169.00 12.70 + 27.20 174.00 12.60 + 27.25 164.00 12.60 + 27.30 146.00 11.60 + 27.35 149.00 12.00 + 27.40 155.00 11.90 + 27.45 155.00 12.20 + 27.50 168.00 12.40 + 27.55 131.00 11.20 + 27.60 159.00 12.10 + 27.65 181.00 13.20 + 27.70 146.00 11.60 + 27.75 188.00 13.50 + 27.80 162.00 12.20 + 27.85 161.00 12.50 + 27.90 176.00 12.70 + 27.95 152.00 12.10 + 28.00 170.00 12.40 + 28.05 152.00 12.00 + 28.10 158.00 12.00 + 28.15 168.00 12.60 + 28.20 161.00 12.10 + 28.25 184.00 13.30 + 28.30 166.00 12.30 + 28.35 193.00 13.60 + 28.40 157.00 12.00 + 28.45 167.00 12.60 + 28.50 158.00 12.00 + 28.55 135.00 11.40 + 28.60 150.00 11.70 + 28.65 167.00 12.70 + 28.70 161.00 12.20 + 28.75 157.00 12.30 + 28.80 153.00 11.80 + 28.85 161.00 12.50 + 28.90 163.00 12.20 + 28.95 133.00 11.40 + 29.00 169.00 12.50 + 29.05 162.00 12.50 + 29.10 161.00 12.20 + 29.15 163.00 12.60 + 29.20 144.00 11.60 + 29.25 178.00 13.20 + 29.30 161.00 12.20 + 29.35 141.00 11.80 + 29.40 169.00 12.50 + 29.45 160.00 12.50 + 29.50 177.00 12.90 + 29.55 174.00 13.10 + 29.60 157.00 12.10 + 29.65 176.00 13.20 + 29.70 179.00 13.00 + 29.75 166.00 12.90 + 29.80 162.00 12.40 + 29.85 147.00 12.20 + 29.90 152.00 12.00 + 29.95 171.00 13.20 + 30.00 178.00 13.10 + 30.05 208.00 14.60 + 30.10 178.00 13.20 + 30.15 149.00 12.40 + 30.20 181.00 13.30 + 30.25 162.00 13.00 + 30.30 177.00 13.20 + 30.35 165.00 13.10 + 30.40 177.00 13.30 + 30.45 158.00 12.90 + 30.50 157.00 12.60 + 30.55 163.00 13.10 + 30.60 144.00 12.00 + 30.65 156.00 12.80 + 30.70 176.00 13.30 + 30.75 179.00 13.70 + 30.80 174.00 13.20 + 30.85 182.00 13.80 + 30.90 161.00 12.70 + 30.95 166.00 13.10 + 31.00 168.00 13.00 + 31.05 153.00 12.60 + 31.10 156.00 12.40 + 31.15 174.00 13.40 + 31.20 167.00 12.80 + 31.25 192.00 14.00 + 31.30 154.00 12.30 + 31.35 166.00 13.00 + 31.40 169.00 12.90 + 31.45 185.00 13.70 + 31.50 165.00 12.60 + 31.55 163.00 12.80 + 31.60 173.00 12.90 + 31.65 169.00 13.00 + 31.70 188.00 13.40 + 31.75 195.00 13.90 + 31.80 195.00 13.60 + 31.85 221.00 14.70 + 31.90 229.00 14.70 + 31.95 302.00 17.20 + 32.00 327.00 17.50 + 32.05 380.00 19.30 + 32.10 358.00 18.30 + 32.15 394.00 19.60 + 32.20 373.00 18.70 + 32.25 362.00 18.70 + 32.30 306.00 16.90 + 32.35 276.00 16.40 + 32.40 237.00 14.80 + 32.45 203.00 14.00 + 32.50 178.00 12.80 + 32.55 199.00 13.90 + 32.60 167.00 12.40 + 32.65 185.00 13.40 + 32.70 180.00 12.90 + 32.75 178.00 13.10 + 32.80 145.00 11.50 + 32.85 176.00 13.00 + 32.90 177.00 12.70 + 32.95 182.00 13.20 + 33.00 167.00 12.40 + 33.05 152.00 12.10 + 33.10 144.00 11.50 + 33.15 170.00 12.80 + 33.20 156.00 11.90 + 33.25 154.00 12.20 + 33.30 180.00 12.80 + 33.35 176.00 13.00 + 33.40 183.00 12.90 + 33.45 162.00 12.40 + 33.50 180.00 12.80 + 33.55 165.00 12.60 + 33.60 174.00 12.50 + 33.65 179.00 13.00 + 33.70 152.00 11.70 + 33.75 182.00 13.10 + 33.80 184.00 12.90 + 33.85 166.00 12.50 + 33.90 182.00 12.80 + 33.95 162.00 12.40 + 34.00 174.00 12.50 + 34.05 153.00 12.00 + 34.10 182.00 12.80 + 34.15 180.00 13.00 + 34.20 167.00 12.20 + 34.25 173.00 12.70 + 34.30 153.00 11.70 + 34.35 160.00 12.30 + 34.40 180.00 12.70 + 34.45 168.00 12.50 + 34.50 167.00 12.20 + 34.55 176.00 12.80 + 34.60 165.00 12.10 + 34.65 174.00 12.80 + 34.70 161.00 12.00 + 34.75 178.00 12.90 + 34.80 170.00 12.30 + 34.85 166.00 12.50 + 34.90 173.00 12.40 + 34.95 158.00 12.20 + 35.00 166.00 12.20 + 35.05 170.00 12.60 + 35.10 162.00 12.00 + 35.15 183.00 13.10 + 35.20 176.00 12.50 + 35.25 171.00 12.60 + 35.30 174.00 12.50 + 35.35 179.00 12.90 + 35.40 176.00 12.50 + 35.45 193.00 13.40 + 35.50 180.00 12.70 + 35.55 188.00 13.30 + 35.60 177.00 12.60 + 35.65 176.00 12.90 + 35.70 171.00 12.40 + 35.75 185.00 13.30 + 35.80 178.00 12.70 + 35.85 152.00 12.10 + 35.90 160.00 12.10 + 35.95 187.00 13.50 + 36.00 167.00 12.40 + 36.05 181.00 13.30 + 36.10 166.00 12.40 + 36.15 165.00 12.80 + 36.20 170.00 12.70 + 36.25 197.00 14.10 + 36.30 179.00 13.10 + 36.35 172.00 13.20 + 36.40 181.00 13.30 + 36.45 174.00 13.40 + 36.50 162.00 12.60 + 36.55 166.00 13.10 + 36.60 158.00 12.50 + 36.65 199.00 14.40 + 36.70 188.00 13.70 + 36.75 177.00 13.70 + 36.80 167.00 12.90 + 36.85 156.00 12.90 + 36.90 174.00 13.20 + 36.95 176.00 13.70 + 37.00 152.00 12.40 + 37.05 191.00 14.40 + 37.10 151.00 12.50 + 37.15 202.00 14.80 + 37.20 191.00 14.00 + 37.25 161.00 13.20 + 37.30 199.00 14.30 + 37.35 175.00 13.70 + 37.40 146.00 12.30 + 37.45 181.00 14.00 + 37.50 221.00 15.00 + 37.55 194.00 14.40 + 37.60 158.00 12.70 + 37.65 171.00 13.50 + 37.70 172.00 13.20 + 37.75 168.00 13.30 + 37.80 192.00 13.90 + 37.85 185.00 13.90 + 37.90 193.00 13.90 + 37.95 178.00 13.60 + 38.00 195.00 13.90 + 38.05 175.00 13.40 + 38.10 178.00 13.20 + 38.15 173.00 13.30 + 38.20 195.00 13.70 + 38.25 194.00 13.90 + 38.30 191.00 13.50 + 38.35 178.00 13.30 + 38.40 184.00 13.30 + 38.45 186.00 13.50 + 38.50 202.00 13.80 + 38.55 200.00 14.00 + 38.60 210.00 14.00 + 38.65 198.00 13.90 + 38.70 225.00 14.50 + 38.75 209.00 14.30 + 38.80 229.00 14.60 + 38.85 197.00 13.90 + 38.90 220.00 14.30 + 38.95 215.00 14.40 + 39.00 242.00 15.00 + 39.05 340.00 18.10 + 39.10 441.00 20.20 + 39.15 654.00 25.10 + 39.20 962.00 29.70 + 39.25 1477.00 37.70 + 39.30 2012.00 43.00 + 39.35 2634.00 50.20 + 39.40 3115.00 53.40 + 39.45 3467.00 57.50 + 39.50 3532.00 56.70 + 39.55 3337.00 56.30 + 39.60 2595.00 48.60 + 39.65 1943.00 42.90 + 39.70 1251.00 33.70 + 39.75 828.00 28.00 + 39.80 525.00 21.80 + 39.85 377.00 18.80 + 39.90 294.00 16.30 + 39.95 233.00 14.80 + 40.00 233.00 14.50 + 40.05 253.00 15.40 + 40.10 253.00 15.10 + 40.15 213.00 14.10 + 40.20 196.00 13.20 + 40.25 222.00 14.40 + 40.30 172.00 12.40 + 40.35 218.00 14.30 + 40.40 206.00 13.60 + 40.45 195.00 13.60 + 40.50 209.00 13.70 + 40.55 192.00 13.50 + 40.60 197.00 13.30 + 40.65 188.00 13.30 + 40.70 202.00 13.50 + 40.75 208.00 14.00 + 40.80 184.00 12.90 + 40.85 177.00 13.00 + 40.90 202.00 13.50 + 40.95 198.00 13.80 + 41.00 203.00 13.60 + 41.05 193.00 13.60 + 41.10 188.00 13.10 + 41.15 211.00 14.20 + 41.20 189.00 13.10 + 41.25 200.00 13.90 + 41.30 198.00 13.50 + 41.35 203.00 14.00 + 41.40 197.00 13.40 + 41.45 190.00 13.60 + 41.50 212.00 14.00 + 41.55 185.00 13.40 + 41.60 228.00 14.50 + 41.65 167.00 12.80 + 41.70 207.00 13.90 + 41.75 187.00 13.60 + 41.80 190.00 13.30 + 41.85 192.00 13.80 + 41.90 185.00 13.20 + 41.95 161.00 12.70 + 42.00 187.00 13.30 + 42.05 191.00 13.80 + 42.10 159.00 12.30 + 42.15 170.00 13.10 + 42.20 182.00 13.20 + 42.25 186.00 13.70 + 42.30 192.00 13.60 + 42.35 178.00 13.50 + 42.40 186.00 13.40 + 42.45 180.00 13.50 + 42.50 178.00 13.10 + 42.55 182.00 13.60 + 42.60 179.00 13.20 + 42.65 203.00 14.50 + 42.70 191.00 13.70 + 42.75 207.00 14.60 + 42.80 183.00 13.40 + 42.85 180.00 13.60 + 42.90 191.00 13.70 + 42.95 187.00 13.90 + 43.00 184.00 13.50 + 43.05 182.00 13.80 + 43.10 178.00 13.30 + 43.15 169.00 13.30 + 43.20 158.00 12.60 + 43.25 180.00 13.70 + 43.30 174.00 13.20 + 43.35 184.00 14.00 + 43.40 178.00 13.40 + 43.45 180.00 13.80 + 43.50 144.00 12.00 + 43.55 169.00 13.40 + 43.60 177.00 13.30 + 43.65 156.00 12.80 + 43.70 148.00 12.20 + 43.75 159.00 12.90 + 43.80 195.00 14.00 + 43.85 186.00 14.00 + 43.90 180.00 13.40 + 43.95 192.00 14.10 + 44.00 186.00 13.50 + 44.05 180.00 13.60 + 44.10 174.00 13.10 + 44.15 181.00 13.60 + 44.20 178.00 13.20 + 44.25 189.00 13.80 + 44.30 206.00 14.10 + 44.35 183.00 13.60 + 44.40 161.00 12.40 + 44.45 170.00 13.00 + 44.50 203.00 13.90 + 44.55 168.00 12.90 + 44.60 199.00 13.70 + 44.65 192.00 13.70 + 44.70 192.00 13.40 + 44.75 200.00 14.00 + 44.80 206.00 13.90 + 44.85 193.00 13.70 + 44.90 188.00 13.20 + 44.95 200.00 13.90 + 45.00 193.00 13.40 + 45.05 203.00 14.00 + 45.10 212.00 14.00 + 45.15 197.00 13.80 + 45.20 219.00 14.20 + 45.25 219.00 14.60 + 45.30 226.00 14.50 + 45.35 282.00 16.50 + 45.40 353.00 18.10 + 45.45 469.00 21.30 + 45.50 741.00 26.20 + 45.55 1176.00 33.70 + 45.60 1577.00 38.10 + 45.65 2122.00 45.30 + 45.70 2726.00 50.10 + 45.75 2990.00 53.70 + 45.80 2991.00 52.50 + 45.85 2796.00 52.00 + 45.90 2372.00 46.80 + 45.95 1752.00 41.20 + 46.00 1209.00 33.40 + 46.05 824.00 28.30 + 46.10 512.00 21.80 + 46.15 353.00 18.60 + 46.20 273.00 15.90 + 46.25 259.00 15.90 + 46.30 233.00 14.80 + 46.35 220.00 14.70 + 46.40 228.00 14.60 + 46.45 231.00 15.10 + 46.50 218.00 14.30 + 46.55 210.00 14.40 + 46.60 212.00 14.20 + 46.65 187.00 13.60 + 46.70 207.00 14.00 + 46.75 212.00 14.50 + 46.80 188.00 13.40 + 46.85 178.00 13.30 + 46.90 186.00 13.30 + 46.95 192.00 13.80 + 47.00 192.00 13.50 + 47.05 186.00 13.60 + 47.10 208.00 14.10 + 47.15 199.00 14.10 + 47.20 165.00 12.50 + 47.25 212.00 14.50 + 47.30 191.00 13.50 + 47.35 185.00 13.60 + 47.40 171.00 12.70 + 47.45 176.00 13.20 + 47.50 179.00 13.00 + 47.55 187.00 13.60 + 47.60 181.00 13.10 + 47.65 173.00 13.10 + 47.70 167.00 12.50 + 47.75 182.00 13.40 + 47.80 171.00 12.70 + 47.85 185.00 13.50 + 47.90 177.00 12.90 + 47.95 154.00 12.40 + 48.00 200.00 13.70 + 48.05 177.00 13.30 + 48.10 184.00 13.20 + 48.15 166.00 12.80 + 48.20 181.00 13.10 + 48.25 208.00 14.40 + 48.30 186.00 13.20 + 48.35 164.00 12.70 + 48.40 196.00 13.60 + 48.45 169.00 12.90 + 48.50 173.00 12.70 + 48.55 200.00 14.10 + 48.60 163.00 12.40 + 48.65 173.00 13.10 + 48.70 187.00 13.30 + 48.75 177.00 13.30 + 48.80 200.00 13.80 + 48.85 171.00 13.00 + 48.90 192.00 13.50 + 48.95 178.00 13.30 + 49.00 169.00 12.70 + 49.05 160.00 12.70 + 49.10 182.00 13.20 + 49.15 173.00 13.20 + 49.20 170.00 12.80 + 49.25 181.00 13.60 + 49.30 170.00 12.90 + 49.35 164.00 13.00 + 49.40 166.00 12.70 + 49.45 174.00 13.40 + 49.50 173.00 13.10 + 49.55 137.00 11.90 + 49.60 166.00 12.80 + 49.65 194.00 14.20 + 49.70 160.00 12.60 + 49.75 152.00 12.50 + 49.80 180.00 13.30 + 49.85 160.00 12.90 + 49.90 149.00 12.20 + 49.95 172.00 13.40 + 50.00 170.00 13.00 + 50.05 175.00 13.50 + 50.10 162.00 12.70 + 50.15 168.00 13.20 + 50.20 186.00 13.60 + 50.25 179.00 13.60 + 50.30 165.00 12.70 + 50.35 155.00 12.60 + 50.40 170.00 12.90 + 50.45 162.00 12.80 + 50.50 157.00 12.30 + 50.55 173.00 13.20 + 50.60 149.00 12.00 + 50.65 167.00 13.00 + 50.70 165.00 12.60 + 50.75 157.00 12.50 + 50.80 177.00 13.00 + 50.85 187.00 13.60 + 50.90 155.00 12.10 + 50.95 194.00 13.70 + 51.00 147.00 11.70 + 51.05 169.00 12.80 + 51.10 166.00 12.40 + 51.15 193.00 13.60 + 51.20 168.00 12.40 + 51.25 188.00 13.40 + 51.30 182.00 12.80 + 51.35 180.00 13.10 + 51.40 177.00 12.70 + 51.45 188.00 13.30 + 51.50 187.00 13.00 + 51.55 178.00 12.90 + 51.60 177.00 12.60 + 51.65 184.00 13.10 + 51.70 172.00 12.40 + 51.75 188.00 13.30 + 51.80 194.00 13.20 + 51.85 179.00 12.90 + 51.90 176.00 12.50 + 51.95 180.00 12.90 + 52.00 169.00 12.20 + 52.05 178.00 12.90 + 52.10 165.00 12.10 + 52.15 149.00 11.70 + 52.20 168.00 12.20 + 52.25 157.00 12.10 + 52.30 151.00 11.60 + 52.35 181.00 13.00 + 52.40 172.00 12.40 + 52.45 178.00 12.90 + 52.50 179.00 12.60 + 52.55 171.00 12.60 + 52.60 129.00 10.70 + 52.65 180.00 13.00 + 52.70 154.00 11.70 + 52.75 182.00 13.10 + 52.80 166.00 12.20 + 52.85 156.00 12.10 + 52.90 164.00 12.10 + 52.95 166.00 12.50 + 53.00 176.00 12.50 + 53.05 182.00 13.10 + 53.10 173.00 12.50 + 53.15 160.00 12.30 + 53.20 169.00 12.30 + 53.25 162.00 12.30 + 53.30 164.00 12.10 + 53.35 165.00 12.40 + 53.40 177.00 12.60 + 53.45 173.00 12.80 + 53.50 158.00 11.90 + 53.55 164.00 12.40 + 53.60 175.00 12.50 + 53.65 166.00 12.50 + 53.70 161.00 12.00 + 53.75 167.00 12.50 + 53.80 136.00 11.00 + 53.85 167.00 12.50 + 53.90 152.00 11.70 + 53.95 159.00 12.20 + 54.00 172.00 12.40 + 54.05 179.00 12.90 + 54.10 169.00 12.20 + 54.15 165.00 12.40 + 54.20 166.00 12.10 + 54.25 162.00 12.30 + 54.30 175.00 12.40 + 54.35 162.00 12.30 + 54.40 145.00 11.40 + 54.45 148.00 11.70 + 54.50 157.00 11.80 + 54.55 176.00 12.80 + 54.60 162.00 12.00 + 54.65 153.00 12.00 + 54.70 178.00 12.60 + 54.75 147.00 11.80 + 54.80 146.00 11.50 + 54.85 170.00 12.70 + 54.90 155.00 11.80 + 54.95 170.00 12.70 + 55.00 142.00 11.30 + 55.05 154.00 12.10 + 55.10 150.00 11.70 + 55.15 145.00 11.80 + 55.20 151.00 11.80 + 55.25 162.00 12.50 + 55.30 153.00 11.90 + 55.35 170.00 12.90 + 55.40 153.00 11.90 + 55.45 156.00 12.40 + 55.50 163.00 12.40 + 55.55 149.00 12.20 + 55.60 135.00 11.30 + 55.65 158.00 12.60 + 55.70 144.00 11.70 + 55.75 152.00 12.40 + 55.80 165.00 12.70 + 55.85 164.00 13.00 + 55.90 175.00 13.10 + 55.95 150.00 12.40 + 56.00 168.00 12.90 + 56.05 159.00 12.90 + 56.10 187.00 13.60 + 56.15 170.00 13.30 + 56.20 159.00 12.60 + 56.25 148.00 12.50 + 56.30 159.00 12.60 + 56.35 174.00 13.50 + 56.40 195.00 14.00 + 56.45 219.00 15.10 + 56.50 216.00 14.70 + 56.55 271.00 16.80 + 56.60 337.00 18.30 + 56.65 417.00 20.80 + 56.70 390.00 19.70 + 56.75 414.00 20.70 + 56.80 388.00 19.60 + 56.85 317.00 18.10 + 56.90 307.00 17.40 + 56.95 250.00 16.00 + 57.00 205.00 14.20 + 57.05 167.00 13.00 + 57.10 179.00 13.20 + 57.15 159.00 12.70 + 57.20 170.00 12.80 + 57.25 168.00 13.00 + 57.30 180.00 13.10 + 57.35 144.00 12.00 + 57.40 178.00 13.00 + 57.45 203.00 14.20 + 57.50 159.00 12.30 + 57.55 165.00 12.80 + 57.60 164.00 12.40 + 57.65 135.00 11.60 + 57.70 157.00 12.20 + 57.75 162.00 12.70 + 57.80 175.00 12.90 + 57.85 161.00 12.60 + 57.90 174.00 12.80 + 57.95 187.00 13.70 + 58.00 164.00 12.50 + 58.05 188.00 13.70 + 58.10 163.00 12.40 + 58.15 177.00 13.30 + 58.20 181.00 13.10 + 58.25 156.00 12.50 + 58.30 163.00 12.40 + 58.35 190.00 13.80 + 58.40 162.00 12.40 + 58.45 186.00 13.70 + 58.50 169.00 12.70 + 58.55 160.00 12.70 + 58.60 171.00 12.80 + 58.65 160.00 12.60 + 58.70 174.00 12.90 + 58.75 163.00 12.70 + 58.80 180.00 13.10 + 58.85 176.00 13.20 + 58.90 174.00 12.80 + 58.95 177.00 13.30 + 59.00 186.00 13.30 + 59.05 157.00 12.40 + 59.10 188.00 13.30 + 59.15 162.00 12.60 + 59.20 160.00 12.20 + 59.25 196.00 13.90 + 59.30 178.00 12.90 + 59.35 188.00 13.50 + 59.40 161.00 12.30 + 59.45 157.00 12.30 + 59.50 183.00 13.00 + 59.55 169.00 12.80 + 59.60 150.00 11.80 + 59.65 195.00 13.70 + 59.70 175.00 12.70 + 59.75 160.00 12.40 + 59.80 168.00 12.40 + 59.85 191.00 13.50 + 59.90 181.00 12.80 + 59.95 168.00 12.70 + 60.00 181.00 12.80 + 60.05 158.00 12.20 + 60.10 160.00 12.00 + 60.15 151.00 12.00 + 60.20 171.00 12.40 + 60.25 167.00 12.60 + 60.30 160.00 12.00 + 60.35 157.00 12.10 + 60.40 172.00 12.40 + 60.45 140.00 11.50 + 60.50 172.00 12.40 + 60.55 150.00 11.90 + 60.60 179.00 12.70 + 60.65 153.00 12.00 + 60.70 170.00 12.40 + 60.75 184.00 13.10 + 60.80 158.00 11.90 + 60.85 177.00 12.90 + 60.90 159.00 12.00 + 60.95 157.00 12.20 + 61.00 168.00 12.30 + 61.05 154.00 12.00 + 61.10 170.00 12.40 + 61.15 147.00 11.80 + 61.20 161.00 12.10 + 61.25 175.00 12.90 + 61.30 170.00 12.40 + 61.35 153.00 12.10 + 61.40 165.00 12.30 + 61.45 164.00 12.50 + 61.50 174.00 12.60 + 61.55 160.00 12.40 + 61.60 188.00 13.20 + 61.65 182.00 13.30 + 61.70 197.00 13.50 + 61.75 163.00 12.60 + 61.80 176.00 12.80 + 61.85 157.00 12.40 + 61.90 166.00 12.40 + 61.95 173.00 13.10 + 62.00 167.00 12.50 + 62.05 175.00 13.20 + 62.10 143.00 11.60 + 62.15 148.00 12.10 + 62.20 178.00 13.00 + 62.25 180.00 13.40 + 62.30 141.00 11.60 + 62.35 202.00 14.30 + 62.40 172.00 12.80 + 62.45 169.00 13.00 + 62.50 143.00 11.80 + 62.55 146.00 12.20 + 62.60 169.00 12.80 + 62.65 146.00 12.30 + 62.70 156.00 12.30 + 62.75 147.00 12.30 + 62.80 158.00 12.40 + 62.85 178.00 13.50 + 62.90 163.00 12.60 + 62.95 168.00 13.10 + 63.00 164.00 12.60 + 63.05 180.00 13.60 + 63.10 189.00 13.60 + 63.15 164.00 12.90 + 63.20 181.00 13.20 + 63.25 179.00 13.50 + 63.30 147.00 11.90 + 63.35 179.00 13.50 + 63.40 150.00 12.00 + 63.45 168.00 12.90 + 63.50 156.00 12.20 + 63.55 181.00 13.40 + 63.60 170.00 12.70 + 63.65 181.00 13.30 + 63.70 184.00 13.10 + 63.75 153.00 12.20 + 63.80 166.00 12.40 + 63.85 166.00 12.60 + 63.90 169.00 12.50 + 63.95 175.00 12.90 + 64.00 157.00 12.00 + 64.05 165.00 12.40 + 64.10 169.00 12.30 + 64.15 164.00 12.40 + 64.20 181.00 12.80 + 64.25 189.00 13.30 + 64.30 179.00 12.60 + 64.35 157.00 12.10 + 64.40 189.00 13.00 + 64.45 167.00 12.50 + 64.50 178.00 12.50 + 64.55 144.00 11.60 + 64.60 180.00 12.60 + 64.65 182.00 12.90 + 64.70 199.00 13.20 + 64.75 172.00 12.60 + 64.80 191.00 12.90 + 64.85 166.00 12.30 + 64.90 157.00 11.70 + 64.95 197.00 13.50 + 65.00 204.00 13.40 + 65.05 183.00 13.00 + 65.10 189.00 12.90 + 65.15 189.00 13.20 + 65.20 170.00 12.20 + 65.25 188.00 13.20 + 65.30 176.00 12.40 + 65.35 172.00 12.60 + 65.40 182.00 12.70 + 65.45 205.00 13.80 + 65.50 191.00 13.00 + 65.55 192.00 13.30 + 65.60 190.00 12.90 + 65.65 194.00 13.40 + 65.70 212.00 13.70 + 65.75 221.00 14.30 + 65.80 227.00 14.20 + 65.85 227.00 14.60 + 65.90 239.00 14.60 + 65.95 261.00 15.60 + 66.00 301.00 16.40 + 66.05 409.00 19.60 + 66.10 559.00 22.30 + 66.15 820.00 27.80 + 66.20 1276.00 33.90 + 66.25 1776.00 41.00 + 66.30 2322.00 45.70 + 66.35 2880.00 52.20 + 66.40 3051.00 52.50 + 66.45 2980.00 53.10 + 66.50 2572.00 48.20 + 66.55 1961.00 43.20 + 66.60 1315.00 34.50 + 66.65 919.00 29.60 + 66.70 548.00 22.40 + 66.75 405.00 19.70 + 66.80 299.00 16.50 + 66.85 309.00 17.20 + 66.90 279.00 15.90 + 66.95 281.00 16.40 + 67.00 235.00 14.70 + 67.05 239.00 15.10 + 67.10 212.00 14.00 + 67.15 228.00 14.80 + 67.20 231.00 14.50 + 67.25 198.00 13.80 + 67.30 223.00 14.30 + 67.35 201.00 13.90 + 67.40 208.00 13.80 + 67.45 207.00 14.10 + 67.50 217.00 14.10 + 67.55 196.00 13.70 + 67.60 182.00 12.90 + 67.65 182.00 13.20 + 67.70 186.00 13.10 + 67.75 176.00 13.00 + 67.80 192.00 13.30 + 67.85 215.00 14.50 + 67.90 178.00 12.90 + 67.95 191.00 13.70 + 68.00 178.00 12.90 + 68.05 185.00 13.50 + 68.10 171.00 12.70 + 68.15 174.00 13.30 + 68.20 193.00 13.60 + 68.25 182.00 13.60 + 68.30 178.00 13.10 + 68.35 196.00 14.10 + 68.40 178.00 13.10 + 68.45 173.00 13.30 + 68.50 175.00 13.10 + 68.55 178.00 13.60 + 68.60 177.00 13.20 + 68.65 176.00 13.60 + 68.70 200.00 14.10 + 68.75 177.00 13.60 + 68.80 185.00 13.60 + 68.85 167.00 13.20 + 68.90 158.00 12.60 + 68.95 176.00 13.60 + 69.00 192.00 13.80 + 69.05 174.00 13.50 + 69.10 154.00 12.40 + 69.15 153.00 12.70 + 69.20 167.00 12.90 + 69.25 168.00 13.30 + 69.30 167.00 12.90 + 69.35 163.00 13.10 + 69.40 157.00 12.50 + 69.45 185.00 13.90 + 69.50 151.00 12.30 + 69.55 176.00 13.50 + 69.60 187.00 13.60 + 69.65 170.00 13.20 + 69.70 164.00 12.70 + 69.75 204.00 14.50 + 69.80 169.00 12.80 + 69.85 191.00 13.90 + 69.90 177.00 13.10 + 69.95 157.00 12.60 + 70.00 173.00 12.80 + 70.05 199.00 14.10 + 70.10 168.00 12.60 + 70.15 191.00 13.70 + 70.20 165.00 12.40 + 70.25 156.00 12.30 + 70.30 163.00 12.30 + 70.35 149.00 12.00 + 70.40 199.00 13.60 + 70.45 158.00 12.30 + 70.50 158.00 12.10 + 70.55 150.00 12.00 + 70.60 197.00 13.50 + 70.65 167.00 12.60 + 70.70 180.00 12.80 + 70.75 187.00 13.40 + 70.80 190.00 13.20 + 70.85 169.00 12.70 + 70.90 214.00 14.00 + 70.95 188.00 13.50 + 71.00 200.00 13.50 + 71.05 186.00 13.30 + 71.10 169.00 12.40 + 71.15 166.00 12.60 + 71.20 175.00 12.60 + 71.25 170.00 12.80 + 71.30 191.00 13.20 + 71.35 185.00 13.30 + 71.40 191.00 13.20 + 71.45 181.00 13.20 + 71.50 188.00 13.10 + 71.55 164.00 12.60 + 71.60 185.00 13.00 + 71.65 168.00 12.70 + 71.70 168.00 12.40 + 71.75 167.00 12.60 + 71.80 158.00 12.00 + 71.85 173.00 12.90 + 71.90 177.00 12.70 + 71.95 193.00 13.60 + 72.00 190.00 13.20 + 72.05 174.00 12.90 + 72.10 161.00 12.10 + 72.15 147.00 11.80 + 72.20 165.00 12.30 + 72.25 188.00 13.40 + 72.30 172.00 12.50 + 72.35 176.00 12.90 + 72.40 167.00 12.30 + 72.45 186.00 13.30 + 72.50 178.00 12.70 + 72.55 158.00 12.20 + 72.60 168.00 12.30 + 72.65 180.00 13.10 + 72.70 154.00 11.80 + 72.75 162.00 12.40 + 72.80 168.00 12.30 + 72.85 194.00 13.50 + 72.90 164.00 12.10 + 72.95 169.00 12.60 + 73.00 160.00 12.00 + 73.05 164.00 12.50 + 73.10 171.00 12.40 + 73.15 169.00 12.60 + 73.20 167.00 12.30 + 73.25 150.00 12.00 + 73.30 173.00 12.50 + 73.35 183.00 13.20 + 73.40 169.00 12.40 + 73.45 180.00 13.10 + 73.50 173.00 12.50 + 73.55 195.00 13.70 + 73.60 178.00 12.80 + 73.65 193.00 13.60 + 73.70 179.00 12.80 + 73.75 153.00 12.20 + 73.80 169.00 12.40 + 73.85 165.00 12.60 + 73.90 172.00 12.60 + 73.95 171.00 12.80 + 74.00 178.00 12.80 + 74.05 180.00 13.20 + 74.10 168.00 12.50 + 74.15 169.00 12.80 + 74.20 190.00 13.20 + 74.25 170.00 12.80 + 74.30 178.00 12.80 + 74.35 158.00 12.40 + 74.40 185.00 13.10 + 74.45 181.00 13.30 + 74.50 173.00 12.70 + 74.55 163.00 12.60 + 74.60 184.00 13.10 + 74.65 181.00 13.40 + 74.70 192.00 13.50 + 74.75 166.00 12.90 + 74.80 168.00 12.60 + 74.85 200.00 14.20 + 74.90 188.00 13.40 + 74.95 190.00 13.90 + 75.00 211.00 14.30 + 75.05 172.00 13.20 + 75.10 198.00 13.90 + 75.15 230.00 15.40 + 75.20 264.00 16.10 + 75.25 227.00 15.20 + 75.30 289.00 16.80 + 75.35 290.00 17.20 + 75.40 284.00 16.70 + 75.45 250.00 16.10 + 75.50 233.00 15.10 + 75.55 239.00 15.70 + 75.60 239.00 15.30 + 75.65 204.00 14.40 + 75.70 178.00 13.20 + 75.75 189.00 13.90 + 75.80 202.00 14.00 + 75.85 181.00 13.50 + 75.90 190.00 13.50 + 75.95 177.00 13.30 + 76.00 199.00 13.80 + 76.05 193.00 13.90 + 76.10 170.00 12.70 + 76.15 170.00 13.00 + 76.20 165.00 12.50 + 76.25 192.00 13.70 + 76.30 171.00 12.70 + 76.35 169.00 12.80 + 76.40 168.00 12.50 + 76.45 183.00 13.30 + 76.50 173.00 12.60 + 76.55 178.00 13.10 + 76.60 175.00 12.70 + 76.65 191.00 13.50 + 76.70 166.00 12.30 + 76.75 187.00 13.40 + 76.80 191.00 13.20 + 76.85 184.00 13.30 + 76.90 168.00 12.40 + 76.95 177.00 13.00 + 77.00 205.00 13.70 + 77.05 188.00 13.40 + 77.10 166.00 12.30 + 77.15 180.00 13.10 + 77.20 179.00 12.80 + 77.25 179.00 13.10 + 77.30 163.00 12.20 + 77.35 188.00 13.40 + 77.40 169.00 12.40 + 77.45 179.00 13.00 + 77.50 169.00 12.40 + 77.55 201.00 13.80 + 77.60 184.00 12.90 + 77.65 187.00 13.30 + 77.70 207.00 13.70 + 77.75 170.00 12.70 + 77.80 193.00 13.20 + 77.85 189.00 13.50 + 77.90 205.00 13.70 + 77.95 183.00 13.20 + 78.00 179.00 12.80 + 78.05 188.00 13.40 + 78.10 194.00 13.30 + 78.15 220.00 14.50 + 78.20 195.00 13.40 + 78.25 176.00 13.00 + 78.30 208.00 13.80 + 78.35 185.00 13.30 + 78.40 217.00 14.10 + 78.45 203.00 14.00 + 78.50 200.00 13.50 + 78.55 196.00 13.70 + 78.60 197.00 13.40 + 78.65 217.00 14.40 + 78.70 179.00 12.80 + 78.75 184.00 13.30 + 78.80 187.00 13.10 + 78.85 219.00 14.40 + 78.90 193.00 13.30 + 78.95 214.00 14.30 + 79.00 207.00 13.70 + 79.05 199.00 13.80 + 79.10 224.00 14.30 + 79.15 244.00 15.20 + 79.20 217.00 14.10 + 79.25 266.00 15.90 + 79.30 281.00 16.00 + 79.35 425.00 20.10 + 79.40 527.00 21.90 + 79.45 735.00 26.50 + 79.50 1057.00 31.10 + 79.55 1483.00 37.70 + 79.60 1955.00 42.20 + 79.65 2315.00 47.10 + 79.70 2552.00 48.30 + 79.75 2506.00 49.00 + 79.80 2261.00 45.50 + 79.85 1842.00 42.10 + 79.90 1328.00 34.90 + 79.95 911.00 29.60 + 80.00 592.00 23.40 + 80.05 430.00 20.40 + 80.10 312.00 17.00 + 80.15 284.00 16.60 + 80.20 285.00 16.20 + 80.25 247.00 15.50 + 80.30 250.00 15.20 + 80.35 231.00 15.00 + 80.40 272.00 15.90 + 80.45 235.00 15.20 + 80.50 188.00 13.20 + 80.55 223.00 14.80 + 80.60 218.00 14.30 + 80.65 221.00 14.80 + 80.70 210.00 14.10 + 80.75 199.00 14.00 + 80.80 207.00 14.00 + 80.85 208.00 14.40 + 80.90 178.00 13.00 + 80.95 194.00 14.00 + 81.00 202.00 13.90 + 81.05 226.00 15.10 + 81.10 209.00 14.20 + 81.15 194.00 14.10 + 81.20 179.00 13.20 + 81.25 183.00 13.70 + 81.30 187.00 13.50 + 81.35 198.00 14.30 + 81.40 198.00 14.00 + 81.45 209.00 14.70 + 81.50 187.00 13.60 + 81.55 211.00 14.90 + 81.60 198.00 14.10 + 81.65 164.00 13.10 + 81.70 200.00 14.10 + 81.75 212.00 14.90 + 81.80 197.00 14.00 + 81.85 191.00 14.20 + 81.90 195.00 14.00 + 81.95 217.00 15.10 + 82.00 189.00 13.80 + 82.05 182.00 13.80 + 82.10 174.00 13.20 + 82.15 182.00 13.80 + 82.20 199.00 14.00 + 82.25 179.00 13.60 + 82.30 197.00 13.90 + 82.35 228.00 15.30 + 82.40 170.00 12.90 + 82.45 203.00 14.40 + 82.50 232.00 15.10 + 82.55 178.00 13.50 + 82.60 216.00 14.50 + 82.65 205.00 14.30 + 82.70 185.00 13.30 + 82.75 212.00 14.60 + 82.80 199.00 13.70 + 82.85 169.00 12.90 + 82.90 165.00 12.50 + 82.95 203.00 14.10 + 83.00 215.00 14.20 + 83.05 199.00 13.90 + 83.10 200.00 13.60 + 83.15 174.00 12.90 + 83.20 192.00 13.30 + 83.25 206.00 14.10 + 83.30 191.00 13.20 + 83.35 203.00 13.90 + 83.40 210.00 13.90 + 83.45 194.00 13.60 + 83.50 245.00 14.90 + 83.55 242.00 15.10 + 83.60 255.00 15.20 + 83.65 310.00 17.10 + 83.70 408.00 19.20 + 83.75 498.00 21.70 + 83.80 729.00 25.60 + 83.85 934.00 29.60 + 83.90 1121.00 31.70 + 83.95 1320.00 35.20 + 84.00 1476.00 36.30 + 84.05 1276.00 34.60 + 84.10 1129.00 31.80 + 84.15 887.00 28.80 + 84.20 643.00 23.90 + 84.25 490.00 21.40 + 84.30 343.00 17.50 + 84.35 284.00 16.30 + 84.40 263.00 15.30 + 84.45 229.00 14.60 + 84.50 235.00 14.50 + 84.55 246.00 15.10 + 84.60 205.00 13.50 + 84.65 217.00 14.20 + 84.70 217.00 13.90 + 84.75 197.00 13.50 + 84.80 195.00 13.10 + 84.85 232.00 14.70 + 84.90 182.00 12.70 + 84.95 192.00 13.40 + 85.00 172.00 12.40 + 85.05 191.00 13.30 + 85.10 200.00 13.30 + 85.15 186.00 13.10 + 85.20 190.00 13.00 + 85.25 211.00 14.00 + 85.30 184.00 12.80 + 85.35 180.00 12.90 + 85.40 182.00 12.70 + 85.45 184.00 13.10 + 85.50 175.00 12.40 + 85.55 176.00 12.80 + 85.60 166.00 12.10 + 85.65 180.00 12.90 + 85.70 195.00 13.10 + 85.75 183.00 13.10 + 85.80 182.00 12.70 + 85.85 168.00 12.50 + 85.90 177.00 12.60 + 85.95 190.00 13.30 + 86.00 178.00 12.60 + 86.05 180.00 13.00 + 86.10 181.00 12.70 + 86.15 177.00 12.90 + 86.20 171.00 12.40 + 86.25 193.00 13.50 + 86.30 181.00 12.70 + 86.35 180.00 13.00 + 86.40 198.00 13.30 + 86.45 177.00 12.90 + 86.50 161.00 12.00 + 86.55 166.00 12.50 + 86.60 176.00 12.60 + 86.65 190.00 13.40 + 86.70 185.00 12.90 + 86.75 173.00 12.90 + 86.80 176.00 12.60 + 86.85 159.00 12.30 + 86.90 188.00 13.10 + 86.95 199.00 13.90 + 87.00 180.00 12.90 + 87.05 164.00 12.60 + 87.10 180.00 12.90 + 87.15 190.00 13.60 + 87.20 179.00 12.90 + 87.25 177.00 13.20 + 87.30 183.00 13.10 + 87.35 174.00 13.20 + 87.40 164.00 12.50 + 87.45 165.00 12.90 + 87.50 185.00 13.30 + 87.55 191.00 13.90 + 87.60 181.00 13.20 + 87.65 143.00 12.10 + 87.70 170.00 12.90 + 87.75 150.00 12.40 + 87.80 187.00 13.50 + 87.85 181.00 13.60 + 87.90 171.00 12.90 + 87.95 179.00 13.60 + 88.00 146.00 12.00 + 88.05 175.00 13.40 + 88.10 182.00 13.40 + 88.15 176.00 13.50 + 88.20 164.00 12.70 + 88.25 152.00 12.60 + 88.30 188.00 13.60 + 88.35 152.00 12.50 + 88.40 172.00 13.00 + 88.45 140.00 12.00 + 88.50 176.00 13.10 + 88.55 168.00 13.10 + 88.60 197.00 13.80 + 88.65 190.00 13.90 + 88.70 176.00 13.10 + 88.75 167.00 13.00 + 88.80 182.00 13.30 + 88.85 175.00 13.20 + 88.90 154.00 12.10 + 88.95 168.00 12.90 + 89.00 187.00 13.30 + 89.05 163.00 12.70 + 89.10 173.00 12.80 + 89.15 161.00 12.50 + 89.20 170.00 12.60 + 89.25 178.00 13.10 + 89.30 174.00 12.70 + 89.35 172.00 12.80 + 89.40 167.00 12.40 + 89.45 168.00 12.60 + 89.50 164.00 12.20 + 89.55 183.00 13.10 + 89.60 141.00 11.30 + 89.65 173.00 12.80 + 89.70 190.00 13.10 + 89.75 180.00 13.00 + 89.80 162.00 12.10 + 89.85 166.00 12.50 + 89.90 164.00 12.10 + 89.95 166.00 12.50 + 90.00 170.00 12.40 + 90.05 176.00 12.90 + 90.10 181.00 12.80 + 90.15 175.00 12.90 + 90.20 161.00 12.10 + 90.25 170.00 12.70 + 90.30 166.00 12.30 + 90.35 175.00 12.90 + 90.40 171.00 12.50 + 90.45 172.00 12.80 + 90.50 183.00 12.90 + 90.55 165.00 12.50 + 90.60 181.00 12.80 + 90.65 168.00 12.70 + 90.70 179.00 12.70 + 90.75 157.00 12.20 + 90.80 172.00 12.50 + 90.85 187.00 13.30 + 90.90 181.00 12.80 + 90.95 163.00 12.40 + 91.00 163.00 12.10 + 91.05 166.00 12.50 + 91.10 161.00 12.00 + 91.15 167.00 12.50 + 91.20 148.00 11.50 + 91.25 175.00 12.80 + 91.30 195.00 13.20 + 91.35 181.00 13.00 + 91.40 173.00 12.50 + 91.45 160.00 12.30 + 91.50 180.00 12.70 + 91.55 183.00 13.10 + 91.60 156.00 11.90 + 91.65 163.00 12.40 + 91.70 175.00 12.50 + 91.75 189.00 13.30 + 91.80 181.00 12.70 + 91.85 186.00 13.20 + 91.90 184.00 12.80 + 91.95 187.00 13.20 + 92.00 191.00 13.10 + 92.05 203.00 13.70 + 92.10 194.00 13.10 + 92.15 237.00 14.80 + 92.20 242.00 14.60 + 92.25 307.00 16.90 + 92.30 299.00 16.30 + 92.35 340.00 17.70 + 92.40 357.00 17.70 + 92.45 354.00 18.10 + 92.50 370.00 18.00 + 92.55 375.00 18.60 + 92.60 303.00 16.30 + 92.65 264.00 15.60 + 92.70 243.00 14.60 + 92.75 207.00 13.90 + 92.80 199.00 13.20 + 92.85 180.00 12.90 + 92.90 202.00 13.30 + 92.95 188.00 13.20 + 93.00 183.00 12.70 + 93.05 170.00 12.60 + 93.10 180.00 12.60 + 93.15 182.00 13.10 + 93.20 186.00 12.90 + 93.25 196.00 13.60 + 93.30 177.00 12.60 + 93.35 198.00 13.70 + 93.40 182.00 12.80 + 93.45 183.00 13.20 + 93.50 184.00 12.90 + 93.55 181.00 13.20 + 93.60 190.00 13.20 + 93.65 176.00 13.10 + 93.70 197.00 13.50 + 93.75 174.00 13.10 + 93.80 159.00 12.20 + 93.85 171.00 13.00 + 93.90 159.00 12.20 + 93.95 170.00 13.00 + 94.00 172.00 12.70 + 94.05 159.00 12.60 + 94.10 160.00 12.30 + 94.15 173.00 13.20 + 94.20 147.00 11.90 + 94.25 143.00 12.00 + 94.30 150.00 12.00 + 94.35 155.00 12.50 + 94.40 160.00 12.40 + 94.45 155.00 12.60 + 94.50 176.00 13.00 + 94.55 198.00 14.20 + 94.60 179.00 13.20 + 94.65 161.00 12.80 + 94.70 175.00 13.10 + 94.75 157.00 12.70 + 94.80 173.00 13.00 + 94.85 168.00 13.10 + 94.90 171.00 12.90 + 94.95 173.00 13.20 + 95.00 183.00 13.30 + 95.05 148.00 12.20 + 95.10 160.00 12.40 + 95.15 171.00 13.10 + 95.20 167.00 12.60 + 95.25 195.00 13.90 + 95.30 175.00 12.90 + 95.35 200.00 14.10 + 95.40 176.00 12.90 + 95.45 175.00 13.10 + 95.50 194.00 13.50 + 95.55 190.00 13.60 + 95.60 154.00 12.00 + 95.65 166.00 12.70 + 95.70 164.00 12.30 + 95.75 166.00 12.60 + 95.80 162.00 12.20 + 95.85 183.00 13.20 + 95.90 149.00 11.60 + 95.95 171.00 12.80 + 96.00 165.00 12.30 + 96.05 181.00 13.10 + 96.10 188.00 13.00 + 96.15 184.00 13.20 + 96.20 162.00 12.10 + 96.25 163.00 12.40 + 96.30 165.00 12.20 + 96.35 183.00 13.10 + 96.40 182.00 12.80 + 96.45 156.00 12.10 + 96.50 159.00 11.90 + 96.55 139.00 11.40 + 96.60 165.00 12.10 + 96.65 164.00 12.40 + 96.70 184.00 12.80 + 96.75 159.00 12.10 + 96.80 159.00 11.90 + 96.85 155.00 12.00 + 96.90 162.00 12.00 + 96.95 157.00 12.00 + 97.00 160.00 11.90 + 97.05 168.00 12.50 + 97.10 168.00 12.20 + 97.15 151.00 11.80 + 97.20 162.00 11.90 + 97.25 163.00 12.20 + 97.30 166.00 12.10 + 97.35 161.00 12.20 + 97.40 158.00 11.80 + 97.45 151.00 11.80 + 97.50 163.00 12.00 + 97.55 179.00 12.80 + 97.60 166.00 12.10 + 97.65 155.00 11.90 + 97.70 160.00 11.80 + 97.75 152.00 11.80 + 97.80 184.00 12.70 + 97.85 175.00 12.60 + 97.90 161.00 11.80 + 97.95 166.00 12.30 + 98.00 150.00 11.40 + 98.05 179.00 12.80 + 98.10 184.00 12.70 + 98.15 151.00 11.80 + 98.20 173.00 12.30 + 98.25 164.00 12.30 + 98.30 178.00 12.50 + 98.35 176.00 12.80 + 98.40 162.00 11.90 + 98.45 173.00 12.70 + 98.50 154.00 11.60 + 98.55 184.00 13.10 + 98.60 142.00 11.20 + 98.65 184.00 13.00 + 98.70 156.00 11.70 + 98.75 177.00 12.80 + 98.80 163.00 12.00 + 98.85 173.00 12.70 + 98.90 180.00 12.70 + 98.95 181.00 13.00 + 99.00 165.00 12.10 + 99.05 177.00 12.90 + 99.10 155.00 11.80 + 99.15 147.00 11.70 + 99.20 163.00 12.10 + 99.25 172.00 12.70 + 99.30 145.00 11.40 + 99.35 156.00 12.10 + 99.40 161.00 12.00 + 99.45 189.00 13.50 + 99.50 182.00 12.90 + 99.55 172.00 12.80 + 99.60 176.00 12.70 + 99.65 166.00 12.60 + 99.70 190.00 13.20 + 99.75 154.00 12.20 + 99.80 198.00 13.50 + 99.85 152.00 12.20 + 99.90 160.00 12.20 + 99.95 174.00 13.00 + 100.00 187.00 13.20 + 100.05 178.00 13.20 + 100.10 149.00 11.80 + 100.15 171.00 13.00 + 100.20 185.00 13.20 + 100.25 207.00 14.40 + 100.30 184.00 13.20 + 100.35 187.00 13.70 + 100.40 231.00 14.90 + 100.45 226.00 15.10 + 100.50 203.00 14.00 + 100.55 214.00 14.80 + 100.60 279.00 16.50 + 100.65 319.00 18.10 + 100.70 397.00 19.70 + 100.75 435.00 21.20 + 100.80 539.00 23.00 + 100.85 665.00 26.30 + 100.90 724.00 26.80 + 100.95 723.00 27.50 + 101.00 783.00 27.90 + 101.05 719.00 27.50 + 101.10 585.00 24.20 + 101.15 465.00 22.10 + 101.20 371.00 19.30 + 101.25 328.00 18.50 + 101.30 277.00 16.70 + 101.35 248.00 16.10 + 101.40 209.00 14.40 + 101.45 221.00 15.10 + 101.50 198.00 14.00 + 101.55 203.00 14.50 + 101.60 188.00 13.60 + 101.65 207.00 14.50 + 101.70 195.00 13.80 + 101.75 170.00 13.10 + 101.80 192.00 13.60 + 101.85 172.00 13.10 + 101.90 185.00 13.30 + 101.95 183.00 13.40 + 102.00 211.00 14.10 + 102.05 147.00 12.00 + 102.10 176.00 12.80 + 102.15 186.00 13.40 + 102.20 171.00 12.60 + 102.25 169.00 12.70 + 102.30 192.00 13.20 + 102.35 215.00 14.30 + 102.40 146.00 11.50 + 102.45 169.00 12.60 + 102.50 188.00 13.10 + 102.55 175.00 12.80 + 102.60 165.00 12.20 + 102.65 184.00 13.10 + 102.70 172.00 12.40 + 102.75 179.00 13.00 + 102.80 163.00 12.10 + 102.85 167.00 12.50 + 102.90 179.00 12.70 + 102.95 171.00 12.70 + 103.00 181.00 12.70 + 103.05 171.00 12.70 + 103.10 180.00 12.70 + 103.15 173.00 12.80 + 103.20 167.00 12.20 + 103.25 186.00 13.20 + 103.30 176.00 12.50 + 103.35 191.00 13.40 + 103.40 170.00 12.30 + 103.45 167.00 12.50 + 103.50 165.00 12.10 + 103.55 182.00 13.00 + 103.60 173.00 12.40 + 103.65 186.00 13.20 + 103.70 161.00 12.00 + 103.75 166.00 12.40 + 103.80 157.00 11.80 + 103.85 170.00 12.50 + 103.90 183.00 12.70 + 103.95 179.00 12.90 + 104.00 164.00 12.00 + 104.05 169.00 12.50 + 104.10 161.00 11.90 + 104.15 156.00 12.00 + 104.20 163.00 12.00 + 104.25 174.00 12.70 + 104.30 161.00 11.90 + 104.35 169.00 12.50 + 104.40 158.00 11.80 + 104.45 180.00 12.90 + 104.50 171.00 12.30 + 104.55 165.00 12.30 + 104.60 163.00 12.00 + 104.65 172.00 12.60 + 104.70 164.00 12.00 + 104.75 174.00 12.60 + 104.80 178.00 12.50 + 104.85 154.00 11.90 + 104.90 176.00 12.40 + 104.95 142.00 11.40 + 105.00 163.00 12.00 + 105.05 177.00 12.80 + 105.10 194.00 13.00 + 105.15 176.00 12.70 + 105.20 207.00 13.50 + 105.25 158.00 12.10 + 105.30 151.00 11.50 + 105.35 183.00 13.00 + 105.40 159.00 11.80 + 105.45 179.00 12.90 + 105.50 170.00 12.20 + 105.55 192.00 13.30 + 105.60 160.00 11.90 + 105.65 168.00 12.40 + 105.70 183.00 12.70 + 105.75 163.00 12.30 + 105.80 162.00 11.90 + 105.85 182.00 12.90 + 105.90 154.00 11.60 + 105.95 180.00 12.90 + 106.00 168.00 12.20 + 106.05 166.00 12.40 + 106.10 155.00 11.70 + 106.15 190.00 13.30 + 106.20 165.00 12.10 + 106.25 163.00 12.30 + 106.30 183.00 12.80 + 106.35 165.00 12.50 + 106.40 173.00 12.50 + 106.45 163.00 12.50 + 106.50 151.00 11.70 + 106.55 198.00 13.80 + 106.60 165.00 12.20 + 106.65 157.00 12.30 + 106.70 159.00 12.10 + 106.75 177.00 13.10 + 106.80 156.00 12.00 + 106.85 182.00 13.40 + 106.90 181.00 13.00 + 106.95 158.00 12.50 + 107.00 176.00 12.80 + 107.05 163.00 12.70 + 107.10 156.00 12.10 + 107.15 213.00 14.60 + 107.20 172.00 12.80 + 107.25 170.00 13.00 + 107.30 168.00 12.60 + 107.35 169.00 13.00 + 107.40 169.00 12.70 + 107.45 168.00 13.00 + 107.50 155.00 12.10 + 107.55 164.00 12.80 + 107.60 168.00 12.70 + 107.65 144.00 12.00 + 107.70 166.00 12.60 + 107.75 172.00 13.10 + 107.80 156.00 12.20 + 107.85 154.00 12.40 + 107.90 143.00 11.60 + 107.95 152.00 12.30 + 108.00 174.00 12.80 + 108.05 168.00 12.80 + 108.10 164.00 12.40 + 108.15 160.00 12.50 + 108.20 176.00 12.80 + 108.25 174.00 13.00 + 108.30 175.00 12.70 + 108.35 163.00 12.60 + 108.40 169.00 12.50 + 108.45 180.00 13.10 + 108.50 159.00 12.00 + 108.55 173.00 12.80 + 108.60 148.00 11.60 + 108.65 169.00 12.60 + 108.70 167.00 12.30 + 108.75 168.00 12.50 + 108.80 175.00 12.50 + 108.85 163.00 12.30 + 108.90 164.00 12.10 + 108.95 189.00 13.30 + 109.00 192.00 13.10 + 109.05 181.00 13.00 + 109.10 202.00 13.40 + 109.15 190.00 13.30 + 109.20 163.00 12.00 + 109.25 216.00 14.10 + 109.30 220.00 14.00 + 109.35 230.00 14.60 + 109.40 255.00 15.00 + 109.45 253.00 15.30 + 109.50 273.00 15.50 + 109.55 296.00 16.50 + 109.60 300.00 16.30 + 109.65 331.00 17.50 + 109.70 347.00 17.50 + 109.75 349.00 18.00 + 109.80 341.00 17.40 + 109.85 332.00 17.50 + 109.90 298.00 16.20 + 109.95 259.00 15.50 + 110.00 227.00 14.10 + 110.05 203.00 13.70 + 110.10 222.00 14.00 + 110.15 175.00 12.70 + 110.20 183.00 12.70 + 110.25 197.00 13.50 + 110.30 176.00 12.40 + 110.35 179.00 12.90 + 110.40 176.00 12.50 + 110.45 178.00 12.80 + 110.50 210.00 13.60 + 110.55 181.00 13.00 + 110.60 167.00 12.20 + 110.65 165.00 12.40 + 110.70 172.00 12.30 + 110.75 175.00 12.80 + 110.80 177.00 12.50 + 110.85 194.00 13.40 + 110.90 171.00 12.30 + 110.95 177.00 12.80 + 111.00 188.00 12.90 + 111.05 175.00 12.80 + 111.10 194.00 13.10 + 111.15 179.00 12.90 + 111.20 171.00 12.30 + 111.25 165.00 12.40 + 111.30 183.00 12.70 + 111.35 184.00 13.00 + 111.40 187.00 12.90 + 111.45 178.00 12.80 + 111.50 172.00 12.30 + 111.55 179.00 12.90 + 111.60 205.00 13.40 + 111.65 168.00 12.50 + 111.70 161.00 11.90 + 111.75 182.00 13.00 + 111.80 167.00 12.20 + 111.85 193.00 13.40 + 111.90 188.00 12.90 + 111.95 204.00 13.80 + 112.00 179.00 12.60 + 112.05 176.00 12.80 + 112.10 185.00 12.80 + 112.15 174.00 12.70 + 112.20 175.00 12.50 + 112.25 198.00 13.60 + 112.30 199.00 13.30 + 112.35 207.00 13.90 + 112.40 204.00 13.50 + 112.45 180.00 13.00 + 112.50 137.00 11.10 + 112.55 179.00 13.00 + 112.60 183.00 12.80 + 112.65 166.00 12.60 + 112.70 166.00 12.30 + 112.75 189.00 13.40 + 112.80 181.00 12.80 + 112.85 194.00 13.60 + 112.90 171.00 12.50 + 112.95 202.00 13.90 + 113.00 216.00 14.10 + 113.05 198.00 14.00 + 113.10 189.00 13.30 + 113.15 170.00 13.00 + 113.20 182.00 13.10 + 113.25 195.00 14.00 + 113.30 177.00 13.00 + 113.35 180.00 13.50 + 113.40 195.00 13.70 + 113.45 201.00 14.30 + 113.50 203.00 14.00 + 113.55 200.00 14.30 + 113.60 209.00 14.20 + 113.65 231.00 15.40 + 113.70 281.00 16.60 + 113.75 287.00 17.20 + 113.80 324.00 17.80 + 113.85 395.00 20.20 + 113.90 457.00 21.20 + 113.95 580.00 24.40 + 114.00 685.00 26.00 + 114.05 873.00 30.00 + 114.10 964.00 30.80 + 114.15 1126.00 34.00 + 114.20 1266.00 35.20 + 114.25 1307.00 36.50 + 114.30 1221.00 34.50 + 114.35 1096.00 33.30 + 114.40 978.00 30.70 + 114.45 792.00 28.20 + 114.50 600.00 24.00 + 114.55 487.00 22.00 + 114.60 358.00 18.50 + 114.65 279.00 16.60 + 114.70 265.00 15.80 + 114.75 258.00 15.90 + 114.80 244.00 15.10 + 114.85 226.00 14.80 + 114.90 227.00 14.50 + 114.95 188.00 13.50 + 115.00 195.00 13.40 + 115.05 211.00 14.20 + 115.10 205.00 13.70 + 115.15 198.00 13.70 + 115.20 218.00 14.00 + 115.25 200.00 13.70 + 115.30 200.00 13.40 + 115.35 188.00 13.30 + 115.40 209.00 13.70 + 115.45 184.00 13.10 + 115.50 186.00 12.90 + 115.55 202.00 13.70 + 115.60 183.00 12.70 + 115.65 187.00 13.10 + 115.70 182.00 12.60 + 115.75 185.00 13.10 + 115.80 213.00 13.70 + 115.85 177.00 12.80 + 115.90 199.00 13.20 + 115.95 185.00 13.00 + 116.00 184.00 12.70 + 116.05 191.00 13.30 + 116.10 173.00 12.30 + 116.15 196.00 13.50 + 116.20 201.00 13.30 + 116.25 173.00 12.70 + 116.30 178.00 12.60 + 116.35 161.00 12.30 + 116.40 208.00 13.60 + 116.45 183.00 13.10 + 116.50 183.00 12.80 + 116.55 173.00 12.80 + 116.60 184.00 12.80 + 116.65 215.00 14.20 + 116.70 201.00 13.40 + 116.75 193.00 13.40 + 116.80 190.00 13.00 + 116.85 216.00 14.20 + 116.90 195.00 13.10 + 116.95 203.00 13.80 + 117.00 183.00 12.80 + 117.05 203.00 13.70 + 117.10 187.00 12.90 + 117.15 216.00 14.20 + 117.20 191.00 13.00 + 117.25 189.00 13.30 + 117.30 189.00 13.00 + 117.35 226.00 14.50 + 117.40 185.00 12.90 + 117.45 194.00 13.50 + 117.50 185.00 12.80 + 117.55 213.00 14.10 + 117.60 197.00 13.30 + 117.65 198.00 14.50 + 117.70 168.00 13.00 + 117.75 209.00 14.90 + 117.80 185.00 13.70 + 117.85 208.00 14.90 + 117.90 213.00 14.70 + 117.95 203.00 14.70 + 118.00 225.00 15.10 + 118.05 214.00 15.10 + 118.10 233.00 15.40 + 118.15 245.00 16.20 + 118.20 236.00 15.50 + 118.25 245.00 16.20 + 118.30 305.00 17.60 + 118.35 287.00 17.10 + 118.40 317.00 17.40 + 118.45 421.00 20.60 + 118.50 422.00 20.10 + 118.55 590.00 24.40 + 118.60 701.00 26.80 + 118.65 861.00 28.60 + 118.70 1054.00 31.00 + 118.75 1232.00 34.30 + 118.80 1483.00 36.80 + 118.85 1694.00 40.30 + 118.90 1819.00 40.80 + 118.95 1845.00 42.30 + 119.00 1866.00 41.50 + 119.05 1726.00 41.00 + 119.10 1492.00 37.20 + 119.15 1232.00 34.80 + 119.20 971.00 30.10 + 119.25 753.00 27.20 + 119.30 626.00 24.20 + 119.35 487.00 21.90 + 119.40 409.00 19.60 + 119.45 342.00 18.50 + 119.50 307.00 17.10 + 119.55 296.00 17.20 + 119.60 231.00 14.90 + 119.65 246.00 15.80 + 119.70 220.00 14.50 + 119.75 255.00 16.10 + 119.80 214.00 14.40 + 119.85 247.00 15.90 + 119.90 238.00 15.20 + 119.95 218.00 15.00 + 120.00 222.00 14.70 + 120.05 218.00 15.00 + 120.10 253.00 15.80 + 120.15 197.00 14.30 + 120.20 190.00 13.60 + 120.25 221.00 15.10 + 120.30 204.00 14.20 + 120.35 206.00 14.60 + 120.40 189.00 13.60 + 120.45 231.00 15.40 + 120.50 190.00 13.60 + 120.55 191.00 13.90 + 120.60 211.00 14.30 + 120.65 204.00 14.30 + 120.70 200.00 13.90 + 120.75 199.00 14.10 + 120.80 190.00 13.50 + 120.85 195.00 13.90 + 120.90 179.00 13.00 + 120.95 189.00 13.60 + 121.00 190.00 13.30 + 121.05 195.00 13.80 + 121.10 193.00 13.40 + 121.15 173.00 12.80 + 121.20 183.00 13.00 + 121.25 181.00 13.10 + 121.30 203.00 13.50 + 121.35 177.00 12.90 + 121.40 201.00 13.40 + 121.45 179.00 12.90 + 121.50 179.00 12.60 + 121.55 194.00 13.40 + 121.60 158.00 11.90 + 121.65 195.00 13.40 + 121.70 201.00 13.40 + 121.75 192.00 13.40 + 121.80 189.00 13.00 + 121.85 186.00 13.10 + 121.90 170.00 12.30 + 121.95 166.00 12.40 + 122.00 185.00 12.80 + 122.05 197.00 13.60 + 122.10 177.00 12.60 + 122.15 198.00 13.60 + 122.20 174.00 12.50 + 122.25 171.00 12.60 + 122.30 190.00 13.00 + 122.35 214.00 14.20 + 122.40 189.00 13.00 + 122.45 174.00 12.80 + 122.50 171.00 12.40 + 122.55 163.00 12.40 + 122.60 174.00 12.40 + 122.65 177.00 12.80 + 122.70 180.00 12.60 + 122.75 186.00 13.10 + 122.80 190.00 13.00 + 122.85 170.00 12.60 + 122.90 175.00 12.50 + 122.95 194.00 13.40 + 123.00 175.00 12.50 + 123.05 194.00 13.40 + 123.10 189.00 12.90 + 123.15 222.00 14.30 + 123.20 178.00 12.50 + 123.25 158.00 12.10 + 123.30 191.00 13.00 + 123.35 184.00 13.00 + 123.40 190.00 12.90 + 123.45 183.00 13.00 + 123.50 178.00 12.50 + 123.55 204.00 13.70 + 123.60 192.00 13.00 + 123.65 200.00 13.50 + 123.70 182.00 12.60 + 123.75 171.00 12.50 + 123.80 186.00 12.70 + 123.85 197.00 13.40 + 123.90 174.00 12.30 + 123.95 167.00 12.30 + 124.00 178.00 12.40 + 124.05 198.00 13.40 + 124.10 205.00 13.30 + 124.15 216.00 14.00 + 124.20 200.00 13.20 + 124.25 204.00 13.60 + 124.30 190.00 12.80 + 124.35 188.00 13.10 + 124.40 191.00 12.90 + 124.45 186.00 13.00 + 124.50 175.00 12.30 + 124.55 175.00 12.60 + 124.60 174.00 12.30 + 124.65 194.00 13.30 + 124.70 181.00 12.50 + 124.75 161.00 12.10 + 124.80 186.00 12.70 + 124.85 200.00 13.50 + 124.90 168.00 12.10 + 124.95 177.00 12.70 + 125.00 188.00 12.80 + 125.05 177.00 12.70 + 125.10 163.00 11.90 + 125.15 175.00 12.70 + 125.20 188.00 12.80 + 125.25 176.00 12.80 + 125.30 172.00 12.30 + 125.35 172.00 12.60 + 125.40 181.00 12.70 + 125.45 186.00 13.20 + 125.50 181.00 12.70 + 125.55 193.00 13.40 + 125.60 177.00 12.60 + 125.65 176.00 12.90 + 125.70 194.00 13.20 + 125.75 179.00 13.00 + 125.80 147.00 11.50 + 125.85 186.00 13.30 + 125.90 182.00 12.90 + 125.95 165.00 12.70 + 126.00 164.00 12.30 + 126.05 199.00 13.90 + 126.10 167.00 12.40 + 126.15 184.00 13.40 + 126.20 203.00 13.80 + 126.25 190.00 13.70 + 126.30 182.00 13.10 + 126.35 180.00 13.40 + 126.40 179.00 13.00 + 126.45 179.00 13.40 + 126.50 170.00 12.70 + 126.55 176.00 13.30 + 126.60 178.00 13.10 + 126.65 185.00 13.70 + 126.70 193.00 13.60 + 126.75 192.00 14.00 + 126.80 198.00 13.80 + 126.85 195.00 14.00 + 126.90 165.00 12.60 + 126.95 189.00 13.80 + 127.00 175.00 13.00 + 127.05 176.00 13.30 + 127.10 184.00 13.30 + 127.15 179.00 13.40 + 127.20 187.00 13.40 + 127.25 176.00 13.20 + 127.30 191.00 13.50 + 127.35 194.00 13.90 + 127.40 177.00 12.90 + 127.45 177.00 13.20 + 127.50 180.00 13.00 + 127.55 158.00 12.40 + 127.60 193.00 13.40 + 127.65 177.00 13.10 + 127.70 185.00 13.10 + 127.75 178.00 13.10 + 127.80 184.00 13.00 + 127.85 188.00 13.40 + 127.90 182.00 12.90 + 127.95 190.00 13.50 + 128.00 191.00 13.20 + 128.05 165.00 12.50 + 128.10 174.00 12.50 + 128.15 158.00 12.20 + 128.20 197.00 13.30 + 128.25 183.00 13.10 + 128.30 196.00 13.30 + 128.35 166.00 12.50 + 128.40 218.00 14.00 + 128.45 206.00 13.80 + 128.50 184.00 12.80 + 128.55 176.00 12.70 + 128.60 198.00 13.20 + 128.65 215.00 14.10 + 128.70 179.00 12.60 + 128.75 192.00 13.30 + 128.80 201.00 13.30 + 128.85 221.00 14.20 + 128.90 227.00 14.10 + 128.95 229.00 14.40 + 129.00 254.00 14.90 + 129.05 256.00 15.30 + 129.10 272.00 15.40 + 129.15 239.00 14.80 + 129.20 228.00 14.10 + 129.25 255.00 15.20 + 129.30 213.00 13.60 + 129.35 203.00 13.60 + 129.40 228.00 14.10 + 129.45 220.00 14.10 + 129.50 185.00 12.60 + 129.55 192.00 13.20 + 129.60 187.00 12.70 + 129.65 182.00 12.80 + 129.70 209.00 13.40 + 129.75 173.00 12.50 + 129.80 202.00 13.20 + 129.85 178.00 12.70 + 129.90 189.00 12.80 + 129.95 177.00 12.60 + 130.00 177.00 12.30 + 130.05 190.00 13.10 + 130.10 178.00 12.40 + 130.15 177.00 12.60 + 130.20 164.00 11.90 + 130.25 185.00 12.90 + 130.30 153.00 11.40 + 130.35 174.00 12.50 + 130.40 197.00 13.00 + 130.45 192.00 13.10 + 130.50 174.00 12.20 + 130.55 177.00 12.60 + 130.60 172.00 12.10 + 130.65 173.00 12.50 + 130.70 178.00 12.40 + 130.75 180.00 12.80 + 130.80 203.00 13.20 + 130.85 192.00 13.20 + 130.90 184.00 12.60 + 130.95 197.00 13.30 + 131.00 169.00 12.10 + 131.05 187.00 13.00 + 131.10 175.00 12.30 + 131.15 177.00 12.60 + 131.20 199.00 13.10 + 131.25 180.00 12.80 + 131.30 203.00 13.20 + 131.35 175.00 12.60 + 131.40 183.00 12.50 + 131.45 192.00 13.20 + 131.50 174.00 12.30 + 131.55 180.00 12.80 + 131.60 179.00 12.50 + 131.65 191.00 13.20 + 131.70 182.00 12.60 + 131.75 174.00 12.60 + 131.80 191.00 12.90 + 131.85 195.00 13.40 + 131.90 171.00 12.30 + 131.95 198.00 13.60 + 132.00 193.00 13.10 + 132.05 175.00 12.80 + 132.10 207.00 13.60 + 132.15 189.00 13.40 + 132.20 174.00 12.50 + 132.25 196.00 13.70 + 132.30 175.00 12.60 + 132.35 196.00 13.80 + 132.40 183.00 13.00 + 132.45 198.00 13.80 + 132.50 196.00 13.40 + 132.55 169.00 12.90 + 132.60 189.00 13.30 + 132.65 171.00 13.00 + 132.70 193.00 13.50 + 132.75 170.00 13.00 + 132.80 175.00 12.90 + 132.85 166.00 12.90 + 132.90 188.00 13.40 + 132.95 186.00 13.70 + 133.00 165.00 12.60 + 133.05 201.00 14.20 + 133.10 182.00 13.20 + 133.15 151.00 12.40 + 133.20 156.00 12.20 + 133.25 187.00 13.70 + 133.30 153.00 12.10 + 133.35 193.00 14.00 + 133.40 200.00 13.90 + 133.45 165.00 12.90 + 133.50 172.00 12.90 + 133.55 162.00 12.70 + 133.60 165.00 12.50 + 133.65 218.00 14.70 + 133.70 197.00 13.60 + 133.75 206.00 14.20 + 133.80 186.00 13.20 + 133.85 162.00 12.50 + 133.90 176.00 12.80 + 133.95 174.00 12.90 + 134.00 196.00 13.40 + 134.05 174.00 12.90 + 134.10 177.00 12.70 + 134.15 183.00 13.10 + 134.20 184.00 12.90 + 134.25 185.00 13.10 + 134.30 200.00 13.40 + 134.35 175.00 12.70 + 134.40 190.00 13.00 + 134.45 195.00 13.40 + 134.50 192.00 13.00 + 134.55 171.00 12.50 + 134.60 194.00 13.00 + 134.65 190.00 13.10 + 134.70 165.00 12.00 + 134.75 192.00 13.20 + 134.80 160.00 11.70 + 134.85 192.00 13.10 + 134.90 181.00 12.50 + 134.95 208.00 13.70 + 135.00 179.00 12.40 + 135.05 172.00 12.40 + 135.10 183.00 12.50 + 135.15 187.00 12.90 + 135.20 185.00 12.50 + 135.25 182.00 12.70 + 135.30 184.00 12.50 + 135.35 163.00 11.90 + 135.40 201.00 13.00 + 135.45 189.00 12.80 + 135.50 204.00 13.10 + 135.55 178.00 12.50 + 135.60 178.00 12.20 + 135.65 193.00 13.00 + 135.70 215.00 13.40 + 135.75 203.00 13.30 + 135.80 216.00 13.40 + 135.85 165.00 12.10 + 135.90 196.00 12.80 + 135.95 178.00 12.50 + 136.00 170.00 11.90 + 136.05 173.00 12.40 + 136.10 188.00 12.60 + 136.15 176.00 12.50 + 136.20 186.00 12.50 + 136.25 189.00 12.90 + 136.30 166.00 11.80 + 136.35 177.00 12.50 + 136.40 169.00 11.90 + 136.45 171.00 12.30 + 136.50 194.00 12.80 + 136.55 187.00 12.90 + 136.60 162.00 11.70 + 136.65 160.00 11.90 + 136.70 183.00 12.40 + 136.75 150.00 11.50 + 136.80 180.00 12.40 + 136.85 194.00 13.20 + 136.90 185.00 12.60 + 136.95 158.00 11.90 + 137.00 193.00 12.90 + 137.05 165.00 12.20 + 137.10 178.00 12.30 + 137.15 183.00 12.90 + 137.20 180.00 12.40 + 137.25 176.00 12.70 + 137.30 183.00 12.60 + 137.35 189.00 13.20 + 137.40 180.00 12.50 + 137.45 160.00 12.20 + 137.50 202.00 13.30 + 137.55 201.00 13.60 + 137.60 173.00 12.30 + 137.65 176.00 12.80 + 137.70 195.00 13.10 + 137.75 197.00 13.50 + 137.80 186.00 12.80 + 137.85 183.00 13.00 + 137.90 175.00 12.40 + 137.95 178.00 12.80 + 138.00 190.00 12.90 + 138.05 174.00 12.70 + 138.10 163.00 12.00 + 138.15 190.00 13.30 + 138.20 169.00 12.20 + 138.25 198.00 13.60 + 138.30 199.00 13.30 + 138.35 184.00 13.10 + 138.40 216.00 13.90 + 138.45 183.00 13.10 + 138.50 200.00 13.40 + 138.55 186.00 13.30 + 138.60 177.00 12.70 + 138.65 186.00 13.40 + 138.70 193.00 13.30 + 138.75 200.00 14.00 + 138.80 180.00 12.90 + 138.85 178.00 13.20 + 138.90 198.00 13.60 + 138.95 236.00 15.30 + 139.00 203.00 13.80 + 139.05 207.00 14.30 + 139.10 190.00 13.40 + 139.15 171.00 13.10 + 139.20 203.00 13.90 + 139.25 203.00 14.20 + 139.30 198.00 13.70 + 139.35 200.00 14.20 + 139.40 187.00 13.30 + 139.45 214.00 14.70 + 139.50 198.00 13.70 + 139.55 220.00 14.80 + 139.60 196.00 13.70 + 139.65 239.00 15.50 + 139.70 212.00 14.20 + 139.75 219.00 14.80 + 139.80 248.00 15.40 + 139.85 220.00 14.80 + 139.90 241.00 15.10 + 139.95 245.00 15.50 + 140.00 269.00 15.90 + 140.05 294.00 17.00 + 140.10 323.00 17.40 + 140.15 302.00 17.20 + 140.20 312.00 17.10 + 140.25 371.00 18.90 + 140.30 420.00 19.70 + 140.35 516.00 22.30 + 140.40 596.00 23.40 + 140.45 644.00 24.70 + 140.50 711.00 25.40 + 140.55 833.00 28.10 + 140.60 895.00 28.40 + 140.65 1010.00 30.70 + 140.70 1058.00 30.80 + 140.75 1183.00 33.10 + 140.80 1278.00 33.70 + 140.85 1298.00 34.60 + 140.90 1419.00 35.40 + 140.95 1381.00 35.60 + 141.00 1299.00 33.80 + 141.05 1371.00 35.40 + 141.10 1273.00 33.30 + 141.15 1131.00 32.10 + 141.20 992.00 29.40 + 141.25 918.00 28.90 + 141.30 832.00 26.90 + 141.35 655.00 24.50 + 141.40 629.00 23.50 + 141.45 522.00 21.90 + 141.50 472.00 20.30 + 141.55 409.00 19.30 + 141.60 371.00 18.00 + 141.65 325.00 17.30 + 141.70 306.00 16.30 + 141.75 270.00 15.70 + 141.80 238.00 14.40 + 141.85 231.00 14.50 + 141.90 232.00 14.20 + 141.95 223.00 14.30 + 142.00 221.00 13.90 + 142.05 244.00 14.90 + 142.10 228.00 14.10 + 142.15 212.00 13.90 + 142.20 226.00 14.00 + 142.25 197.00 13.40 + 142.30 204.00 13.30 + 142.35 189.00 13.10 + 142.40 201.00 13.20 + 142.45 226.00 14.30 + 142.50 210.00 13.50 + 142.55 213.00 13.90 + 142.60 202.00 13.30 + 142.65 206.00 13.70 + 142.70 189.00 12.80 + 142.75 213.00 13.90 + 142.80 193.00 12.90 + 142.85 206.00 13.70 + 142.90 204.00 13.30 + 142.95 188.00 13.10 + 143.00 221.00 13.80 + 143.05 203.00 13.60 + 143.10 192.00 12.90 + 143.15 197.00 13.40 + 143.20 187.00 12.70 + 143.25 206.00 13.70 + 143.30 197.00 13.10 + 143.35 182.00 12.80 + 143.40 186.00 12.70 + 143.45 228.00 14.40 + 143.50 201.00 13.20 + 143.55 176.00 12.60 + 143.60 193.00 12.90 + 143.65 200.00 13.50 + 143.70 189.00 12.80 + 143.75 198.00 13.40 + 143.80 188.00 12.80 + 143.85 169.00 12.40 + 143.90 183.00 12.60 + 143.95 198.00 13.40 + 144.00 156.00 11.60 + 144.05 172.00 12.50 + 144.10 190.00 12.80 + 144.15 166.00 12.30 + 144.20 163.00 11.90 + 144.25 184.00 13.00 + 144.30 182.00 12.60 + 144.35 173.00 12.60 + 144.40 182.00 12.60 + 144.45 183.00 13.00 + 144.50 186.00 12.80 + 144.55 195.00 13.40 + 144.60 204.00 13.40 + 144.65 179.00 13.00 + 144.70 192.00 13.10 + 144.75 213.00 14.10 + 144.80 187.00 12.90 + 144.85 194.00 13.50 + 144.90 185.00 12.90 + 144.95 183.00 13.20 + 145.00 192.00 13.20 + 145.05 201.00 13.90 + 145.10 211.00 13.90 + 145.15 163.00 12.50 + 145.20 202.00 13.60 + 145.25 197.00 13.80 + 145.30 183.00 13.00 + 145.35 177.00 13.20 + 145.40 188.00 13.20 + 145.45 158.00 12.50 + 145.50 184.00 13.20 + 145.55 162.00 12.70 + 145.60 169.00 12.70 + 145.65 171.00 13.10 + 145.70 188.00 13.40 + 145.75 167.00 13.00 + 145.80 182.00 13.20 + 145.85 197.00 14.10 + 145.90 179.00 13.10 + 145.95 172.00 13.20 + 146.00 163.00 12.50 + 146.05 172.00 13.10 + 146.10 178.00 13.00 + 146.15 179.00 13.40 + 146.20 171.00 12.80 + 146.25 189.00 13.70 + 146.30 190.00 13.40 + 146.35 185.00 13.50 + 146.40 169.00 12.60 + 146.45 165.00 12.70 + 146.50 185.00 13.10 + 146.55 158.00 12.40 + 146.60 190.00 13.30 + 146.65 165.00 12.60 + 146.70 173.00 12.60 + 146.75 206.00 14.10 + 146.80 170.00 12.50 + 146.85 193.00 13.60 + 146.90 167.00 12.30 + 146.95 182.00 13.10 + 147.00 191.00 13.20 + 147.05 175.00 12.90 + 147.10 184.00 12.90 + 147.15 163.00 12.40 + 147.20 174.00 12.50 + 147.25 176.00 12.90 + 147.30 163.00 12.10 + 147.35 174.00 12.80 + 147.40 155.00 11.80 + 147.45 153.00 12.00 + 147.50 190.00 13.00 + 147.55 190.00 13.30 + 147.60 169.00 12.30 + 147.65 189.00 13.30 + 147.70 177.00 12.60 + 147.75 167.00 12.50 + 147.80 163.00 12.00 + 147.85 196.00 13.50 + 147.90 175.00 12.50 + 147.95 146.00 11.60 + 148.00 170.00 12.20 + 148.05 179.00 12.90 + 148.10 182.00 12.60 + 148.15 175.00 12.70 + 148.20 171.00 12.30 + 148.25 201.00 13.60 + 148.30 181.00 12.60 + 148.35 152.00 11.80 + 148.40 194.00 13.00 + 148.45 160.00 12.20 + 148.50 179.00 12.50 + 148.55 181.00 12.90 + 148.60 175.00 12.40 + 148.65 178.00 12.80 + 148.70 186.00 12.80 + 148.75 195.00 13.40 + 148.80 166.00 12.00 + 148.85 184.00 13.00 + 148.90 215.00 13.70 + 148.95 183.00 12.90 + 149.00 184.00 12.60 + 149.05 174.00 12.60 + 149.10 175.00 12.30 + 149.15 171.00 12.50 + 149.20 166.00 12.00 + 149.25 188.00 13.00 + 149.30 165.00 11.90 + 149.35 184.00 12.90 + 149.40 181.00 12.60 + 149.45 174.00 12.60 + 149.50 178.00 12.40 + 149.55 191.00 13.20 + 149.60 181.00 12.50 + 149.65 174.00 12.60 + 149.70 180.00 12.50 + 149.75 177.00 12.70 + 149.80 164.00 11.90 + 149.85 203.00 13.60 + 149.90 178.00 12.40 + 149.95 162.00 12.20 + 150.00 192.00 12.90 + 150.05 164.00 12.20 + 150.10 151.00 11.40 + 150.15 170.00 12.50 + 150.20 166.00 12.00 + 150.25 194.00 13.30 + 150.30 168.00 12.10 + 150.35 173.00 12.50 + 150.40 175.00 12.30 + 150.45 193.00 13.30 + 150.50 177.00 12.40 + 150.55 185.00 13.00 + 150.60 178.00 12.40 + 150.65 178.00 12.70 + 150.70 179.00 12.50 + 150.75 180.00 12.90 + 150.80 169.00 12.20 + 150.85 177.00 12.80 + 150.90 159.00 11.80 + 150.95 167.00 12.40 + 151.00 180.00 12.60 + 151.05 158.00 12.20 + 151.10 173.00 12.40 + 151.15 172.00 12.70 + 151.20 163.00 12.10 + 151.25 168.00 12.60 + 151.30 166.00 12.20 + 151.35 179.00 13.00 + 151.40 159.00 12.00 + 151.45 173.00 12.90 + 151.50 170.00 12.40 + 151.55 151.00 12.10 + 151.60 174.00 12.60 + 151.65 182.00 13.20 + 151.70 182.00 12.90 + 151.75 172.00 12.90 + 151.80 157.00 12.00 + 151.85 156.00 12.30 + 151.90 168.00 12.50 + 151.95 194.00 13.80 + 152.00 177.00 12.80 + 152.05 170.00 12.90 + 152.10 169.00 12.60 + 152.15 173.00 13.00 + 152.20 161.00 12.30 + 152.25 169.00 12.90 + 152.30 167.00 12.50 + 152.35 194.00 13.80 + 152.40 150.00 11.90 + 152.45 159.00 12.50 + 152.50 181.00 13.10 + 152.55 180.00 13.30 + 152.60 193.00 13.40 + 152.65 192.00 13.70 + 152.70 152.00 11.90 + 152.75 159.00 12.50 + 152.80 147.00 11.70 + 152.85 190.00 13.60 + 152.90 167.00 12.40 + 152.95 193.00 13.60 + 153.00 159.00 12.10 + 153.05 195.00 13.60 + 153.10 172.00 12.50 + 153.15 148.00 11.90 + 153.20 174.00 12.50 + 153.25 194.00 13.50 + 153.30 159.00 11.90 + 153.35 190.00 13.30 + 153.40 181.00 12.70 + 153.45 159.00 12.10 + 153.50 168.00 12.20 + 153.55 175.00 12.70 + 153.60 184.00 12.70 + 153.65 200.00 13.50 + 153.70 161.00 11.90 + 153.75 162.00 12.10 + 153.80 152.00 11.50 + 153.85 177.00 12.70 + 153.90 173.00 12.20 + 153.95 184.00 12.90 + 154.00 169.00 12.10 + 154.05 163.00 12.10 + 154.10 177.00 12.40 + 154.15 171.00 12.50 + 154.20 180.00 12.50 + 154.25 201.00 13.40 + 154.30 206.00 13.30 + 154.35 181.00 12.70 + 154.40 170.00 12.00 + 154.45 177.00 12.60 + 154.50 196.00 12.90 + 154.55 201.00 13.40 + 154.60 161.00 11.70 + 154.65 179.00 12.60 + 154.70 185.00 12.50 + 154.75 167.00 12.10 + 154.80 162.00 11.70 + 154.85 178.00 12.60 + 154.90 203.00 13.10 + 154.95 193.00 13.10 + 155.00 164.00 11.70 + 155.05 191.00 13.00 + 155.10 173.00 12.10 + 155.15 165.00 12.00 + 155.20 178.00 12.20 + 155.25 196.00 13.20 + 155.30 188.00 12.50 + 155.35 183.00 12.70 + 155.40 188.00 12.60 + 155.45 166.00 12.10 + 155.50 189.00 12.60 + 155.55 175.00 12.40 + 155.60 173.00 12.00 + 155.65 201.00 13.30 + 155.70 177.00 12.20 + 155.75 202.00 13.30 + 155.80 169.00 11.90 + 155.85 198.00 13.20 + 155.90 191.00 12.70 + 155.95 207.00 13.50 + 156.00 226.00 13.80 + 156.05 184.00 12.80 + 156.10 218.00 13.50 + 156.15 215.00 13.80 + 156.20 239.00 14.20 + 156.25 292.00 16.10 + 156.30 251.00 14.60 + 156.35 255.00 15.10 + 156.40 244.00 14.40 + 156.45 259.00 15.20 + 156.50 260.00 14.90 + 156.55 294.00 16.30 + 156.60 303.00 16.10 + 156.65 282.00 15.90 + 156.70 312.00 16.40 + 156.75 317.00 16.90 + 156.80 342.00 17.20 + 156.85 338.00 17.50 + 156.90 351.00 17.40 + 156.95 359.00 18.10 + 157.00 394.00 18.50 + 157.05 316.00 17.00 + 157.10 379.00 18.20 + 157.15 359.00 18.20 + 157.20 404.00 18.80 + 157.25 381.00 18.80 + 157.30 359.00 17.80 + 157.35 364.00 18.40 + 157.40 347.00 17.60 + 157.45 328.00 17.50 + 157.50 344.00 17.50 + 157.55 320.00 17.40 + 157.60 333.00 17.40 + 157.65 319.00 17.50 + 157.70 289.00 16.30 + 157.75 284.00 16.60 + 157.80 283.00 16.20 + 157.85 305.00 17.20 + 157.90 281.00 16.20 + 157.95 244.00 15.60 + 158.00 253.00 15.40 + 158.05 245.00 15.60 + 158.10 210.00 14.10 + 158.15 201.00 14.20 + 158.20 226.00 14.70 + 158.25 206.00 14.40 + 158.30 218.00 14.40 + 158.35 201.00 14.30 + 158.40 226.00 14.70 + 158.45 201.00 14.20 + 158.50 210.00 14.20 + 158.55 207.00 14.40 + 158.60 176.00 13.00 + 158.65 172.00 13.10 + 158.70 173.00 12.90 + 158.75 195.00 13.90 + 158.80 168.00 12.70 + 158.85 177.00 13.30 + 158.90 186.00 13.30 + 158.95 170.00 13.00 + 159.00 190.00 13.40 + 159.05 175.00 13.10 + 159.10 191.00 13.40 + 159.15 164.00 12.70 + 159.20 189.00 13.30 + 159.25 176.00 13.10 + 159.30 175.00 12.80 + 159.35 162.00 12.50 + 159.40 184.00 13.00 + 159.45 163.00 12.50 + 159.50 179.00 12.80 + 159.55 194.00 13.60 + 159.60 165.00 12.20 + 159.65 180.00 13.00 + 159.70 174.00 12.60 + 159.75 180.00 13.00 + 159.80 179.00 12.60 + 159.85 189.00 13.30 + 159.90 185.00 12.90 + 159.95 151.00 11.80 + 160.00 176.00 12.50 + 160.05 165.00 12.30 + 160.10 163.00 12.00 + 160.15 184.00 13.00 + 160.20 157.00 11.70 + 160.25 166.00 12.30 + 160.30 160.00 11.80 + 160.35 183.00 12.90 + 160.40 167.00 12.10 + 160.45 180.00 12.80 + 160.50 183.00 12.60 + 160.55 163.00 12.20 + 160.60 178.00 12.40 + 160.65 179.00 12.80 + 160.70 161.00 11.80 + 160.75 168.00 12.40 + 160.80 173.00 12.30 + 160.85 202.00 13.60 + 160.90 145.00 11.30 + 160.95 162.00 12.20 + 161.00 180.00 12.50 + 161.05 186.00 13.10 + 161.10 166.00 12.10 + 161.15 177.00 12.70 + 161.20 194.00 13.10 + 161.25 177.00 12.80 + 161.30 178.00 12.50 + 161.35 190.00 13.20 + 161.40 160.00 11.90 + 161.45 173.00 12.60 + 161.50 191.00 12.90 + 161.55 161.00 12.20 + 161.60 181.00 12.60 + 161.65 152.00 11.80 + 161.70 195.00 13.00 + 161.75 171.00 12.50 + 161.80 188.00 12.80 + 161.85 164.00 12.20 + 161.90 185.00 12.70 + 161.95 173.00 12.60 + 162.00 162.00 11.90 + 162.05 166.00 12.30 + 162.10 201.00 13.20 + 162.15 173.00 12.60 + 162.20 172.00 12.20 + 162.25 181.00 12.80 + 162.30 159.00 11.70 + 162.35 185.00 13.00 + 162.40 170.00 12.10 + 162.45 200.00 13.50 + 162.50 196.00 13.00 + 162.55 176.00 12.60 + 162.60 197.00 13.00 + 162.65 176.00 12.60 + 162.70 181.00 12.50 + 162.75 176.00 12.60 + 162.80 184.00 12.60 + 162.85 179.00 12.70 + 162.90 165.00 11.90 + 162.95 146.00 11.50 + 163.00 165.00 11.90 + 163.05 151.00 11.70 + 163.10 164.00 11.90 + 163.15 179.00 12.80 + 163.20 186.00 12.70 + 163.25 182.00 13.00 + 163.30 168.00 12.20 + 163.35 193.00 13.50 + 163.40 177.00 12.60 + 163.45 180.00 13.10 + 163.50 171.00 12.40 + 163.55 207.00 14.10 + 163.60 180.00 12.90 + 163.65 159.00 12.40 + 163.70 165.00 12.40 + 163.75 178.00 13.20 + 163.80 150.00 11.80 + 163.85 177.00 13.20 + 163.90 174.00 12.80 + 163.95 180.00 13.40 + 164.00 184.00 13.20 + 164.05 166.00 13.60 + 164.10 182.00 13.90 + 164.15 188.00 15.60 + 164.20 186.00 15.00 + 164.25 152.00 15.20 + 164.30 200.00 16.90 + 164.35 177.00 18.00 + 164.40 202.00 18.50 + 164.45 178.00 20.40 + 164.50 153.00 18.00 + 164.55 197.00 25.30 + 164.60 153.00 20.70 + 164.65 173.00 30.10 + 164.70 187.00 27.90 + 164.75 175.00 38.20 + 164.80 168.00 30.90 + 164.85 109.00 41.20 diff --git a/tmp/data/lbco.cif b/tmp/data/lbco.cif new file mode 100644 index 00000000..e80dfecb --- /dev/null +++ b/tmp/data/lbco.cif @@ -0,0 +1,36 @@ + data_lbco + + _space_group.IT_coordinate_system_code 1 + _space_group.name_H-M_alt "P m -3 m" + + _cell.angle_alpha 90 + _cell.angle_beta 90 + _cell.length_a 3.88(1) + _cell.length_b 3.88 + _cell.length_c 3.88 + + loop_ + _atom_site.ADP_type + _atom_site.B_iso_or_equiv + _atom_site.fract_x + _atom_site.fract_y + _atom_site.fract_z + _atom_site.label + _atom_site.occupancy + _atom_site.type_symbol + _atom_site.Wyckoff_letter + Biso 0.5 0.0 0.0 0.0 La 0.5 La a + Biso 0.5 0.0 0.0 0.0 Ba 0.5 Ba a + Biso 0.5 0.5 0.5 0.5 Co 1.0 Co b + Biso 0.5 0.0 0.5 0.5 O 1.0 O c + + loop_ + _pd_meas.2theta_scan + _pd_proc.intensity_total + _pd_calc.intensity_total_su + 10.0 167.0 12.6 + 10.05 157.0 12.5 + 10.1 187.0 13.3 + 10.15 197.0 14.0 + 10.2 164.0 12.5 + 10.25 171.0 13.0 diff --git a/tmp/generate_overview_mermaid.py b/tmp/generate_overview_mermaid.py new file mode 100644 index 00000000..67dce703 --- /dev/null +++ b/tmp/generate_overview_mermaid.py @@ -0,0 +1,309 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Generate an overview Mermaid class diagram dynamically from code. + +This script scans the source tree with Python's AST to discover: + - Classes and their inheritance + - Key composition relationships (via __init__ assignments or property + return annotations) + - Collection "contains" relationships for DatablockCollection + +It renders an overview Mermaid classDiagram that automatically adapts +to project structure changes. Analysis internals are intentionally +omitted; we only keep the top-level link from Project to Analysis. + +Output: docs/architecture/overview-diagram.md + +Run (from repo root): + pixi run python tools/generate_overview_mermaid.py +""" + +from __future__ import annotations + +import ast +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path +from typing import Dict +from typing import Iterable +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple + +REPO_ROOT = Path(__file__).resolve().parents[1] +SRC_ROOT = REPO_ROOT / 'src' / 'easydiffraction' +DOCS_OUT_DIR = REPO_ROOT / 'docs' / 'architecture' +OUT_MD = DOCS_OUT_DIR / 'overview-diagram.md' + + +@dataclass +class ClassInfo: + name: str + module: str # e.g., experiments/experiment_types/powder.py + bases: List[str] # base class names (unqualified) + category: Optional[str] = None # container | collection | baseclass | measurement | model + compositions: Set[str] = field(default_factory=set) # composed-with class names + contains: Set[str] = field(default_factory=set) # collection element types + + +INCLUDE_FILES = [ + # Project and Summary + SRC_ROOT / 'project' / 'project.py', + SRC_ROOT / 'project' / 'project_info.py', + SRC_ROOT / 'summary' / 'summary.py', + SRC_ROOT / 'analysis' / 'analysis.py', + # Sample models + SRC_ROOT / 'sample_models' / 'sample_models.py', + SRC_ROOT / 'sample_models' / 'sample_model.py', + SRC_ROOT / 'sample_models' / 'sample_model_types' / 'base.py', + # Experiments + SRC_ROOT / 'experiments' / 'experiments.py', + SRC_ROOT / 'experiments' / 'experiment.py', + SRC_ROOT / 'experiments' / 'experiment_types' / 'base.py', +] + + +def iter_experiment_type_files() -> Iterable[Path]: + expt_dir = SRC_ROOT / 'experiments' / 'experiment_types' + if not expt_dir.exists(): + return [] + skip = {'__init__.py', 'base.py', 'enums.py', 'instrument_mixin.py'} + for py in sorted(expt_dir.glob('*.py')): + if py.name in skip: + continue + yield py + + +def parse_file(py_path: Path) -> Optional[ast.Module]: + try: + src = py_path.read_text(encoding='utf-8') + return ast.parse(src) + except Exception: + return None + + +def name_from_node(n: ast.AST) -> Optional[str]: + if isinstance(n, ast.Name): + return n.id + if isinstance(n, ast.Attribute): + # Return attribute tail (unqualified) + return n.attr + return None + + +def discover_classes() -> Dict[str, ClassInfo]: + files = list(INCLUDE_FILES) + list(iter_experiment_type_files()) + classes: Dict[str, ClassInfo] = {} + + for py in files: + mod = parse_file(py) + if not mod: + continue + rel_module = str(py.relative_to(SRC_ROOT)) + for node in mod.body: + if isinstance(node, ast.ClassDef): + base_names = [name_from_node(b) for b in node.bases] + base_names = [n for n in base_names if n] + ci = ClassInfo(name=node.name, module=rel_module, bases=base_names) + classes[ci.name] = ci + + # Second pass: gather compositions and contains + for node in mod.body: + if isinstance(node, ast.ClassDef): + ci = classes.get(node.name) + if not ci: + continue + for inner in node.body: + # __init__ assignments: self.attr = SomeClass(...) + if isinstance(inner, ast.FunctionDef) and inner.name == '__init__': + for stmt in ast.walk(inner): + if isinstance(stmt, ast.Assign): + # Look for Call on RHS + if isinstance(stmt.value, ast.Call): + callee = name_from_node(stmt.value.func) + if callee: + ci.compositions.add(callee) + if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call): + # super().__init__(item_type=SomeClass) + call = stmt.value + func_name = name_from_node(call.func) + if func_name == '__init__' and isinstance(call, ast.Call): + for kw in call.keywords: + if kw.arg == 'item_type': + n = name_from_node(kw.value) + if n: + ci.contains.add(n) + # @property return annotation: def x(self) -> SomeClass + if isinstance(inner, ast.FunctionDef): + if inner.returns is not None: + ann = name_from_node(inner.returns) + if ann: + ci.compositions.add(ann) + + # Categorize + for ci in classes.values(): + path = ci.module + # Collections + if 'DatablockCollection' in ci.bases: + ci.category = 'collection' + # Measurement (concrete experiment types) + elif ( + 'experiments/experiment_types/' in path + and ci.name.endswith('Experiment') + and not ci.name.startswith('Base') + ): + ci.category = 'measurement' + # Base classes (heuristic) + elif ci.name.startswith('Base'): + ci.category = 'baseclass' + # Models + elif 'sample_models/' in path and ci.name in {'BaseSampleModel', 'SampleModel'}: + ci.category = 'model' + # Containers + elif path.endswith('project.py') and ci.name == 'Project' or path.endswith('project_info.py') and ci.name == 'ProjectInfo' or path.endswith('summary.py') and ci.name == 'Summary' or path.endswith('analysis/analysis.py') and ci.name == 'Analysis': + ci.category = 'container' + # Keep others uncategorized (they will still participate via edges) + + return classes + + +# ----------------- +# Mermaid rendering +# ----------------- + + +def mermaid_header() -> str: + return 'classDiagram\n' + + +def mermaid_classes(classes: Dict[str, ClassInfo]) -> str: + lines: List[str] = [] + for ci in classes.values(): + # Limit to overview classes: containers, collections, models, baseclasses, measurements + if ci.category in {'container', 'collection', 'model', 'baseclass', 'measurement'}: + style = f':::{ci.category}' if ci.category else '' + lines.append(f' class {ci.name}{style}') + return '\n'.join(lines) + ('\n\n' if lines else '') + + +def build_edges(classes: Dict[str, ClassInfo]) -> Tuple[List[str], List[str], List[str]]: + """Return (inheritance, composition, contains) edge lists.""" + # Work only with classes we render as overview categories + showable = { + name + for name, ci in classes.items() + if ci.category in {'container', 'collection', 'model', 'baseclass', 'measurement'} + } + + inheritance: List[str] = [] + composition: List[str] = [] + contains: List[str] = [] + + # Inheritance: Parent <|-- Child + for child_name, ci in classes.items(): + for base in ci.bases: + if base in showable and child_name in showable: + inheritance.append(f' {base} <|-- {child_name}') + + # Composition: A *-- B (if A composes B) + for a_name, ci in classes.items(): + if a_name not in showable: + continue + for b in ci.compositions: + if b in showable: + # Special-case: hide Analysis internals; keep only Project *-- Analysis + if b == 'Analysis' and a_name != 'Project': + continue + composition.append(f' {a_name} *-- {b}') + + # Contains: Collections "1" -- "*" T : contains + for a_name, ci in classes.items(): + if ci.category != 'collection': + continue + for t in ci.contains: + if t in classes: + # Expand Experiments contains Experiment into all concrete measurements + if a_name == 'Experiments' and t == 'Experiment': + for name, c2 in classes.items(): + if c2.category == 'measurement': + contains.append(f' {a_name} "1" -- "*" {name} : contains') + else: + contains.append(f' {a_name} "1" -- "*" {t} : contains') + + return inheritance, composition, contains + + +def mermaid_relationships(classes: Dict[str, ClassInfo]) -> str: + inh, comp, cont = build_edges(classes) + lines: List[str] = [] + lines.append(' %% Relationships %%\n') + if comp or cont or inh: + lines.append('') + lines.extend(inh) + if inh and (comp or cont): + lines.append('') + lines.extend(cont) + if cont and comp: + lines.append('') + lines.extend(comp) + lines.append('\n') + return '\n'.join(lines) + + +def mermaid_styles() -> str: + return ( + ' %%%%%%%%%%%%%\n' + ' %% STYLING %%\n' + ' %%%%%%%%%%%%%\n\n' + ' %% Abstract Base Classes\n' + ' classDef baseclass fill:#6A5ACD,stroke:#333,stroke-width:1px,color:white;\n\n' + ' %% Containers (Project, ProjectInfo, Summary, Analysis)\n' + ' classDef container fill:#455A64,stroke:#333,stroke-width:1px,color:white;\n\n' + ' %% Collections (SampleModels, Experiments)\n' + ' classDef collection fill:#607D8B,stroke:#333,stroke-width:1px,color:white;\n\n' + ' %% Concrete Experiments\n' + ' classDef measurement fill:#4682B4,stroke:#0D47A1,stroke-width:1px,color:white;\n\n' + ' %% Models (SampleModel, StructuralModel)\n' + ' classDef model fill:#009688,stroke:#004D40,stroke-width:1px,color:white;\n' + ) + + +def build_mermaid() -> str: + classes = discover_classes() + parts = [ + mermaid_header(), + mermaid_classes(classes), + mermaid_relationships(classes), + mermaid_styles(), + ] + return ''.join(parts) + + +def write_markdown(mermaid: str) -> None: + DOCS_OUT_DIR.mkdir(parents=True, exist_ok=True) + content = [ + '# Architecture Overview', + '', + '```mermaid', + mermaid, + '```', + '', + '> Note: This diagram is auto-generated. Edit tools/generate_overview_mermaid.py to change structure or style.', + '', + ] + OUT_MD.write_text('\n'.join(content), encoding='utf-8') + rel = OUT_MD.relative_to(REPO_ROOT) + print(f'Wrote: {rel}') + + +def main() -> None: + if not SRC_ROOT.exists(): + raise SystemExit(f'Source root not found: {SRC_ROOT}') + mermaid = build_mermaid() + write_markdown(mermaid) + + +if __name__ == '__main__': + main() diff --git a/tutorials-drafts/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye b/tmp/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye similarity index 100% rename from tutorials-drafts/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye rename to tmp/hrpt_n_Bi0p88Sm0p12Fe0p94Ti0p06O3_DW_V_9x8x52_1p49_HI.xye diff --git a/tmp/short.py b/tmp/short.py new file mode 100644 index 00000000..87f90d00 --- /dev/null +++ b/tmp/short.py @@ -0,0 +1,94 @@ +from easydiffraction import Experiment +from easydiffraction import Experiments +from easydiffraction import Logger +from easydiffraction import Project +from easydiffraction import SampleModel +from easydiffraction import SampleModels +from easydiffraction.experiments.categories.linked_phases import LinkedPhase +from easydiffraction.experiments.categories.linked_phases import LinkedPhases +from easydiffraction.sample_models.categories.atom_sites import AtomSite +from easydiffraction.sample_models.categories.atom_sites import AtomSites +from easydiffraction.sample_models.categories.cell import Cell +from easydiffraction.sample_models.categories.space_group import SpaceGroup + +Logger.configure(mode=Logger.Mode.LOG, level=Logger.Level.DEBUG) +# Logger.configure(mode=Logger.Mode.RAISE, level=Logger.Level.DEBUG) + +sg = SpaceGroup() +sg.name_h_m = 'P n m a' +sg.it_coordinate_system_code = 'cab' + +cell = Cell() +cell.length_a = 5.4603 + +site = AtomSite() +site.type_symbol = 'Si' + +sites = AtomSites() +sites.add_from_args(site) + +model = SampleModel(name='mdl') +model.space_group = sg +model.cell = cell +model.atom_sites = sites + + +print(model.parameters) + +models = SampleModels() +# models.add_from_args(model) +models.add_from_cif_path('tutorials/data/lbco.cif') + +print(models) +for p in models.parameters: + print(p) +print(models.as_cif) + +exp = Experiment(name='hrpt', data_path='tutorials/data/hrpt_lbco.xye') +print(exp) + +linked_phases = LinkedPhases() +linked_phase = LinkedPhase(id='lbco', scale=10.0) +linked_phases.add_from_args(linked_phase) + +exp.linked_phases = linked_phases + +exp.instrument.setup_wavelength = 1.494 +exp.instrument.calib_twotheta_offset = 0.6 + +exp.peak.broad_gauss_u = 0.1 +exp.peak.broad_gauss_v = -0.1 +exp.peak.broad_gauss_w = 0.1 +exp.peak.broad_lorentz_y = 0.1 + +# exp.background.add_from_args(x=10, y=170) +# exp.background.add_from_args(x=30, y=170) +# exp.background.add_from_args(x=50, y=170) +# exp.background.add_from_args(x=110, y=170) +# exp.background.add_from_args(x=165, y=170) + +experiments = Experiments() +print(experiments) + +experiments.add(exp) +print(experiments) +for p in experiments.parameters: + print(p) +# print(experiments.as_cif) + + +proj = Project(name='PROJ') +print(proj) + +proj.sample_models = models +proj.experiments = experiments + + +# proj.plotter.engine = 'plotly' + +proj.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) + + +models['lbco'].cell.length_a.free = True +print('----', models['lbco'].cell.length_a.free) +# proj.analysis.show_free_params() diff --git a/tmp/short2.py b/tmp/short2.py new file mode 100644 index 00000000..d1bd5eb1 --- /dev/null +++ b/tmp/short2.py @@ -0,0 +1,228 @@ +import easydiffraction as ed +from easydiffraction import Experiment +from easydiffraction import Experiments +from easydiffraction import Project +from easydiffraction import SampleModel +from easydiffraction import SampleModels +from easydiffraction.experiments.categories.background import LineSegmentBackground +from easydiffraction.experiments.categories.background import Point +from easydiffraction.experiments.categories.linked_phases import LinkedPhase +from easydiffraction.experiments.categories.linked_phases import LinkedPhases +from easydiffraction.sample_models.categories.atom_sites import AtomSite +from easydiffraction.sample_models.categories.atom_sites import AtomSites +from easydiffraction.sample_models.categories.cell import Cell +from easydiffraction.sample_models.categories.space_group import SpaceGroup + +# from easydiffraction.core.parameters import BaseDescriptor, GenericDescriptor, Descriptor +# from easydiffraction.core.parameters import BaseParameter, GenericParameter, Parameter + +# Logger.configure(mode=Logger.Mode.VERBOSE, level=Logger.Level.DEBUG) +# Logger.configure(mode=Logger.Mode.COMPACT, level=Logger.Level.DEBUG) + + +# bd = BaseDescriptor() +# gd = GenericDescriptor() +# d = Descriptor() + +# bp = BaseParameter() +# gp = GenericParameter() +# p = Parameter() + +project = ed.Project() + + +sg = SpaceGroup() +sg.name_h_m = 'P n m a' +sg.name_h_m = 33.3 + +sg.it_coordinate_system_code = 'cab' + +cell = Cell() +cell.length_a = 5.4603 +cell.length_a = '5.4603' +cell.length_a = -5.4603 + +cell.lengtha = -5.4603 + + +exit() + +site = AtomSite() +site.label = 'Si' +site.type_symbol = 'Si' + +sites = AtomSites() +sites.add_from_args(site) + + +model = SampleModel(name='mdl') +model.space_group = sg +model.cell = cell +model.atom_sites = sites + +site = AtomSite() +site.label = 'Tb' +site.type_symbol = 'Tb' +sites.add_from_args(site) + + +# model.cell = 'k' + + +# print(model.parameters) +for p in model.parameters: + print(p) + +# exit() + +print('================================') + +models = SampleModels() +# models.add_from_args(model) +models.add_from_cif_path('tutorials/data/lbco.cif') + +print(models) +for p in models.parameters: + print(p) +print(models.as_cif) + +exp = Experiment(name='hrpt', data_path='tutorials/data/hrpt_lbco.xye') +print(exp) + +linked_phases = LinkedPhases() +linked_phase = LinkedPhase(id='lbco', scale=10.0) +linked_phases.add_from_args(linked_phase) + +exp.linked_phases = linked_phases + +exp.instrument.setup_wavelength = 1.494 +exp.instrument.calib_twotheta_offset = 0.6 + +exp.peak.broad_gauss_u = 0.1 +exp.peak.broad_gauss_v = -0.1 +exp.peak.broad_gauss_w = 0.1 +exp.peak.broad_lorentz_y = 0.1 + +bkg = LineSegmentBackground() +point1 = Point(x=10, y=170) +point2 = Point(x=30, y=170) +point3 = Point(x=50, y=170) +point4 = Point(x=110, y=170) +point5 = Point(x=165, y=170) +bkg.add_from_args(point1) +bkg.add_from_args(point2) +bkg.add_from_args(point3) +bkg.add_from_args(point4) +bkg.add_from_args(point5) +# exp.background.add_from_args(bkg) +exp.background = bkg + +# exp.background.add_from_args(x=10, y=170) +# exp.background.add_from_args(x=30, y=170) +# exp.background.add_from_args(x=50, y=170) +# exp.background.add_from_args(x=110, y=170) +# exp.background.add_from_args(x=165, y=170) + +experiments = Experiments() +print(experiments) + +experiments.add(exp) +print(experiments) +for p in experiments.parameters: + print(p) +# print(experiments.as_cif) +# exit() + +proj = Project(name='PROJ') +print(proj) + +proj.sample_models = models +proj.experiments = experiments + + +proj.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) + + +def set_as_online(): + m = proj.sample_models['lbco'] + m.cell.length_a = 3.8909 + m.cell.length_b = 3.8909 + m.cell.length_c = 3.8909 + m.atom_sites['La'].b_iso = 0.5052 + m.atom_sites['Ba'].b_iso = 0.5049 + m.atom_sites['Co'].b_iso = 0.2370 + m.atom_sites['O'].b_iso = 1.3935 + e = proj.experiments['hrpt'] + e.linked_phases['lbco'].scale = 9.1351 + e.instrument.calib_twotheta_offset = 0.6226 + e.peak.broad_gauss_u = 0.0816 + e.peak.broad_gauss_v = -0.1159 + e.peak.broad_gauss_w = 0.1204 + e.peak.broad_lorentz_y = 0.0844 + e.background[10].y = 168.5585 + e.background[30].y = 164.3357 + e.background[50].y = 166.8881 + e.background[110].y = 175.4006 + e.background[165].y = 174.2813 + + +def set_as_initial(): + m = proj.sample_models['lbco'] + m.cell.length_a.uncertainty = None + m.cell.length_a = 3.885 + m.cell.length_b = 3.885 + m.cell.length_c = 3.885 + # m.atom_sites['La'].b_iso = 0.5052 + # m.atom_sites['Ba'].b_iso = 0.5049 + # m.atom_sites['Co'].b_iso = 0.2370 + # m.atom_sites['O'].b_iso = 1.3935 + # e = proj.experiments['hrpt'] + # e.linked_phases['lbco'].scale = 9.1351 + # e.instrument.calib_twotheta_offset = 0.6226 + # e.peak.broad_gauss_u = 0.0816 + # e.peak.broad_gauss_v = -0.1159 + # e.peak.broad_gauss_w = 0.1204 + # e.peak.broad_lorentz_y = 0.0844 + # e.background[10].y = 168.5585 + # e.background[30].y = 164.3357 + # e.background[50].y = 166.8881 + # e.background[110].y = 175.4006 + # e.background[165].y = 174.2813 + + +set_as_online() +# set_as_initial() +proj.plotter.engine = 'plotly' +proj.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) +# exit() + +models['lbco'].cell.length_a.free = True + +models['lbco'].atom_sites['La'].b_iso.free = True +models['lbco'].atom_sites['Ba'].b_iso.free = True +models['lbco'].atom_sites['Co'].b_iso.free = True +models['lbco'].atom_sites['O'].b_iso.free = True + +exp.instrument.calib_twotheta_offset.free = True + +exp.peak.broad_gauss_u.free = True +exp.peak.broad_gauss_v.free = True +exp.peak.broad_gauss_w.free = True +exp.peak.broad_lorentz_y.free = True + +exp.background[10].y.free = True +exp.background[30].y.free = True +exp.background[50].y.free = True +exp.background[110].y.free = True +exp.background[165].y.free = True + +exp.linked_phases['lbco'].scale.free = True + + +print('----', models['lbco'].cell.length_a.free) +proj.analysis.show_free_params() +proj.analysis.fit() + +# proj.plotter.engine = 'plotly' +# proj.plot_meas_vs_calc(expt_name='hrpt') +proj.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41) diff --git a/tmp/short3.py b/tmp/short3.py new file mode 100644 index 00000000..a261a944 --- /dev/null +++ b/tmp/short3.py @@ -0,0 +1,138 @@ +# %% [markdown] +# # Structure Refinement: LBCO, HRPT +# +# This minimalistic example is designed to be as compact as possible for +# a Rietveld refinement of a crystal structure using constant-wavelength +# neutron powder diffraction data for La0.5Ba0.5CoO3 from HRPT at PSI. +# +# It does not contain any advanced features or options, and includes no +# comments or explanations—these can be found in the other tutorials. +# Default values are used for all parameters if not specified. Only +# essential and self-explanatory code is provided. +# +# The example is intended for users who are already familiar with the +# EasyDiffraction library and want to quickly get started with a simple +# refinement. It is also useful for those who want to see what a +# refinement might look like in code. For a more detailed explanation of +# the code, please refer to the other tutorials. + +# %% [markdown] +# ## Import Library + +# %% +import easydiffraction as ed + +# %% [markdown] +# ## Step 1: Define Project + +# %% +project = ed.Project() + +project.plotter.x_min = 38 +project.plotter.x_max = 41 + +# %% [markdown] +# ## Step 2: Define Sample Model + +# %% +project.sample_models.add_minimal(name='lbco') + +# %% +sample_model = project.sample_models['lbco'] + +# %% +sample_model.space_group.name_h_m = 'P m -3 m' +sample_model.space_group.it_coordinate_system_code = '1' + +# %% +sample_model.cell.length_a = 3.88 + +# %% +sample_model.atom_sites.add_from_args( + label='La', type_symbol='La', fract_x=0., fract_y=0., fract_z=0, b_iso=0.5, occupancy=0.5, + wyckoff_letter='a' +) +sample_model.atom_sites.add_from_args( + label='Ba', type_symbol='Ba', fract_x=0, fract_y=0, fract_z=0, b_iso=0.5, occupancy=0.5, wyckoff_letter='a' +) +sample_model.atom_sites.add_from_args(label='Co', type_symbol='Co', fract_x=0.5, fract_y=0.5, fract_z=0.5, b_iso=0.5, wyckoff_letter='b') +sample_model.atom_sites.add_from_args(label='O', type_symbol='O', fract_x=0, fract_y=0.5, fract_z=0.5, b_iso=0.5, wyckoff_letter='c') + +# %% [markdown] +# ## Step 3: Define Experiment + +# %% +ed.download_from_repository('hrpt_lbco.xye', destination='data') + +# %% +project.experiments.add_from_data_path( + name='hrpt', + data_path='data/hrpt_lbco.xye', + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='neutron', +) + +# %% +experiment = project.experiments['hrpt'] + +# %% +experiment.instrument.setup_wavelength = 1.494 +experiment.instrument.calib_twotheta_offset = 0.6 + +# %% +experiment.peak.broad_gauss_u = 0.1 +experiment.peak.broad_gauss_v = -0.1 +experiment.peak.broad_gauss_w = 0.1 +experiment.peak.broad_lorentz_y = 0.1 + +# %% +experiment.background.add_from_args(x=10, y=170) +experiment.background.add_from_args(x=30, y=170) +experiment.background.add_from_args(x=50, y=170) +experiment.background.add_from_args(x=110, y=170) + +# %% +experiment.excluded_regions.add_from_args(start=0, end=5) +experiment.excluded_regions.add_from_args(start=130, end=180) + +# %% +experiment.linked_phases.add_from_args(id='lbco', scale=10.0) + +# %% [markdown] +# ## Step 4: Perform Analysis + +# %% +sample_model.cell.length_a.free = True + +sample_model.atom_sites['La'].b_iso.free = True +sample_model.atom_sites['Ba'].b_iso.free = True +sample_model.atom_sites['Co'].b_iso.free = True +sample_model.atom_sites['O'].b_iso.free = True + +# %% +experiment.instrument.calib_twotheta_offset.free = True + +experiment.peak.broad_gauss_u.free = True +experiment.peak.broad_gauss_v.free = True +experiment.peak.broad_gauss_w.free = True +experiment.peak.broad_lorentz_y.free = True + +experiment.background['10'].y.free = True +experiment.background['30'].y.free = True +experiment.background['50'].y.free = True +experiment.background['110'].y.free = True + +experiment.linked_phases['lbco'].scale.free = True + + +# sample_model.show_as_cif() +# experiment.show_as_cif() +# exit() + + +# %% +project.analysis.fit() + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) diff --git a/tmp/short5.py b/tmp/short5.py new file mode 100644 index 00000000..843b252d --- /dev/null +++ b/tmp/short5.py @@ -0,0 +1,316 @@ +from __future__ import annotations + +from typing import ParamSpec +from typing import TypeVar + +from easydiffraction.analysis.categories.constraints import Constraint +from easydiffraction.analysis.categories.constraints import Constraints +from easydiffraction.sample_models.categories.atom_sites import AtomSite # type: ignore +from easydiffraction.sample_models.categories.atom_sites import AtomSites # type: ignore +from easydiffraction.sample_models.categories.cell import Cell # type: ignore +from easydiffraction.sample_models.categories.space_group import SpaceGroup # type: ignore +from easydiffraction.sample_models.sample_model.base import SampleModelBase +from easydiffraction.sample_models.sample_model.factory import SampleModel +from easydiffraction.sample_models.sample_models import SampleModels +from easydiffraction.utils.logging import log # type: ignore + +P = ParamSpec('P') +R = TypeVar('R') + + +# --------------------------------------------------------------------- +# Example usage +# --------------------------------------------------------------------- +if __name__ == '__main__': + + log.info('-------- Types --------') + + s1 = AtomSite(label='La', type_symbol='La') + s1.fract_x.value = 1.234 + assert s1.fract_x.value == 1.234 + s1.fract_x.value = 'xyz' + assert s1.fract_x.value == 1.234 + s1.fract_x = 'qwe' + assert s1.fract_x.value == 1.234 + + assert s1.fract_x.free == False + s1.fract_x.free = True + assert s1.fract_x.free == True + s1.fract_x.free = 'abc' + assert s1.fract_x.free == True + + s1 = AtomSite(label='Si', type_symbol='Si', fract_x='uuuu') + assert s1.fract_x.value == 0.0 + + log.info('-------- Cell --------') + + c = Cell() + assert c.length_b.value == 10.0 + c = Cell(length_b=-8.8) + assert c.length_b.value == 10.0 + c = Cell(length_b='7.7') # type: ignore + assert c.length_b.value == 10.0 + c = Cell(length_b=6.6) + assert c.length_b.value == 6.6 + c.length_b.value = -5.5 + assert c.length_b.value == 6.6 + c.length_b = -4.4 + assert c.length_b.value == 6.6 + c.length_b = 3.3 + assert c.length_b.value == 3.3 + c.length_b = 2222.2 + assert c.length_b.value == 3.3 + c.length_b.free = 'qwe' # type: ignore + assert c.length_b.free is False + c.length_b.fre = 'fre' # type: ignore + assert getattr(c.length_b, 'fre', None) is None + c.length_b.qwe = 'qwe' # type: ignore + assert getattr(c.length_b, 'qwe', None) is None + c.length_b.description = 'desc' # type: ignore + assert c.length_b.description == 'Length of the b axis of the unit cell.' # type: ignore + assert c.length_b._public_readonly_attrs() == {'as_cif', 'constrained', + 'description', + 'unique_name', 'name', 'parameters', + 'uid', 'units'} + assert c.length_b._public_writable_attrs() == {'fit_max', 'fit_min', 'free', 'uncertainty', + 'value'} + c.qwe = 'qwe' + assert getattr(c.length_b, 'qwe', None) is None + assert c.length_b._cif_handler.names == ['_cell.length_b'] + assert len(c.length_b._minimizer_uid) == 16 + assert (c.parameters[1].value == 3.3) # type: ignore + + log.info('-------- SpaceGroup --------') + + sg = SpaceGroup() + assert sg.name_h_m.value == 'P 1' + sg = SpaceGroup(name_h_m='qwe') + assert sg.name_h_m.value == 'P 1' + sg = SpaceGroup(name_h_m='P b n m', it_coordinate_system_code='cab') + assert sg.name_h_m.value == 'P 1' + assert sg.it_coordinate_system_code.value == '' + sg = SpaceGroup(name_h_m='P n m a', it_coordinate_system_code='cab') + assert sg.name_h_m.value == 'P n m a' + assert sg.it_coordinate_system_code.value == 'cab' + sg.name_h_m = 34.9 + assert sg.name_h_m.value == 'P n m a' + sg.name_h_m = 'P 1' + assert sg.name_h_m.value == 'P 1' + assert sg.it_coordinate_system_code.value == '' + sg.name_h_m = 'P n m a' + assert sg.name_h_m.value == 'P n m a' + assert sg.it_coordinate_system_code.value == 'abc' + + log.info('-------- AtomSites --------') + + s1 = AtomSite(label='La', type_symbol='La') + assert s1.label.value == 'La' + assert s1.type_symbol.value == 'La' + s2 = AtomSite(label='Si', type_symbol='Si') + assert s2.label.value == 'Si' + assert s2.type_symbol.value == 'Si' + sites = AtomSites() + assert len(sites) == 0 + sites.add(s1) + sites.add(s2) + assert len(sites) == 2 + s1.label = 'Tb' + assert s1.label.value == 'Tb' + assert list(sites.keys()) == ['Tb', 'Si'] + assert sites['Tb'] is s1 + assert sites['Tb'].fract_x.value == 0.0 + s2.fract_x.value = 0.123 + assert s2.fract_x.value == 0.123 + s2.fract_x = 0.456 + assert s2.fract_x.value == 0.456 + sites['Tb'].fract_x = 0.789 + assert sites['Tb'].fract_x.value == 0.789 + sites['Tb'].qwe = 'qwe' # type: ignore + assert getattr(sites['Tb'], 'qwe', None) is None + sites.abc = 'abc' # type: ignore + assert getattr(sites, 'abc', None) is None + sites['Tb'].label = 'a b c' + assert sites['Tb'].label.value == 'Tb' + + assert sites['Tb']._label.value == 'Tb' + assert sites['Tb'].label.value == 'Tb' + assert sites['Tb'].name is None + + log.info('-------- SampleModel --------') + + model = SampleModel(name='lbco') + assert model.name == 'lbco' + assert model.cell.length_b.value == 10.0 + assert len(model.atom_sites) == 0 + model.atom_sites.add(s1) + model.atom_sites.add(s2) + assert len(model.atom_sites) == 2 + assert model.atom_sites.names == ['Tb', 'Si'] + assert model.atom_sites._items[0].label.value == 'Tb' + assert model.atom_sites._items[1].label.value == 'Si' + + log.info('-------- SampleModels --------') + + models = SampleModels() + assert len(models) == 0 + models.add(model) + assert len(models) == 1 + assert models._items[0].name == 'lbco' + + log.info('-------- PARENTS --------') + + assert models._parent is None + assert type(models['lbco']._parent) is SampleModels + assert type(models['lbco'].cell._parent) is SampleModelBase + assert type(models['lbco'].cell.length_b._parent) is Cell + assert type(models['lbco'].atom_sites._parent) is SampleModelBase + assert type(models['lbco'].atom_sites['Tb']._parent) is AtomSites + assert type(models['lbco'].atom_sites['Tb'].fract_x._parent) is AtomSite + + assert type(s1._parent) is AtomSites + assert type(models['lbco'].atom_sites) is AtomSites + assert len(models['lbco'].atom_sites) == 2 + del models['lbco'].atom_sites['Tb'] + assert len(models['lbco'].atom_sites) == 1 + assert s1._parent is None + assert type(models['lbco'].atom_sites) is AtomSites + + log.info('-------- PARAMETERS --------') + + assert len(models['lbco'].atom_sites['Si'].parameters) == 9 + assert models['lbco'].atom_sites['Si'].parameters[0].value == 'Si' + assert len(models['lbco'].atom_sites.parameters) == 9 + assert len(models['lbco'].cell.parameters) == 6 + assert len(models['lbco'].parameters) == 17 + assert len(models.parameters) == 17 + + log.info('-------- CIF HANDLERS --------') + + s3 = AtomSite(label='La', type_symbol='La') + assert s3.label.value == 'La' + assert s3.type_symbol.value == 'La' + + assert len(models['lbco'].atom_sites) == 1 + models['lbco'].atom_sites.add(s3) + assert len(models['lbco'].atom_sites) == 2 + assert models['lbco'].cell.length_b.as_cif == '_cell.length_b 10.0' + assert models['lbco'].cell.as_cif == """_cell.length_a 10.0 +_cell.length_b 10.0 +_cell.length_c 10.0 +_cell.angle_alpha 90.0 +_cell.angle_beta 90.0 +_cell.angle_gamma 90.0""" + + assert models['lbco'].atom_sites.as_cif == """loop_ +_atom_site.label +_atom_site.type_symbol +_atom_site.fract_x +_atom_site.fract_y +_atom_site.fract_z +_atom_site.Wyckoff_letter +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.adp_type +Si Si 0.456 0.0 0.0 a 1.0 0.0 Biso +La La 0.0 0.0 0.0 a 1.0 0.0 Biso""" + + print(models['lbco'].as_cif) + + assert models['lbco'].as_cif == """data_lbco + +_cell.length_a 10.0 +_cell.length_b 10.0 +_cell.length_c 10.0 +_cell.angle_alpha 90.0 +_cell.angle_beta 90.0 +_cell.angle_gamma 90.0 + +_space_group.name_H-M_alt "P 1" +_space_group.IT_coordinate_system_code + +loop_ +_atom_site.label +_atom_site.type_symbol +_atom_site.fract_x +_atom_site.fract_y +_atom_site.fract_z +_atom_site.Wyckoff_letter +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.adp_type +Si Si 0.456 0.0 0.0 a 1.0 0.0 Biso +La La 0.0 0.0 0.0 a 1.0 0.0 Biso""" + + assert models.as_cif == """data_lbco + +_cell.length_a 10.0 +_cell.length_b 10.0 +_cell.length_c 10.0 +_cell.angle_alpha 90.0 +_cell.angle_beta 90.0 +_cell.angle_gamma 90.0 + +_space_group.name_H-M_alt "P 1" +_space_group.IT_coordinate_system_code + +loop_ +_atom_site.label +_atom_site.type_symbol +_atom_site.fract_x +_atom_site.fract_y +_atom_site.fract_z +_atom_site.Wyckoff_letter +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.adp_type +Si Si 0.456 0.0 0.0 a 1.0 0.0 Biso +La La 0.0 0.0 0.0 a 1.0 0.0 Biso""" + + log.info('-------- Full Names --------') + + cell = Cell() + assert cell.unique_name == 'cell' + + assert cell.length_b.unique_name == 'cell.length_b' + + site = AtomSite(label='Tb', type_symbol='Tb') + assert site.unique_name == 'atom_site.Tb' + + sites = AtomSites() # + assert sites.unique_name is None + + sites.add(site) + assert site.unique_name == 'atom_site.Tb' + assert sites['Tb'].unique_name == 'atom_site.Tb' + + model = SampleModel(name='lbco') # + assert model.unique_name == 'lbco' + + model.cell = cell + assert cell.unique_name == 'lbco.cell' + assert cell.length_b.unique_name == 'lbco.cell.length_b' + assert model.cell.unique_name == 'lbco.cell' + assert model.cell.length_b.unique_name == 'lbco.cell.length_b' + + model.atom_sites = sites + assert sites.unique_name is None + assert model.atom_sites.unique_name is None + assert model.atom_sites['Tb'].unique_name == 'lbco.atom_site.Tb' + + models = SampleModels() # + assert models.unique_name is None + + models.add(model) + assert models['lbco'].cell.unique_name == 'lbco.cell' + assert models['lbco'].cell.length_b.unique_name == 'lbco.cell.length_b' + assert models['lbco'].atom_sites.unique_name is None + assert models['lbco'].atom_sites['Tb'].unique_name == 'lbco.atom_site.Tb' + + log.info('-------- Constraints --------') + con = Constraint(lhs_alias='cell.length_a', rhs_expr='2 * cell.length_b + 1.0') + assert con.lhs_alias.value == 'cell.length_a' + assert con.rhs_expr.value == '2 * cell.length_b + 1.0' + cons = Constraints() + assert len(cons) == 0 + cons.add(con) + assert len(cons) == 1 diff --git a/tmp/short6.py b/tmp/short6.py new file mode 100644 index 00000000..ef027c51 --- /dev/null +++ b/tmp/short6.py @@ -0,0 +1,85 @@ +from easydiffraction.core.category import CategoryItem +from easydiffraction.core.parameters import CifHandler +from easydiffraction.core.parameters import Parameter +from easydiffraction.core.validation import AttributeSpec +from easydiffraction.core.validation import RangeValidator +from easydiffraction.utils.logging import Logger +from easydiffraction.utils.logging import log + +Logger.configure( + level=Logger.Level.DEBUG, + mode=Logger.Mode.COMPACT, + reaction=Logger.Reaction.WARN, +) + + +class Cell(CategoryItem): + def __init__(self, *, length_a=None): + super().__init__() + + self._length_a = Parameter( + value_spec=AttributeSpec( + value=length_a, + default=10.0, + content_validator=RangeValidator(ge=0, le=1000), + ), + name='length_a', + description='Length of the a-axis of the unit cell.', + units='Å', + cif_handler=CifHandler(names=['_cell.length_a']), + ) + + @property + def length_a(self) -> Parameter: + """Parameter representing the a-axis length of the unit cell.""" + return self._length_a + + @length_a.setter + def length_a(self, v): + """Assign a raw value to length_a (validated internally).""" + self._length_a.value = v + + +# ---------------------- Example usage ---------------------- # + +if __name__ == '__main__': + c = Cell() + + c.length_a.value = 1.234 + log.info(f'c.length_a.value: {c.length_a.value}') + + c.length_a.value = -5.5 + log.info(f'c.length_a.value: {c.length_a.value}') + + c.length_a.value = 'xyz' + log.info(f'c.length_a.value: {c.length_a.value}') + + c.length_a.free = True + log.info(f'c.length_a.free: {c.length_a.free}') + + c.length_a.free = 'oops' + log.info(f'c.length_a.free: {c.length_a.free}') + + c.length_a = 'xyz' + log.info(f'c.length_a.value (after direct assign attempt): {c.length_a.value}') + + c_bad = Cell(length_a='xyz') + log.info(f'c_bad.length_a.value: {c_bad.length_a.value}') + + c_ok = Cell(length_a=2.5) + log.info(f'c_ok.length_a.value: {c_ok.length_a.value}') + + c_ok.length_a.description = 'read-only' + log.info(f'c_ok.length_a.description: {c_ok.length_a.description}') + + c_ok.length_a.aaa = 'aaa' + log.info(f'c_ok.length_a.aaa: {c_ok.length_a.aaa}') + + log.info(f'c_ok.length_a.bbb: {c_ok.length_a.bbb}') + + log.info(f'c_ok.length_a.fre: {c_ok.length_a.fre}') + + log.info(c.as_cif) + log.info(c.length_a.as_cif) + + log.info(c.length_a._cif_handler.uid) diff --git a/tmp/short7.py b/tmp/short7.py new file mode 100644 index 00000000..dfec2cd8 --- /dev/null +++ b/tmp/short7.py @@ -0,0 +1,129 @@ +import os +import tempfile + +from numpy.testing import assert_almost_equal + +from easydiffraction import Project +from easydiffraction import download_from_repository + +TEMP_DIR = tempfile.gettempdir() + + +def single_fit_neutron_pd_cwl_lbco() -> None: + # Create project + project = Project() + + # Set sample model + project.sample_models.add_minimal(name='lbco') + model = project.sample_models['lbco'] + model.space_group.name_h_m = 'P m -3 m' + model.cell.length_a = 3.88 + model.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + occupancy=0.5, + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + occupancy=0.5, + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.1, + ) + model.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=0.1, + ) + + # Set experiment + data_file = 'hrpt_lbco.xye' + download_from_repository(data_file, destination=TEMP_DIR) + project.experiments.add_from_data_path(name='hrpt', data_path=os.path.join(TEMP_DIR, data_file)) + expt = project.experiments['hrpt'] + expt.instrument.setup_wavelength = 1.494 + expt.instrument.calib_twotheta_offset = 0 + expt.peak.broad_gauss_u = 0.1 + expt.peak.broad_gauss_v = -0.1 + expt.peak.broad_gauss_w = 0.2 + expt.peak.broad_lorentz_x = 0 + expt.peak.broad_lorentz_y = 0 + expt.linked_phases.add_from_args(id='lbco', scale=5.0) + expt.background.add_from_args(x=10, y=170) + expt.background.add_from_args(x=165, y=170) + + expt.show_as_cif() + + # Prepare for fitting + project.analysis.current_calculator = 'cryspy' + project.analysis.current_minimizer = 'lmfit (leastsq)' + + # ------------ 1st fitting ------------ + + # Select fitting parameters + model.cell.length_a.free = True + expt.linked_phases['lbco'].scale.free = True + expt.instrument.calib_twotheta_offset.free = True + expt.background['10'].y.free = True + expt.background['165'].y.free = True + + # Perform fit + project.analysis.fit() + + # Compare fit quality + assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=5.79, decimal=1) + + # ------------ 2nd fitting ------------ + + # Select fitting parameters + expt.peak.broad_gauss_u.free = True + expt.peak.broad_gauss_v.free = True + expt.peak.broad_gauss_w.free = True + expt.peak.broad_lorentz_y.free = True + + # Perform fit + project.analysis.fit() + + # Compare fit quality + assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=4.41, decimal=1) + + # ------------ 3rd fitting ------------ + + # Select fitting parameters + model.atom_sites['La'].b_iso.free = True + model.atom_sites['Ba'].b_iso.free = True + model.atom_sites['Co'].b_iso.free = True + model.atom_sites['O'].b_iso.free = True + + # Perform fit + project.analysis.fit() + + # Show chart + project.plot_meas_vs_calc(expt_name='hrpt') + + # Compare fit quality + assert_almost_equal(project.analysis.fit_results.reduced_chi_square, desired=1.3, decimal=1) + + +single_fit_neutron_pd_cwl_lbco() diff --git a/tutorials-drafts/test_single-fit_pd-neut-tof_Si-DREAM_nc.py b/tmp/test_single-fit_pd-neut-tof_Si-DREAM_nc.py similarity index 59% rename from tutorials-drafts/test_single-fit_pd-neut-tof_Si-DREAM_nc.py rename to tmp/test_single-fit_pd-neut-tof_Si-DREAM_nc.py index 84090290..aa5bfee5 100644 --- a/tutorials-drafts/test_single-fit_pd-neut-tof_Si-DREAM_nc.py +++ b/tmp/test_single-fit_pd-neut-tof_Si-DREAM_nc.py @@ -2,6 +2,9 @@ # # Structure Refinement: Si (NCrystal sim), DREAM # %% + +import pytest + import easydiffraction as ed # %% [markdown] @@ -17,7 +20,7 @@ # ## Step 2: Sample Model # %% -project.sample_models.add(name='si') +project.sample_models.add_minimal(name='si') sample_model = project.sample_models['si'] # %% @@ -25,51 +28,64 @@ sample_model.space_group.it_coordinate_system_code = '1' # %% -sample_model.cell.length_a = 5.46872800 # 5.43146 +sample_model.cell.length_a = 5.46872800 # 5.43146 # %% -sample_model.atom_sites.add(label='Si', - type_symbol='Si', - fract_x=0., - fract_y=0., - fract_z=0.5, - wyckoff_letter='b', - b_iso=0.5) +import pathlib + +from easydiffraction.sample_models.categories.atom_sites import AtomSite + +sample_model.atom_sites.add( + AtomSite( + label='Si', + type_symbol='Si', + fract_x=0.0, + fract_y=0.0, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5, + ) +) # %% [markdown] # ## Step 3: Experiment # %% -#ed.download_from_repository('NOM_9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr', +# ed.download_from_repository('NOM_9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr', # branch='d-spacing', # destination='data') # %% -project.experiments.add(name='dream', - sample_form='powder', - beam_mode='time-of-flight', - radiation_probe='neutron', - scattering_type='bragg', - data_path='tutorials/data/DREAM_mantle_bc240_nist_cif_2.xye') - #data_path='tutorials/data/DREAM_mantle_bc240_nist_nc_2.xye') +data_path = 'tutorials/data/DREAM_mantle_bc240_nist_cif_2.xye' +if not pathlib.Path(data_path).exists(): # pragma: no cover - environment dependent + pytest.skip(f'Missing data file: {data_path}', allow_module_level=True) +project.experiments.add_from_data_path( + name='dream', + sample_form='powder', + beam_mode='time-of-flight', + radiation_probe='neutron', + scattering_type='bragg', + data_path=data_path, +) +# data_path='tutorials/data/DREAM_mantle_bc240_nist_nc_2.xye') experiment = project.experiments['dream'] # %% -experiment.instrument.setup_twotheta_bank = 90.20761742567521 # 144.845 # 90.20761742567521 +experiment.instrument.setup_twotheta_bank = 90.20761742567521 # 144.845 # 90.20761742567521 experiment.instrument.calib_d_to_tof_offset = 0.0 -experiment.instrument.calib_d_to_tof_linear = 27896.388403762866 # 7476.91 # 278963884037.62866 +experiment.instrument.calib_d_to_tof_linear = 27896.388403762866 # 7476.91 # 278963884037.62866 experiment.instrument.calib_d_to_tof_linear = 26935.57560870018 -experiment.instrument.calib_d_to_tof_quad = -0.00001 # -1.54 # -1.0 +experiment.instrument.calib_d_to_tof_quad = -0.00001 # -1.54 # -1.0 # %% experiment.peak_profile_type = 'pseudo-voigt * ikeda-carpenter' experiment.peak.broad_gauss_sigma_0 = 3.0 experiment.peak.broad_gauss_sigma_1 = 40.0 experiment.peak.broad_gauss_sigma_2 = 0.0 -experiment.peak.broad_mix_beta_0 = 0.024 # 0.04221 -experiment.peak.broad_mix_beta_1 = 0 # 0.00946 +experiment.peak.broad_mix_beta_0 = 0.024 # 0.04221 +experiment.peak.broad_mix_beta_1 = 0 # 0.00946 experiment.peak.asym_alpha_0 = 0.14 -experiment.peak.asym_alpha_1 = 0.0 # 0.5971 +experiment.peak.asym_alpha_1 = 0.0 # 0.5971 # %% experiment.background_type = 'line-segment' @@ -77,30 +93,30 @@ experiment.background.add(x=x, y=0.2) # %% -experiment.linked_phases.add('si', scale=1) +from easydiffraction.experiments.categories.linked_phases import LinkedPhase + +experiment.linked_phases.add(LinkedPhase(id='si', scale=1)) # %% [markdown] # ## Step 4: Analysis # %% -project.plot_meas_vs_calc(expt_name='dream', - show_residual=True) -#exit() +project.plot_meas_vs_calc(expt_name='dream', show_residual=True) +# exit() # %% -#sample_model.cell.length_a.free = True +# sample_model.cell.length_a.free = True experiment.linked_phases['si'].scale.free = True -#experiment.instrument.calib_d_to_tof_offset.free = True +# experiment.instrument.calib_d_to_tof_offset.free = True experiment.peak.broad_gauss_sigma_0.free = True experiment.peak.broad_gauss_sigma_1.free = True -#experiment.peak.broad_gauss_sigma_2.free = True +# experiment.peak.broad_gauss_sigma_2.free = True experiment.peak.broad_mix_beta_0.free = True -#experiment.peak.broad_mix_beta_1.free = True +# experiment.peak.broad_mix_beta_1.free = True experiment.peak.asym_alpha_0.free = True -#experiment.peak.asym_alpha_1.free = True - +# experiment.peak.asym_alpha_1.free = True project.analysis.fit() @@ -109,7 +125,6 @@ exit() - project.sample_models['lbco'].atom_sites['La'].b_iso.free = True project.sample_models['lbco'].atom_sites['Ba'].b_iso.free = True project.sample_models['lbco'].atom_sites['Co'].b_iso.free = True diff --git a/tools/gen_tests_scaffold.py b/tools/gen_tests_scaffold.py new file mode 100644 index 00000000..336bebb9 --- /dev/null +++ b/tools/gen_tests_scaffold.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Generate a one-to-one unit test scaffold mirroring src/ folder +structure. +- Creates tests/unit//test_.py for each Python + file in src +- Inserts a minimal, consistent pytest skeleton with TODO markers. + +Usage: + pixi run python tools/gen_tests_scaffold.py +""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SRC = ROOT / 'src' +TESTS_ROOT = ROOT / 'tests' / 'unit' + +IGNORED_DIRS = {'__pycache__'} +IGNORED_FILES = {'__init__.py'} + +HEADER = """# Auto-generated scaffold. Replace TODOs with concrete tests. +import pytest +import numpy as np + +# expected vs actual helpers + +def _assert_equal(expected, actual): + assert expected == actual + +""" + +TEMPLATE = """ +# Module under test: {module_import} + +# TODO: Replace with real, small tests per class/method. +# Keep names explicit: expected_*, actual_*; compare in a single assert. + +def test_module_import(): + import {module_import} as MUT + expected_module_name = "{module_import}" + actual_module_name = MUT.__name__ + _assert_equal(expected_module_name, actual_module_name) +""" + + +def module_import_from_path(py_path: Path) -> str: + rel = py_path.relative_to(SRC) + parts = list(rel.parts) + parts[-1] = parts[-1].removesuffix('.py') + # Build import path directly from src-relative parts; do not prefix + # with the top-level package name to avoid duplication when the + # first part is already the package (e.g., 'easydiffraction'). + return '.'.join(parts) + + +def ensure_file(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + # Always (re)write to keep scaffold in sync when the generator + # evolves + path.write_text(content) + + +def ensure_package_dirs(dir_path: Path) -> None: + """Ensure every directory from TESTS_ROOT to dir_path has an + __init__.py. + + This makes each folder a package so pytest assigns unique module + names and avoids import file mismatch when multiple files share the + same basename (e.g., test_base.py) across different subdirectories. + """ + # Walk from TESTS_ROOT down to dir_path, creating __init__.py at + # each level + dir_path.mkdir(parents=True, exist_ok=True) + current = TESTS_ROOT + # If dir_path is the same as TESTS_ROOT, the loop will be skipped, + # but we still want to ensure __init__.py at TESTS_ROOT + for part in dir_path.relative_to(TESTS_ROOT).parts: + (current / '__init__.py').touch(exist_ok=True) + current = current / part + # Ensure the final directory also has __init__.py + (current / '__init__.py').touch(exist_ok=True) + + +def main(): + for py in SRC.rglob('*.py'): + if py.name in IGNORED_FILES: + continue + if any(p in IGNORED_DIRS for p in py.parts): + continue + module_import = module_import_from_path(py) + rel_dir = py.parent.relative_to(SRC) + test_dir = TESTS_ROOT / rel_dir + # Ensure package __init__.py files exist to avoid name + # collisions + ensure_package_dirs(test_dir) + test_file = test_dir / f'test_{py.stem}.py' + content = HEADER + TEMPLATE.format(module_import=module_import) + ensure_file(test_file, content) + print(f'Scaffold created/updated under: {TESTS_ROOT}') + + +if __name__ == '__main__': + main() diff --git a/tools/generate_package_docs.py b/tools/generate_package_docs.py new file mode 100644 index 00000000..671a4f0e --- /dev/null +++ b/tools/generate_package_docs.py @@ -0,0 +1,172 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Generate project package structure markdown files. + +Outputs two docs under docs/architecture/: + - package-structure-short.md (folders/files only) + - package-structure-full.md (folders/files and classes) + +Run (from repo root): + pixi run python tools/generate_package_docs.py +""" + +from __future__ import annotations + +import ast +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path +from typing import List + +REPO_ROOT = Path(__file__).resolve().parents[1] +SRC_ROOT = REPO_ROOT / 'src' / 'easydiffraction' +DOCS_OUT_DIR = REPO_ROOT / 'docs' / 'architecture' + + +IGNORE_DIRS = { + '__pycache__', + '.pytest_cache', + '.mypy_cache', + '.ruff_cache', + '.ipynb_checkpoints', +} + + +@dataclass +class Node: + name: str + path: Path + type: str # 'dir' | 'file' + children: List['Node'] = field(default_factory=list) + classes: List[str] = field(default_factory=list) + + +def parse_classes(py_path: Path) -> List[str]: + try: + src = py_path.read_text(encoding='utf-8') + except Exception: + return [] + try: + tree = ast.parse(src) + except Exception: + return [] + classes: List[str] = [] + for node in tree.body: + if isinstance(node, ast.ClassDef): + classes.append(node.name) + return classes + + +def build_tree(root: Path) -> Node: + def _walk(p: Path) -> Node: + if p.is_dir(): + node = Node(name=p.name, path=p, type='dir') + try: + entries = sorted(p.iterdir(), key=lambda q: (q.is_file(), q.name.lower())) + except PermissionError: + entries = [] + for child in entries: + if child.name in IGNORE_DIRS: + continue + if child.is_dir(): + node.children.append(_walk(child)) + elif child.suffix == '.py': + file_node = Node(name=child.name, path=child, type='file') + file_node.classes = parse_classes(child) + node.children.append(file_node) + return node + else: + n = Node(name=p.name, path=p, type='file') + n.classes = parse_classes(p) if p.suffix == '.py' else [] + return n + + return _walk(root) + + +def _branch(prefix: str, is_last: bool) -> str: + return f'{prefix}{"└── " if is_last else "├── "}' + + +def render_short(root: Node) -> List[str]: + lines: List[str] = [] + lines.append(f'📦 {root.name}') + + def _render(node: Node, prefix: str = '') -> None: + for idx, child in enumerate(node.children): + is_last = idx == len(node.children) - 1 + line_prefix = _branch(prefix, is_last) + if child.type == 'dir': + lines.append(f'{line_prefix}📁 {child.name}') + _render(child, prefix + (' ' if is_last else '│ ')) + else: + lines.append(f'{line_prefix}📄 {child.name}') + + _render(root) + return lines + + +def render_full(root: Node) -> List[str]: + lines: List[str] = [] + lines.append(f'📦 {root.name}') + + def _render(node: Node, prefix: str = '') -> None: + for idx, child in enumerate(node.children): + is_last = idx == len(node.children) - 1 + line_prefix = _branch(prefix, is_last) + if child.type == 'dir': + lines.append(f'{line_prefix}📁 {child.name}') + _render(child, prefix + (' ' if is_last else '│ ')) + else: + lines.append(f'{line_prefix}📄 {child.name}') + # Classes under file + for c_idx, cls in enumerate(child.classes): + c_last = c_idx == len(child.classes) - 1 + sub_prefix = prefix + (' ' if is_last else '│ ') + lines.append(f'{_branch(sub_prefix, c_last)}🏷️ class {cls}') + + _render(root) + return lines + + +def write_markdown(short_lines: List[str], full_lines: List[str]) -> None: + DOCS_OUT_DIR.mkdir(parents=True, exist_ok=True) + + short_md = DOCS_OUT_DIR / 'package-structure-short.md' + full_md = DOCS_OUT_DIR / 'package-structure-full.md' + + short_content = [ + '# Package Structure (short)', + '', + '```', + *short_lines, + '```', + '', + ] + full_content = [ + '# Package Structure (full)', + '', + '```', + *full_lines, + '```', + '', + ] + + short_md.write_text('\n'.join(short_content), encoding='utf-8') + full_md.write_text('\n'.join(full_content), encoding='utf-8') + + print(f'Wrote: {short_md.relative_to(REPO_ROOT)}') + print(f'Wrote: {full_md.relative_to(REPO_ROOT)}') + + +def main() -> None: + if not SRC_ROOT.exists(): + raise SystemExit(f'Source root not found: {SRC_ROOT}') + root = build_tree(SRC_ROOT) + short_lines = render_short(root) + full_lines = render_full(root) + write_markdown(short_lines, full_lines) + + +if __name__ == '__main__': + main() diff --git a/tools/naming_check.py b/tools/naming_check.py new file mode 100644 index 00000000..8160775c --- /dev/null +++ b/tools/naming_check.py @@ -0,0 +1,117 @@ +"""Check naming consistency across the EasyDiffraction source tree. + +Rules enforced: +- Concrete implementations: .py in s/ directories → + +- Abstract bases: _base.py → Base +- Factories: _factory.py → Factory +- Enums: _enum.py → Enum +- Mixins: _mixins.py → Mixin (or multiple classes) +- Directories like fit_support/, calculators/, minimizers/, etc. are + scanned recursively. + +Exit code 1 if any mismatches are found. +""" + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] / 'src' / 'easydiffraction' +PATTERNS = { + '_base': r'(?P[A-Z][a-zA-Z0-9]*)Base', + '_factory': r'(?P[A-Z][a-zA-Z0-9]*)Factory', + '_enum': r'(?P[A-Z][a-zA-Z0-9]*)Enum', + '_mixins?': r'(?P[A-Z][a-zA-Z0-9]*)Mixin', +} + + +def extract_classes(file_path: Path): + """Return a list of class names defined in the file.""" + content = file_path.read_text(encoding='utf-8', errors='ignore') + return re.findall(r'^\s*class\s+([A-Z][A-Za-z0-9_]*)', content, flags=re.MULTILINE) + + +def snake_to_pascal(snake: str) -> str: + """Convert 'crysfml_calculator' → 'CrysfmlCalculator'.""" + return ''.join(part.capitalize() for part in snake.split('_')) + + +def check_file(file_path: Path): + """Validate naming conventions for a single file.""" + rel = file_path.relative_to(ROOT) + stem = file_path.stem + classes = extract_classes(file_path) + problems = [] + + # Ignore __init__.py and private modules + if stem.startswith('__'): + return [] + + # Expected class names + expected = [] + + # Handle base/factory/enum/mixin + for key, pattern in PATTERNS.items(): + if stem.endswith(key): + role = stem.replace(key, '') + expected.append( + re.compile(pattern.replace('(?P', f'(?P{snake_to_pascal(role)}')) + ) + break + else: + # Default: if file is in a known role directory, use that role + parent = file_path.parent.name + known_roles = ('calculators', 'minimizers', 'plotters', 'instruments', 'peaks') + if parent in known_roles: + role = snake_to_pascal(parent[:-1]) # remove trailing 's' for role + if stem == 'base': + expected_name = f'{role}Base' + elif stem == 'factory': + expected_name = f'{role}Factory' + else: + variant = snake_to_pascal(stem) + expected_name = f'{variant}{role}' + expected.append(re.compile(f'^{expected_name}$')) + else: + # Otherwise, expect class name to match the PascalCase file + # stem + expected_name = snake_to_pascal(stem) + expected.append(re.compile(f'^{expected_name}$')) + + # Compare classes + if not classes: + problems.append(f'{rel}: No class definitions found') + return problems + + match_found = False + for exp in expected: + for cls in classes: + if exp.match(cls): + match_found = True + break + if not match_found: + problems.append(f'{rel}: Unexpected class name(s): {", ".join(classes)}') + + return problems + + +def main(): + problems = [] + for file_path in ROOT.rglob('*.py'): + if any(skip in file_path.parts for skip in ('__pycache__', 'venv', '.venv', 'tests')): + continue + problems.extend(check_file(file_path)) + + if problems: + print('❌ Naming convention violations found:\n') + for p in problems: + print(' -', p) + print(f'\nTotal issues: {len(problems)}') + sys.exit(1) + + print('✅ All file/class names follow naming conventions.') + + +if __name__ == '__main__': + main() diff --git a/tools/test_scripts.py b/tools/test_scripts.py index be4320ed..88747b76 100644 --- a/tools/test_scripts.py +++ b/tools/test_scripts.py @@ -11,7 +11,12 @@ import pytest -TUTORIALS = list(Path('tutorials').rglob('*.py')) +# Mark this module as 'functional' so it's excluded by default +# (see pytest.ini) +pytestmark = pytest.mark.functional + +# Discover tutorial scripts, excluding temporary checkpoint files +TUTORIALS = [p for p in Path('tutorials').rglob('*.py') if '.ipynb_checkpoints' not in p.parts] @pytest.mark.parametrize('script_path', TUTORIALS) diff --git a/tools/update_spdx.py b/tools/update_spdx.py index e05e1174..ebfe7299 100644 --- a/tools/update_spdx.py +++ b/tools/update_spdx.py @@ -19,35 +19,64 @@ def update_spdx_header(file_path: Path): # Use Path.open to satisfy lint rule PTH123. with file_path.open('r', encoding='utf-8') as f: - lines = f.readlines() + original_lines = f.readlines() - # Patterns to match existing SPDX lines + # Regexes for SPDX lines copy_re = re.compile(r'^#\s*SPDX-FileCopyrightText:.*$') lic_re = re.compile(r'^#\s*SPDX-License-Identifier:.*$') - new_lines = [] - found_copy = False - found_lic = False - - for line in lines: - if copy_re.match(line): - new_lines.append(COPYRIGHT_TEXT + '\n') - found_copy = True - elif lic_re.match(line): - new_lines.append(LICENSE_TEXT + '\n') - found_lic = True - else: - new_lines.append(line) - - # If not found, insert at top - insert_pos = 0 - if not found_copy: - new_lines.insert(insert_pos, COPYRIGHT_TEXT + '\n') - insert_pos += 1 - if not found_lic: - new_lines.insert(insert_pos, LICENSE_TEXT + '\n') - insert_pos += 1 - new_lines.insert(insert_pos, '\n') + # 1) Preserve any leading shebang / coding cookie lines + prefix = [] + body_start = 0 + if original_lines: + # Shebang line like "#!/usr/bin/env python3" + if original_lines[0].startswith('#!'): + prefix.append(original_lines[0]) + body_start = 1 + # PEP 263 coding cookie on first or second line + # e.g. "# -*- coding: utf-8 -*-" or "# coding: utf-8" + for _ in range(2): # at most one more line to inspect + if body_start < len(original_lines): + line = original_lines[body_start] + if re.match(r'^#.*coding[:=]\s*[-\w.]+', line): + prefix.append(line) + body_start += 1 + else: + break + + # 2) Work on the remaining body + body = original_lines[body_start:] + + # Remove any existing SPDX lines anywhere in the body + body = [ln for ln in body if not (copy_re.match(ln) or lic_re.match(ln))] + + # Strip leading blank lines in the body so header is tight + while body and not body[0].strip(): + body.pop(0) + + # 3) Build canonical SPDX block: two lines + exactly one blank + spdx_block = [ + COPYRIGHT_TEXT + '\n', + LICENSE_TEXT + '\n', + '\n', + ] + + # 4) New content: prefix + SPDX + body + new_lines = prefix + spdx_block + body + + # 5) Normalize: collapse any extra blank lines immediately after + # LICENSE to exactly one. This keeps the script idempotent. + # Find the index of LICENSE we just inserted (prefix may be 0, 1, + # or 2 lines) + lic_idx = len(prefix) + 1 # spdx_block[1] is the license line + # Ensure exactly one blank line after LICENSE + # Remove all blank lines after lic_idx, then insert a single blank. + j = lic_idx + 1 + # Remove any number of blank lines following + while j < len(new_lines) and not new_lines[j].strip(): + new_lines.pop(j) + # Insert exactly one blank line at this position + new_lines.insert(j, '\n') with file_path.open('w', encoding='utf-8') as f: f.writelines(new_lines) @@ -55,13 +84,11 @@ def update_spdx_header(file_path: Path): def main(): """Recursively update or insert SPDX headers in all Python files - under the 'src' directory, skipping files located in virtual - environment folders ('venv' or '.venv'). + under the 'src' and 'tests' directories. """ - for py_file in Path('src').rglob('*.py'): - if 'venv' in py_file.parts or '.venv' in py_file.parts: - continue - update_spdx_header(py_file) + for base_dir in ('src', 'tests'): + for py_file in Path(base_dir).rglob('*.py'): + update_spdx_header(py_file) if __name__ == '__main__': diff --git a/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py b/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py index f3d064e0..4cfd0d70 100644 --- a/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py +++ b/tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.py @@ -15,9 +15,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -29,7 +29,7 @@ # #### Create Sample Model # %% -model = SampleModel('pbso4') +model = SampleModelFactory.create(name='pbso4') # %% [markdown] # #### Set Space Group @@ -49,11 +49,51 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Pb', 'Pb', 0.1876, 0.25, 0.167, b_iso=1.37) -model.atom_sites.add('S', 'S', 0.0654, 0.25, 0.684, b_iso=0.3777) -model.atom_sites.add('O1', 'O', 0.9082, 0.25, 0.5954, b_iso=1.9764) -model.atom_sites.add('O2', 'O', 0.1935, 0.25, 0.5432, b_iso=1.4456) -model.atom_sites.add('O3', 'O', 0.0811, 0.0272, 0.8086, b_iso=1.2822) +model.atom_sites.add_from_args( + label='Pb', + type_symbol='Pb', + fract_x=0.1876, + fract_y=0.25, + fract_z=0.167, + wyckoff_letter='c', + b_iso=1.37, +) +model.atom_sites.add_from_args( + label='S', + type_symbol='S', + fract_x=0.0654, + fract_y=0.25, + fract_z=0.684, + wyckoff_letter='c', + b_iso=0.3777, +) +model.atom_sites.add_from_args( + label='O1', + type_symbol='O', + fract_x=0.9082, + fract_y=0.25, + fract_z=0.5954, + wyckoff_letter='c', + b_iso=1.9764, +) +model.atom_sites.add_from_args( + label='O2', + type_symbol='O', + fract_x=0.1935, + fract_y=0.25, + fract_z=0.5432, + wyckoff_letter='c', + b_iso=1.4456, +) +model.atom_sites.add_from_args( + label='O3', + type_symbol='O', + fract_x=0.0811, + fract_y=0.0272, + fract_z=0.8086, + wyckoff_letter='d', + b_iso=1.2822, +) # %% [markdown] @@ -73,7 +113,7 @@ # #### Create Experiment # %% -expt1 = Experiment( +expt1 = ExperimentFactory.create( name='npd', data_path='data/d1a_pbso4.dat', radiation_probe='neutron', @@ -119,13 +159,13 @@ (120.0, 244.4525), (153.0, 226.0595), ]: - expt1.background.add(x, y) + expt1.background.add_from_args(x=x, y=y) # %% [markdown] # #### Set Linked Phases # %% -expt1.linked_phases.add('pbso4', scale=1.5) +expt1.linked_phases.add_from_args(id='pbso4', scale=1.5) # %% [markdown] # ### Experiment 2: xrd @@ -139,7 +179,7 @@ # #### Create Experiment # %% -expt2 = Experiment( +expt2 = ExperimentFactory.create( name='xrd', data_path='data/lab_pbso4.dat', radiation_probe='xray', @@ -183,13 +223,13 @@ (4, 54.552), (5, -20.661), ]: - expt2.background.add(x, y) + expt2.background.add_from_args(order=x, coef=y) # %% [markdown] # #### Set Linked Phases # %% -expt2.linked_phases.add('pbso4', scale=0.001) +expt2.linked_phases.add_from_args(id='pbso4', scale=0.001) # %% [markdown] # ## Define Project diff --git a/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py b/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py index 94e9c62f..3db13fd6 100644 --- a/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py +++ b/tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py @@ -91,7 +91,7 @@ # #### Add Sample Model # %% -project.sample_models.add(name='lbco') +project.sample_models.add_minimal(name='lbco') # %% [markdown] # #### Show Defined Sample Models @@ -126,7 +126,7 @@ # Add atom sites to the sample model. # %% -project.sample_models['lbco'].atom_sites.add( +project.sample_models['lbco'].atom_sites.add_from_args( label='La', type_symbol='La', fract_x=0, @@ -136,7 +136,7 @@ b_iso=0.5, occupancy=0.5, ) -project.sample_models['lbco'].atom_sites.add( +project.sample_models['lbco'].atom_sites.add_from_args( label='Ba', type_symbol='Ba', fract_x=0, @@ -146,7 +146,7 @@ b_iso=0.5, occupancy=0.5, ) -project.sample_models['lbco'].atom_sites.add( +project.sample_models['lbco'].atom_sites.add_from_args( label='Co', type_symbol='Co', fract_x=0.5, @@ -155,7 +155,7 @@ wyckoff_letter='b', b_iso=0.5, ) -project.sample_models['lbco'].atom_sites.add( +project.sample_models['lbco'].atom_sites.add_from_args( label='O', type_symbol='O', fract_x=0, @@ -295,11 +295,11 @@ # Add background points. # %% -project.experiments['hrpt'].background.add(x=10, y=170) -project.experiments['hrpt'].background.add(x=30, y=170) -project.experiments['hrpt'].background.add(x=50, y=170) -project.experiments['hrpt'].background.add(x=110, y=170) -project.experiments['hrpt'].background.add(x=165, y=170) +project.experiments['hrpt'].background.add_from_args(x=10, y=170) +project.experiments['hrpt'].background.add_from_args(x=30, y=170) +project.experiments['hrpt'].background.add_from_args(x=50, y=170) +project.experiments['hrpt'].background.add_from_args(x=110, y=170) +project.experiments['hrpt'].background.add_from_args(x=165, y=170) # %% [markdown] # Show current background points. @@ -313,7 +313,7 @@ # Link the sample model defined in the previous step to the experiment. # %% -project.experiments['hrpt'].linked_phases.add(id='lbco', scale=10.0) +project.experiments['hrpt'].linked_phases.add_from_args(id='lbco', scale=10.0) # %% [markdown] # #### Show Experiment as CIF @@ -564,11 +564,11 @@ # Set aliases for parameters. # %% -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='biso_La', param_uid=project.sample_models['lbco'].atom_sites['La'].b_iso.uid, ) -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='biso_Ba', param_uid=project.sample_models['lbco'].atom_sites['Ba'].b_iso.uid, ) @@ -577,7 +577,7 @@ # Set constraints. # %% -project.analysis.constraints.add(lhs_alias='biso_Ba', rhs_expr='biso_La') +project.analysis.constraints.add_from_args(lhs_alias='biso_Ba', rhs_expr='biso_La') # %% [markdown] # Show defined constraints. @@ -632,11 +632,11 @@ # Set more aliases for parameters. # %% -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='occ_La', param_uid=project.sample_models['lbco'].atom_sites['La'].occupancy.uid, ) -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='occ_Ba', param_uid=project.sample_models['lbco'].atom_sites['Ba'].occupancy.uid, ) @@ -645,7 +645,7 @@ # Set more constraints. # %% -project.analysis.constraints.add( +project.analysis.constraints.add_from_args( lhs_alias='occ_Ba', rhs_expr='1 - occ_La', ) @@ -703,5 +703,6 @@ # %% [markdown] # #### Show Project Summary + # %% project.summary.show_report() diff --git a/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py b/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py index 95a7f9f0..5c643747 100644 --- a/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py +++ b/tutorials/cryst-struct_pd-neut-cwl_CoSiO4-D20.py @@ -9,9 +9,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -23,7 +23,7 @@ # #### Create Sample Model # %% -model = SampleModel('cosio') +model = SampleModelFactory.create(name='cosio') # %% [markdown] # #### Set Space Group @@ -44,12 +44,54 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Co1', 'Co', 0, 0, 0, wyckoff_letter='a', b_iso=0.5) -model.atom_sites.add('Co2', 'Co', 0.279, 0.25, 0.985, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('Si', 'Si', 0.094, 0.25, 0.429, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('O1', 'O', 0.091, 0.25, 0.771, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('O2', 'O', 0.448, 0.25, 0.217, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('O3', 'O', 0.164, 0.032, 0.28, wyckoff_letter='d', b_iso=0.5) +model.atom_sites.add_from_args( + label='Co1', type_symbol='Co', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=0.5 +) +model.atom_sites.add_from_args( + label='Co2', + type_symbol='Co', + fract_x=0.279, + fract_y=0.25, + fract_z=0.985, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.094, + fract_y=0.25, + fract_z=0.429, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='O1', + type_symbol='O', + fract_x=0.091, + fract_y=0.25, + fract_z=0.771, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='O2', + type_symbol='O', + fract_x=0.448, + fract_y=0.25, + fract_z=0.217, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='O3', + type_symbol='O', + fract_x=0.164, + fract_y=0.032, + fract_z=0.28, + wyckoff_letter='d', + b_iso=0.5, +) # %% [markdown] # #### Symmetry Constraints @@ -86,7 +128,7 @@ # #### Create Experiment # %% -expt = Experiment(name='d20', data_path='data/co2sio4_d20.xye') +expt = ExperimentFactory.create(name='d20', data_path='data/co2sio4_d20.xye') # %% [markdown] # #### Set Instrument @@ -107,26 +149,26 @@ # #### Set Background # %% -expt.background.add(x=8, y=500) -expt.background.add(x=9, y=500) -expt.background.add(x=10, y=500) -expt.background.add(x=11, y=500) -expt.background.add(x=12, y=500) -expt.background.add(x=15, y=500) -expt.background.add(x=25, y=500) -expt.background.add(x=30, y=500) -expt.background.add(x=50, y=500) -expt.background.add(x=70, y=500) -expt.background.add(x=90, y=500) -expt.background.add(x=110, y=500) -expt.background.add(x=130, y=500) -expt.background.add(x=150, y=500) +expt.background.add_from_args(x=8, y=500) +expt.background.add_from_args(x=9, y=500) +expt.background.add_from_args(x=10, y=500) +expt.background.add_from_args(x=11, y=500) +expt.background.add_from_args(x=12, y=500) +expt.background.add_from_args(x=15, y=500) +expt.background.add_from_args(x=25, y=500) +expt.background.add_from_args(x=30, y=500) +expt.background.add_from_args(x=50, y=500) +expt.background.add_from_args(x=70, y=500) +expt.background.add_from_args(x=90, y=500) +expt.background.add_from_args(x=110, y=500) +expt.background.add_from_args(x=130, y=500) +expt.background.add_from_args(x=150, y=500) # %% [markdown] # #### Set Linked Phases # %% -expt.linked_phases.add('cosio', scale=1.0) +expt.linked_phases.add_from_args(id='cosio', scale=1.0) # %% [markdown] # ## Define Project @@ -229,11 +271,11 @@ # Set aliases for parameters. # %% -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='biso_Co1', param_uid=project.sample_models['cosio'].atom_sites['Co1'].b_iso.uid, ) -project.analysis.aliases.add( +project.analysis.aliases.add_from_args( label='biso_Co2', param_uid=project.sample_models['cosio'].atom_sites['Co2'].b_iso.uid, ) @@ -242,7 +284,7 @@ # Set constraints. # %% -project.analysis.constraints.add( +project.analysis.constraints.add_from_args( lhs_alias='biso_Co2', rhs_expr='biso_Co1', ) diff --git a/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py b/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py index 515445c2..641a9c9c 100644 --- a/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py +++ b/tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py @@ -9,9 +9,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -23,7 +23,7 @@ # #### Create Sample Model # %% -model = SampleModel('hs') +model = SampleModelFactory.create(name='hs') # %% [markdown] # #### Set Space Group @@ -35,6 +35,7 @@ # %% [markdown] # #### Set Unit Cell + # %% model.cell.length_a = 6.9 model.cell.length_c = 14.1 @@ -43,11 +44,51 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Zn', 'Zn', 0, 0, 0.5, wyckoff_letter='b', b_iso=0.5) -model.atom_sites.add('Cu', 'Cu', 0.5, 0, 0, wyckoff_letter='e', b_iso=0.5) -model.atom_sites.add('O', 'O', 0.21, -0.21, 0.06, wyckoff_letter='h', b_iso=0.5) -model.atom_sites.add('Cl', 'Cl', 0, 0, 0.197, wyckoff_letter='c', b_iso=0.5) -model.atom_sites.add('H', '2H', 0.13, -0.13, 0.08, wyckoff_letter='h', b_iso=0.5) +model.atom_sites.add_from_args( + label='Zn', + type_symbol='Zn', + fract_x=0, + fract_y=0, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='Cu', + type_symbol='Cu', + fract_x=0.5, + fract_y=0, + fract_z=0, + wyckoff_letter='e', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0.21, + fract_y=-0.21, + fract_z=0.06, + wyckoff_letter='h', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='Cl', + type_symbol='Cl', + fract_x=0, + fract_y=0, + fract_z=0.197, + wyckoff_letter='c', + b_iso=0.5, +) +model.atom_sites.add_from_args( + label='H', + type_symbol='2H', + fract_x=0.13, + fract_y=-0.13, + fract_z=0.08, + wyckoff_letter='h', + b_iso=0.5, +) # %% [markdown] # #### Symmetry constraints @@ -84,7 +125,7 @@ # #### Create Experiment # %% -expt = Experiment(name='hrpt', data_path='data/hrpt_hs.xye') +expt = ExperimentFactory.create(name='hrpt', data_path='data/hrpt_hs.xye') # %% [markdown] # #### Set Instrument @@ -107,21 +148,21 @@ # #### Set Background # %% -expt.background.add(x=4.4196, y=500) -expt.background.add(x=6.6207, y=500) -expt.background.add(x=10.4918, y=500) -expt.background.add(x=15.4634, y=500) -expt.background.add(x=45.6041, y=500) -expt.background.add(x=74.6844, y=500) -expt.background.add(x=103.4187, y=500) -expt.background.add(x=121.6311, y=500) -expt.background.add(x=159.4116, y=500) +expt.background.add_from_args(x=4.4196, y=500) +expt.background.add_from_args(x=6.6207, y=500) +expt.background.add_from_args(x=10.4918, y=500) +expt.background.add_from_args(x=15.4634, y=500) +expt.background.add_from_args(x=45.6041, y=500) +expt.background.add_from_args(x=74.6844, y=500) +expt.background.add_from_args(x=103.4187, y=500) +expt.background.add_from_args(x=121.6311, y=500) +expt.background.add_from_args(x=159.4116, y=500) # %% [markdown] # #### Set Linked Phases # %% -expt.linked_phases.add('hs', scale=0.5) +expt.linked_phases.add_from_args(id='hs', scale=0.5) # %% [markdown] # ## Define Project diff --git a/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py b/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py index 0edbdbb0..0a9ef1fb 100644 --- a/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py +++ b/tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py @@ -9,9 +9,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -23,7 +23,7 @@ # #### Create Sample Model # %% -model = SampleModel('si') +model = SampleModelFactory.create(name='si') # %% [markdown] # #### Set Space Group @@ -42,7 +42,14 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Si', 'Si', 0.125, 0.125, 0.125, b_iso=0.5) +model.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.125, + fract_y=0.125, + fract_z=0.125, + b_iso=0.5, +) # %% [markdown] # ## Define Experiment @@ -59,7 +66,9 @@ # #### Create Experiment # %% -expt = Experiment(name='sepd', data_path='data/sepd_si.xye', beam_mode='time-of-flight') +expt = ExperimentFactory.create( + name='sepd', data_path='data/sepd_si.xye', beam_mode='time-of-flight' +) # %% [markdown] # #### Set Instrument @@ -94,13 +103,13 @@ # %% expt.background_type = 'line-segment' for x in range(0, 35000, 5000): - expt.background.add(x=x, y=200) + expt.background.add_from_args(x=x, y=200) # %% [markdown] # #### Set Linked Phases # %% -expt.linked_phases.add('si', scale=10.0) +expt.linked_phases.add_from_args(id='si', scale=10.0) # %% [markdown] # ## Define Project diff --git a/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py b/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py index 4394121a..aacf644d 100644 --- a/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py +++ b/tutorials/cryst-struct_pd-neut-tof_multidata_NCAF-WISH.py @@ -12,9 +12,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -26,7 +26,7 @@ # #### Create Sample Model # %% -model = SampleModel('ncaf') +model = SampleModelFactory.create(name='ncaf') # %% [markdown] # #### Set Space Group @@ -45,12 +45,60 @@ # #### Set Atom Sites # %% -model.atom_sites.add('Ca', 'Ca', 0.4663, 0.0, 0.25, wyckoff_letter='b', b_iso=0.92) -model.atom_sites.add('Al', 'Al', 0.2521, 0.2521, 0.2521, wyckoff_letter='a', b_iso=0.73) -model.atom_sites.add('Na', 'Na', 0.0851, 0.0851, 0.0851, wyckoff_letter='a', b_iso=2.08) -model.atom_sites.add('F1', 'F', 0.1377, 0.3054, 0.1195, wyckoff_letter='c', b_iso=0.90) -model.atom_sites.add('F2', 'F', 0.3625, 0.3633, 0.1867, wyckoff_letter='c', b_iso=1.37) -model.atom_sites.add('F3', 'F', 0.4612, 0.4612, 0.4612, wyckoff_letter='a', b_iso=0.88) +model.atom_sites.add_from_args( + label='Ca', + type_symbol='Ca', + fract_x=0.4663, + fract_y=0.0, + fract_z=0.25, + wyckoff_letter='b', + b_iso=0.92, +) +model.atom_sites.add_from_args( + label='Al', + type_symbol='Al', + fract_x=0.2521, + fract_y=0.2521, + fract_z=0.2521, + wyckoff_letter='a', + b_iso=0.73, +) +model.atom_sites.add_from_args( + label='Na', + type_symbol='Na', + fract_x=0.0851, + fract_y=0.0851, + fract_z=0.0851, + wyckoff_letter='a', + b_iso=2.08, +) +model.atom_sites.add_from_args( + label='F1', + type_symbol='F', + fract_x=0.1377, + fract_y=0.3054, + fract_z=0.1195, + wyckoff_letter='c', + b_iso=0.90, +) +model.atom_sites.add_from_args( + label='F2', + type_symbol='F', + fract_x=0.3625, + fract_y=0.3633, + fract_z=0.1867, + wyckoff_letter='c', + b_iso=1.37, +) +model.atom_sites.add_from_args( + label='F3', + type_symbol='F', + fract_x=0.4612, + fract_y=0.4612, + fract_z=0.4612, + wyckoff_letter='a', + b_iso=0.88, +) # %% [markdown] # ## Define Experiment @@ -76,14 +124,14 @@ # #### Create Experiment # %% -expt56 = Experiment( +expt56 = ExperimentFactory.create( name='wish_5_6', data_path='data/wish_ncaf_5_6.xys', beam_mode='time-of-flight', ) # %% -expt47 = Experiment( +expt47 = ExperimentFactory.create( name='wish_4_7', data_path='data/wish_ncaf_4_7.xys', beam_mode='time-of-flight', @@ -160,7 +208,7 @@ (91958, 268), (102712, 262), ]: - expt56.background.add(x, y) + expt56.background.add_from_args(x=x, y=y) # %% expt47.background_type = 'line-segment' @@ -193,27 +241,27 @@ (92770, 255), (101524, 260), ]: - expt47.background.add(x, y) + expt47.background.add_from_args(x=x, y=y) # %% [markdown] # #### Set Linked Phases # %% -expt56.linked_phases.add('ncaf', scale=1.0) +expt56.linked_phases.add_from_args(id='ncaf', scale=1.0) # %% -expt47.linked_phases.add('ncaf', scale=2.0) +expt47.linked_phases.add_from_args(id='ncaf', scale=2.0) # %% [markdown] # #### Set Excluded Regions # %% -expt56.excluded_regions.add(start=0, end=10010) -expt56.excluded_regions.add(start=100010, end=200000) +expt56.excluded_regions.add_from_args(start=0, end=10010) +expt56.excluded_regions.add_from_args(start=100010, end=200000) # %% -expt47.excluded_regions.add(start=0, end=10006) -expt47.excluded_regions.add(start=100004, end=200000) +expt47.excluded_regions.add_from_args(start=0, end=10006) +expt47.excluded_regions.add_from_args(start=100004, end=200000) # %% [markdown] # ## Define Project diff --git a/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py b/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py index a334a77f..5db8150b 100644 --- a/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py +++ b/tutorials/cryst-struct_pd-neut-tof_multiphase-LBCO-Si_McStas.py @@ -9,9 +9,9 @@ # ## Import Library # %% -from easydiffraction import Experiment +from easydiffraction import ExperimentFactory from easydiffraction import Project -from easydiffraction import SampleModel +from easydiffraction import SampleModelFactory from easydiffraction import download_from_repository # %% [markdown] @@ -23,7 +23,7 @@ # ### Create Sample Model 1: LBCO # %% -model_1 = SampleModel('lbco') +model_1 = SampleModelFactory.create(name='lbco') # %% [markdown] # #### Set Space Group @@ -42,16 +42,50 @@ # #### Set Atom Sites # %% -model_1.atom_sites.add('La', 'La', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5) -model_1.atom_sites.add('Ba', 'Ba', 0, 0, 0, wyckoff_letter='a', b_iso=0.2, occupancy=0.5) -model_1.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, wyckoff_letter='b', b_iso=0.2567) -model_1.atom_sites.add('O', 'O', 0, 0.5, 0.5, wyckoff_letter='c', b_iso=1.4041) +model_1.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.2, + occupancy=0.5, +) +model_1.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.2, + occupancy=0.5, +) +model_1.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.2567, +) +model_1.atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=1.4041, +) # %% [markdown] # ### Create Sample Model 2: Si # %% -model_2 = SampleModel('si') +model_2 = SampleModelFactory.create(name='si') # %% [markdown] # #### Set Space Group @@ -70,12 +104,12 @@ # #### Set Atom Sites # %% -model_2.atom_sites.add( - 'Si', - 'Si', - 0.0, - 0.0, - 0.0, +model_2.atom_sites.add_from_args( + label='Si', + type_symbol='Si', + fract_x=0.0, + fract_y=0.0, + fract_z=0.0, wyckoff_letter='a', b_iso=0.0, ) @@ -95,7 +129,7 @@ # #### Create Experiment # %% -experiment = Experiment( +experiment = ExperimentFactory.create( name='mcstas', data_path='data/mcstas_lbco-si.xye', sample_form='powder', @@ -139,26 +173,26 @@ # Add background points. # %% -experiment.background.add(x=45000, y=0.2) -experiment.background.add(x=50000, y=0.2) -experiment.background.add(x=55000, y=0.2) -experiment.background.add(x=65000, y=0.2) -experiment.background.add(x=70000, y=0.2) -experiment.background.add(x=75000, y=0.2) -experiment.background.add(x=80000, y=0.2) -experiment.background.add(x=85000, y=0.2) -experiment.background.add(x=90000, y=0.2) -experiment.background.add(x=95000, y=0.2) -experiment.background.add(x=100000, y=0.2) -experiment.background.add(x=105000, y=0.2) -experiment.background.add(x=110000, y=0.2) +experiment.background.add_from_args(x=45000, y=0.2) +experiment.background.add_from_args(x=50000, y=0.2) +experiment.background.add_from_args(x=55000, y=0.2) +experiment.background.add_from_args(x=65000, y=0.2) +experiment.background.add_from_args(x=70000, y=0.2) +experiment.background.add_from_args(x=75000, y=0.2) +experiment.background.add_from_args(x=80000, y=0.2) +experiment.background.add_from_args(x=85000, y=0.2) +experiment.background.add_from_args(x=90000, y=0.2) +experiment.background.add_from_args(x=95000, y=0.2) +experiment.background.add_from_args(x=100000, y=0.2) +experiment.background.add_from_args(x=105000, y=0.2) +experiment.background.add_from_args(x=110000, y=0.2) # %% [markdown] # #### Set Linked Phases # %% -experiment.linked_phases.add('lbco', scale=4.0) -experiment.linked_phases.add('si', scale=0.2) +experiment.linked_phases.add_from_args(id='lbco', scale=4.0) +experiment.linked_phases.add_from_args(id='si', scale=0.2) # %% [markdown] # ## Define Project @@ -208,8 +242,8 @@ # Add excluded regions. # %% -experiment.excluded_regions.add(start=0, end=40000) -experiment.excluded_regions.add(start=108000, end=200000) +experiment.excluded_regions.add_from_args(start=0, end=40000) +experiment.excluded_regions.add_from_args(start=108000, end=200000) # %% [markdown] # Show excluded regions. diff --git a/tutorials/data/lbco.cif b/tutorials/data/lbco.cif new file mode 100644 index 00000000..e80dfecb --- /dev/null +++ b/tutorials/data/lbco.cif @@ -0,0 +1,36 @@ + data_lbco + + _space_group.IT_coordinate_system_code 1 + _space_group.name_H-M_alt "P m -3 m" + + _cell.angle_alpha 90 + _cell.angle_beta 90 + _cell.length_a 3.88(1) + _cell.length_b 3.88 + _cell.length_c 3.88 + + loop_ + _atom_site.ADP_type + _atom_site.B_iso_or_equiv + _atom_site.fract_x + _atom_site.fract_y + _atom_site.fract_z + _atom_site.label + _atom_site.occupancy + _atom_site.type_symbol + _atom_site.Wyckoff_letter + Biso 0.5 0.0 0.0 0.0 La 0.5 La a + Biso 0.5 0.0 0.0 0.0 Ba 0.5 Ba a + Biso 0.5 0.5 0.5 0.5 Co 1.0 Co b + Biso 0.5 0.0 0.5 0.5 O 1.0 O c + + loop_ + _pd_meas.2theta_scan + _pd_proc.intensity_total + _pd_calc.intensity_total_su + 10.0 167.0 12.6 + 10.05 157.0 12.5 + 10.1 187.0 13.3 + 10.15 197.0 14.0 + 10.2 164.0 12.5 + 10.25 171.0 13.0 diff --git a/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py b/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py index 7cd784ad..f2d553d0 100644 --- a/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py +++ b/tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.py @@ -183,8 +183,8 @@ # for more details about excluding regions from the measured data. # %% -project_1.experiments['sim_si'].excluded_regions.add(start=0, end=55000) -project_1.experiments['sim_si'].excluded_regions.add(start=105500, end=200000) +project_1.experiments['sim_si'].excluded_regions.add_from_args(start=0, end=55000) +project_1.experiments['sim_si'].excluded_regions.add_from_args(start=105500, end=200000) # %% [markdown] # To visualize the effect of excluding the high TOF region, we can plot @@ -353,13 +353,13 @@ # %% project_1.experiments['sim_si'].background_type = 'line-segment' -project_1.experiments['sim_si'].background.add(x=50000, y=0.01) -project_1.experiments['sim_si'].background.add(x=60000, y=0.01) -project_1.experiments['sim_si'].background.add(x=70000, y=0.01) -project_1.experiments['sim_si'].background.add(x=80000, y=0.01) -project_1.experiments['sim_si'].background.add(x=90000, y=0.01) -project_1.experiments['sim_si'].background.add(x=100000, y=0.01) -project_1.experiments['sim_si'].background.add(x=110000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=50000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=60000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=70000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=80000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=90000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=100000, y=0.01) +project_1.experiments['sim_si'].background.add_from_args(x=110000, y=0.01) # %% [markdown] # ### 🧩 Create a Sample Model – Si @@ -446,7 +446,7 @@ # #### Add Sample Model # %% -project_1.sample_models.add(name='si') +project_1.sample_models.add_minimal(name='si') # %% [markdown] # #### Set Space Group @@ -480,7 +480,7 @@ # for more details about the atom sites category. # %% -project_1.sample_models['si'].atom_sites.add( +project_1.sample_models['si'].atom_sites.add_from_args( label='Si', type_symbol='Si', fract_x=0, @@ -504,7 +504,7 @@ # for more details about linking a sample model to an experiment. # %% -project_1.experiments['sim_si'].linked_phases.add(id='si', scale=1.0) +project_1.experiments['sim_si'].linked_phases.add_from_args(id='si', scale=1.0) # %% [markdown] # ### 🚀 Analyze and Fit the Data @@ -781,8 +781,8 @@ project_2.plotter.engine = 'plotly' project_2.plot_meas(expt_name='sim_lbco') -project_2.experiments['sim_lbco'].excluded_regions.add(start=0, end=55000) -project_2.experiments['sim_lbco'].excluded_regions.add(start=105500, end=200000) +project_2.experiments['sim_lbco'].excluded_regions.add_from_args(start=0, end=55000) +project_2.experiments['sim_lbco'].excluded_regions.add_from_args(start=105500, end=200000) project_2.plot_meas(expt_name='sim_lbco') @@ -830,7 +830,7 @@ # %% tags=["solution", "hide-input"] # # Create a reference to the peak profile parameters from the Si sim_si_peak = project_1.experiments['sim_si'].peak -project_2.peak_profile_type = 'pseudo-voigt * ikeda-carpenter' +project_2.experiments['sim_lbco'].peak_profile_type = 'pseudo-voigt * ikeda-carpenter' project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_0 = sim_si_peak.broad_gauss_sigma_0.value project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_1 = sim_si_peak.broad_gauss_sigma_1.value project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_2 = sim_si_peak.broad_gauss_sigma_2.value @@ -859,13 +859,13 @@ # %% tags=["solution", "hide-input"] project_2.experiments['sim_lbco'].background_type = 'line-segment' -project_2.experiments['sim_lbco'].background.add(x=50000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=60000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=70000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=80000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=90000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=100000, y=0.2) -project_2.experiments['sim_lbco'].background.add(x=110000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=50000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=60000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=70000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=80000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=90000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=100000, y=0.2) +project_2.experiments['sim_lbco'].background.add_from_args(x=110000, y=0.2) # %% [markdown] # ### 🧩 Exercise 3: Define a Sample Model – LBCO @@ -951,7 +951,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.sample_models.add(name='lbco') +project_2.sample_models.add_minimal(name='lbco') # %% [markdown] # #### Exercise 3.2: Set Space Group @@ -1005,7 +1005,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.sample_models['lbco'].atom_sites.add( +project_2.sample_models['lbco'].atom_sites.add_from_args( label='La', type_symbol='La', fract_x=0, @@ -1015,7 +1015,7 @@ b_iso=0.95, occupancy=0.5, ) -project_2.sample_models['lbco'].atom_sites.add( +project_2.sample_models['lbco'].atom_sites.add_from_args( label='Ba', type_symbol='Ba', fract_x=0, @@ -1025,7 +1025,7 @@ b_iso=0.95, occupancy=0.5, ) -project_2.sample_models['lbco'].atom_sites.add( +project_2.sample_models['lbco'].atom_sites.add_from_args( label='Co', type_symbol='Co', fract_x=0.5, @@ -1034,7 +1034,7 @@ wyckoff_letter='b', b_iso=0.80, ) -project_2.sample_models['lbco'].atom_sites.add( +project_2.sample_models['lbco'].atom_sites.add_from_args( label='O', type_symbol='O', fract_x=0, @@ -1060,7 +1060,7 @@ # **Solution:** # %% tags=["solution", "hide-input"] -project_2.experiments['sim_lbco'].linked_phases.add(id='lbco', scale=1.0) +project_2.experiments['sim_lbco'].linked_phases.add_from_args(id='lbco', scale=1.0) # %% [markdown] # ### 🚀 Exercise 5: Analyze and Fit the Data @@ -1366,7 +1366,7 @@ # %% tags=["solution", "hide-input"] # Set Space Group -project_2.sample_models.add(name='si') +project_2.sample_models.add_minimal(name='si') project_2.sample_models['si'].space_group.name_h_m = 'F d -3 m' project_2.sample_models['si'].space_group.it_coordinate_system_code = '2' @@ -1374,7 +1374,7 @@ project_2.sample_models['si'].cell.length_a = 5.43 # Set Atom Sites -project_2.sample_models['si'].atom_sites.add( +project_2.sample_models['si'].atom_sites.add_from_args( label='Si', type_symbol='Si', fract_x=0, @@ -1385,7 +1385,7 @@ ) # Assign Sample Model to Experiment -project_2.experiments['sim_lbco'].linked_phases.add(id='si', scale=1.0) +project_2.experiments['sim_lbco'].linked_phases.add_from_args(id='si', scale=1.0) # %% [markdown] # #### Exercise 5.11: Refine the Scale of the Si Phase diff --git a/tutorials/pdf_pd-neut-cwl_Ni.py b/tutorials/pdf_pd-neut-cwl_Ni.py index 68569520..eb90d442 100644 --- a/tutorials/pdf_pd-neut-cwl_Ni.py +++ b/tutorials/pdf_pd-neut-cwl_Ni.py @@ -30,13 +30,13 @@ # ## Add Sample Model # %% -project.sample_models.add(name='ni') +project.sample_models.add_minimal(name='ni') # %% project.sample_models['ni'].space_group.name_h_m = 'F m -3 m' project.sample_models['ni'].space_group.it_coordinate_system_code = '1' project.sample_models['ni'].cell.length_a = 3.52387 -project.sample_models['ni'].atom_sites.add( +project.sample_models['ni'].atom_sites.add_from_args( label='Ni', type_symbol='Ni', fract_x=0.0, @@ -63,7 +63,7 @@ ) # %% -project.experiments['pdf'].linked_phases.add(id='ni', scale=1.0) +project.experiments['pdf'].linked_phases.add_from_args(id='ni', scale=1.0) project.experiments['pdf'].peak.damp_q = 0 project.experiments['pdf'].peak.broad_q = 0.03 project.experiments['pdf'].peak.cutoff_q = 27.0 diff --git a/tutorials/pdf_pd-neut-tof_Si-NOMAD.py b/tutorials/pdf_pd-neut-tof_Si-NOMAD.py index d35062f9..9bc66bb6 100644 --- a/tutorials/pdf_pd-neut-tof_Si-NOMAD.py +++ b/tutorials/pdf_pd-neut-tof_Si-NOMAD.py @@ -28,14 +28,14 @@ # ## Add Sample Model # %% -project.sample_models.add(name='si') +project.sample_models.add_minimal(name='si') # %% sample_model = project.sample_models['si'] sample_model.space_group.name_h_m.value = 'F d -3 m' sample_model.space_group.it_coordinate_system_code = '1' sample_model.cell.length_a = 5.43146 -sample_model.atom_sites.add( +sample_model.atom_sites.add_from_args( label='Si', type_symbol='Si', fract_x=0, @@ -63,7 +63,7 @@ # %% experiment = project.experiments['nomad'] -experiment.linked_phases.add(id='si', scale=1.0) +experiment.linked_phases.add_from_args(id='si', scale=1.0) experiment.peak.damp_q = 0.02 experiment.peak.broad_q = 0.03 experiment.peak.cutoff_q = 35.0 diff --git a/tutorials/pdf_pd-xray_NaCl.py b/tutorials/pdf_pd-xray_NaCl.py index a650129c..c872fe72 100644 --- a/tutorials/pdf_pd-xray_NaCl.py +++ b/tutorials/pdf_pd-xray_NaCl.py @@ -32,16 +32,16 @@ # ## Add Sample Model # %% -project.sample_models.add(name='nacl') +project.sample_models.add_minimal(name='nacl') # %% project.sample_models['nacl'].space_group.name_h_m = 'F m -3 m' project.sample_models['nacl'].space_group.it_coordinate_system_code = '1' project.sample_models['nacl'].cell.length_a = 5.62 -project.sample_models['nacl'].atom_sites.add( +project.sample_models['nacl'].atom_sites.add_from_args( label='Na', type_symbol='Na', fract_x=0, fract_y=0, fract_z=0, wyckoff_letter='a', b_iso=1.0 ) -project.sample_models['nacl'].atom_sites.add( +project.sample_models['nacl'].atom_sites.add_from_args( label='Cl', type_symbol='Cl', fract_x=0.5, @@ -85,7 +85,7 @@ project.experiments['xray_pdf'].peak.damp_particle_diameter = 0 # %% -project.experiments['xray_pdf'].linked_phases.add(id='nacl', scale=0.5) +project.experiments['xray_pdf'].linked_phases.add_from_args(id='nacl', scale=0.5) # %% [markdown] # ## Select Fitting Parameters diff --git a/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py b/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py index 019f1c40..83597257 100644 --- a/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py +++ b/tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py @@ -32,7 +32,7 @@ # ## Step 2: Define Sample Model # %% -project.sample_models.add(name='lbco') +project.sample_models.add_minimal(name='lbco') # %% sample_model = project.sample_models['lbco'] @@ -45,10 +45,38 @@ sample_model.cell.length_a = 3.88 # %% -sample_model.atom_sites.add('La', 'La', 0, 0, 0, b_iso=0.5, occupancy=0.5) -sample_model.atom_sites.add('Ba', 'Ba', 0, 0, 0, b_iso=0.5, occupancy=0.5) -sample_model.atom_sites.add('Co', 'Co', 0.5, 0.5, 0.5, b_iso=0.5) -sample_model.atom_sites.add('O', 'O', 0, 0.5, 0.5, b_iso=0.5) +sample_model.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) +sample_model.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) +sample_model.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5, +) +sample_model.atom_sites.add_from_args( + label='O', type_symbol='O', fract_x=0, fract_y=0.5, fract_z=0.5, wyckoff_letter='c', b_iso=0.5 +) # %% [markdown] # ## Step 3: Define Experiment @@ -79,18 +107,18 @@ experiment.peak.broad_lorentz_y = 0.1 # %% -experiment.background.add(x=10, y=170) -experiment.background.add(x=30, y=170) -experiment.background.add(x=50, y=170) -experiment.background.add(x=110, y=170) -experiment.background.add(x=165, y=170) +experiment.background.add_from_args(x=10, y=170) +experiment.background.add_from_args(x=30, y=170) +experiment.background.add_from_args(x=50, y=170) +experiment.background.add_from_args(x=110, y=170) +experiment.background.add_from_args(x=165, y=170) # %% -experiment.excluded_regions.add(start=0, end=5) -experiment.excluded_regions.add(start=165, end=180) +experiment.excluded_regions.add_from_args(start=0, end=5) +experiment.excluded_regions.add_from_args(start=165, end=180) # %% -experiment.linked_phases.add(id='lbco', scale=10.0) +experiment.linked_phases.add_from_args(id='lbco', scale=10.0) # %% [markdown] # ## Step 4: Perform Analysis From 17ded39c8bbef994e8040a45fe14932e1bc4d46a Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Thu, 23 Oct 2025 10:39:46 +0200 Subject: [PATCH 3/7] Refactors logging and display architecture (#107) * Converts print statements to logging * Refactors plotting module to display module * Refines parameter handling and logging functionality * Refines CIF serialization and updates docs * Refines renderer architecture and error handling * Refines logging and utils import structure * Refines table rendering and error handling logic * Enhances logging with customizable rich formatting * Refactors plotting modules and updates test expectations * Refactors logger and environment detection functions * Switches logging from log to console * Improves axis scaling for asciichartpy * Refactors logging utility with enhanced features * Renames utils module for clarity * Enhances display and tracking in analysis module * Disable Jupyter notebook output scrolling * Rename environment checks for clarity * Remove redundant type ignores and streamline exception handling * Improves console width detection for flexible environments * Refactors logging modes and console output methods * Enhances logging utilities with improved docs * Removes unused imports and obsolete functions * Refactors logging architecture for clarity * Removes unused show_index parameter * Refactors logger configuration setup * Reorders render_table arguments for clarity * Refactors parameter table rendering and identity handling * Removes redundant header handling in render_table * Adds structure refinement example scripts * Refactors test environment detection logic * Enhances table styling with padding and font size * Refactors logger imports and removes lazy loading * Suppresses user warnings in pytest configuration * Adds SPDX license headers to utils.py * Reorganizes display and logging structure * Refines logging initialization and aliases * Activates commented pre-commit hooks * Adds SPDX license identifiers to init files * Enhances Pandas table styling * Removes Jupyter-specific logic from CIF renderer * Updates deps * Temporarily disable Jupyter scroll in MkDocs * Improves table styling for enhanced readability * Add conditional doc build for branch environments --- .github/workflows/docs.yaml | 9 +- .pre-commit-config.yaml | 12 +- docs/api-reference/display.md | 1 + docs/api-reference/index.md | 2 +- docs/api-reference/io.md | 1 + docs/api-reference/plotting.md | 1 - docs/architecture/package-structure-full.md | 54 +- docs/architecture/package-structure-short.md | 26 +- docs/mkdocs.yml | 9 +- pixi.lock | 715 ++++++++--------- pixi.toml | 1 + pyproject.toml | 9 +- pytest.ini | 3 + src/easydiffraction/__init__.py | 54 +- src/easydiffraction/analysis/analysis.py | 306 ++++--- .../analysis/calculators/factory.py | 12 +- .../analysis/categories/aliases.py | 2 +- .../analysis/categories/constraints.py | 2 +- .../categories/joint_fit_experiments.py | 2 +- .../analysis/fit_helpers/reporting.py | 21 +- .../analysis/fit_helpers/tracking.py | 160 ++-- .../analysis/minimizers/factory.py | 4 +- src/easydiffraction/core/diagnostic.py | 8 +- .../crystallography/crystallography.py | 11 +- src/easydiffraction/display/__init__.py | 17 + src/easydiffraction/display/base.py | 135 ++++ .../display/plotters/__init__.py | 10 + .../plotters/ascii.py} | 41 +- .../plotters/base.py} | 16 +- .../plotters/plotly.py} | 27 +- src/easydiffraction/display/plotting.py | 440 ++++++++++ .../display/tablers/__init__.py | 10 + src/easydiffraction/display/tablers/base.py | 109 +++ src/easydiffraction/display/tablers/pandas.py | 169 ++++ src/easydiffraction/display/tablers/rich.py | 153 ++++ src/easydiffraction/display/tables.py | 118 +++ src/easydiffraction/display/utils.py | 48 ++ .../categories/background/chebyshev.py | 8 +- .../categories/background/factory.py | 5 +- .../categories/background/line_segment.py | 8 +- .../categories/excluded_regions.py | 6 +- .../experiments/categories/instrument/base.py | 4 +- .../experiments/categories/linked_phases.py | 2 +- .../experiments/experiment/base.py | 29 +- .../experiments/experiment/bragg_pd.py | 26 +- .../experiments/experiment/total_pd.py | 6 +- .../experiments/experiments.py | 6 +- .../{plotting => io}/__init__.py | 0 .../{plotting/plotters => io/cif}/__init__.py | 0 src/easydiffraction/io/cif/serialize.py | 42 +- src/easydiffraction/plotting/plotting.py | 376 --------- src/easydiffraction/project/project.py | 39 +- src/easydiffraction/project/project_info.py | 7 +- .../sample_models/categories/atom_sites.py | 2 +- .../sample_models/sample_model/base.py | 19 +- .../sample_models/sample_models.py | 6 +- src/easydiffraction/summary/summary.py | 82 +- src/easydiffraction/utils/environment.py | 140 ++++ src/easydiffraction/utils/formatting.py | 59 -- src/easydiffraction/utils/logging.py | 623 +++++++++++---- src/easydiffraction/utils/utils.py | 258 ++---- .../analysis/fit_helpers/test_reporting.py | 4 +- .../analysis/fit_helpers/test_tracking.py | 4 +- .../analysis/test_analysis_access_params.py | 47 +- .../plotters/test_ascii.py} | 6 +- .../display/plotters/test_base.py | 39 + .../plotters/test_plotly.py} | 8 +- .../{plotting => display}/test_plotting.py | 56 +- .../experiments/experiment/test_base.py | 2 - .../experiments/experiment/test_enums.py | 2 - .../experiment/test_instrument_mixin.py | 2 - .../plotting/plotters/test_plotter_base.py | 36 - .../project/test_project_d_spacing.py | 2 +- .../easydiffraction/utils/test_formatting.py | 39 - .../easydiffraction/utils/test_logging.py | 2 - .../unit/easydiffraction/utils/test_utils.py | 24 +- tmp/Untitled.ipynb | 647 ++++++++------- tmp/Untitled0.ipynb | 492 ++++++++++++ tmp/Untitled2.ipynb | 6 + tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py | 750 ++++++++++++++++++ tmp/display.py | 413 ++++++++++ tmp/display2.py | 28 + tmp/display3-Copy1.py | 45 ++ tmp/display3.py | 173 ++++ tmp/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py | 120 +++ tmp/short.py | 2 +- 86 files changed, 5303 insertions(+), 2117 deletions(-) create mode 100644 docs/api-reference/display.md create mode 100644 docs/api-reference/io.md delete mode 100644 docs/api-reference/plotting.md create mode 100644 src/easydiffraction/display/__init__.py create mode 100644 src/easydiffraction/display/base.py create mode 100644 src/easydiffraction/display/plotters/__init__.py rename src/easydiffraction/{plotting/plotters/plotter_ascii.py => display/plotters/ascii.py} (57%) rename src/easydiffraction/{plotting/plotters/plotter_base.py => display/plotters/base.py} (76%) rename src/easydiffraction/{plotting/plotters/plotter_plotly.py => display/plotters/plotly.py} (80%) create mode 100644 src/easydiffraction/display/plotting.py create mode 100644 src/easydiffraction/display/tablers/__init__.py create mode 100644 src/easydiffraction/display/tablers/base.py create mode 100644 src/easydiffraction/display/tablers/pandas.py create mode 100644 src/easydiffraction/display/tablers/rich.py create mode 100644 src/easydiffraction/display/tables.py create mode 100644 src/easydiffraction/display/utils.py rename src/easydiffraction/{plotting => io}/__init__.py (100%) rename src/easydiffraction/{plotting/plotters => io/cif}/__init__.py (100%) delete mode 100644 src/easydiffraction/plotting/plotting.py create mode 100644 src/easydiffraction/utils/environment.py delete mode 100644 src/easydiffraction/utils/formatting.py rename tests/unit/easydiffraction/{plotting/plotters/test_plotter_ascii.py => display/plotters/test_ascii.py} (73%) create mode 100644 tests/unit/easydiffraction/display/plotters/test_base.py rename tests/unit/easydiffraction/{plotting/plotters/test_plotter_plotly.py => display/plotters/test_plotly.py} (89%) rename tests/unit/easydiffraction/{plotting => display}/test_plotting.py (69%) delete mode 100644 tests/unit/easydiffraction/plotting/plotters/test_plotter_base.py delete mode 100644 tests/unit/easydiffraction/utils/test_formatting.py create mode 100644 tmp/Untitled0.ipynb create mode 100644 tmp/Untitled2.ipynb create mode 100644 tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py create mode 100644 tmp/display.py create mode 100644 tmp/display2.py create mode 100644 tmp/display3-Copy1.py create mode 100644 tmp/display3.py create mode 100644 tmp/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 5a4a4de2..8dd24c1d 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -137,7 +137,14 @@ jobs: # Input: docs/ directory containing the Markdown files # Output: site/ directory containing the generated HTML files - name: Build site with MkDocs - run: pixi run docs-build + run: | + if [[ "${CI_BRANCH}" == "develop" || "${CI_BRANCH}" == "master" || "${CI_BRANCH}" == "main" ]]; then + echo "📘 Building production docs for ${CI_BRANCH}" + pixi run docs-build + else + echo "📗 Building local docs for ${CI_BRANCH}" + pixi run docs-local + fi # Set up the Pages action to configure the static files to be deployed # NOTE: The repository must have GitHub Pages enabled and configured to build using GitHub Actions diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87117c70..c3d471cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,9 +49,9 @@ repos: pass_filenames: false stages: [pre-push] - #- id: pixi-unit-tests - # name: pixi run unit-tests - # entry: pixi run unit-tests - # language: system - # pass_filenames: false - # stages: [pre-push] + - id: pixi-unit-tests + name: pixi run unit-tests + entry: pixi run unit-tests + language: system + pass_filenames: false + stages: [pre-push] diff --git a/docs/api-reference/display.md b/docs/api-reference/display.md new file mode 100644 index 00000000..3d19e2f7 --- /dev/null +++ b/docs/api-reference/display.md @@ -0,0 +1 @@ +::: easydiffraction.display diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index 1d08cd20..25418279 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -13,7 +13,7 @@ available in EasyDiffraction: space groups, and symmetry operations. - [utils](utils.md) – Miscellaneous utility functions for formatting, decorators, and general helpers. -- [plotting](plotting.md) – Tools for visualizing data and fitting results. +- [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. diff --git a/docs/api-reference/io.md b/docs/api-reference/io.md new file mode 100644 index 00000000..794e0d3a --- /dev/null +++ b/docs/api-reference/io.md @@ -0,0 +1 @@ +::: easydiffraction.io diff --git a/docs/api-reference/plotting.md b/docs/api-reference/plotting.md deleted file mode 100644 index 88675b2d..00000000 --- a/docs/api-reference/plotting.md +++ /dev/null @@ -1 +0,0 @@ -::: easydiffraction.plotting diff --git a/docs/architecture/package-structure-full.md b/docs/architecture/package-structure-full.md index 312b6c18..1237c97b 100644 --- a/docs/architecture/package-structure-full.md +++ b/docs/architecture/package-structure-full.md @@ -32,6 +32,7 @@ │ │ ├── 📄 reporting.py │ │ │ └── 🏷️ class FitResults │ │ └── 📄 tracking.py +│ │ ├── 🏷️ class _TerminalLiveHandle │ │ └── 🏷️ class FitProgressTracker │ ├── 📁 minimizers │ │ ├── 📄 __init__.py @@ -93,6 +94,37 @@ │ ├── 📄 __init__.py │ ├── 📄 crystallography.py │ └── 📄 space_groups.py +├── 📁 display +│ ├── 📁 plotters +│ │ ├── 📄 __init__.py +│ │ ├── 📄 ascii.py +│ │ │ └── 🏷️ class AsciiPlotter +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class PlotterBase +│ │ └── 📄 plotly.py +│ │ └── 🏷️ class PlotlyPlotter +│ ├── 📁 tablers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ │ └── 🏷️ class TableBackendBase +│ │ ├── 📄 pandas.py +│ │ │ └── 🏷️ class PandasTableBackend +│ │ └── 📄 rich.py +│ │ └── 🏷️ class RichTableBackend +│ ├── 📄 __init__.py +│ ├── 📄 base.py +│ │ ├── 🏷️ class RendererBase +│ │ └── 🏷️ class RendererFactoryBase +│ ├── 📄 plotting.py +│ │ ├── 🏷️ class PlotterEngineEnum +│ │ ├── 🏷️ class Plotter +│ │ └── 🏷️ class PlotterFactory +│ ├── 📄 tables.py +│ │ ├── 🏷️ class TableEngineEnum +│ │ ├── 🏷️ class TableRenderer +│ │ └── 🏷️ class TableRendererFactory +│ └── 📄 utils.py +│ └── 🏷️ class JupyterScrollManager ├── 📁 experiments │ ├── 📁 categories │ │ ├── 📁 background @@ -192,19 +224,6 @@ │ ├── 📄 handler.py │ │ └── 🏷️ class CifHandler │ └── 📄 serialize.py -├── 📁 plotting -│ ├── 📁 plotters -│ │ ├── 📄 __init__.py -│ │ ├── 📄 plotter_ascii.py -│ │ │ └── 🏷️ class AsciiPlotter -│ │ ├── 📄 plotter_base.py -│ │ │ └── 🏷️ class PlotterBase -│ │ └── 📄 plotter_plotly.py -│ │ └── 🏷️ class PlotlyPlotter -│ ├── 📄 __init__.py -│ └── 📄 plotting.py -│ ├── 🏷️ class Plotter -│ └── 🏷️ class PlotterFactory ├── 📁 project │ ├── 📄 __init__.py │ ├── 📄 project.py @@ -236,9 +255,14 @@ │ └── 🏷️ class Summary ├── 📁 utils │ ├── 📄 __init__.py -│ ├── 📄 formatting.py +│ ├── 📄 environment.py │ ├── 📄 logging.py -│ │ └── 🏷️ class Logger +│ │ ├── 🏷️ class IconifiedRichHandler +│ │ ├── 🏷️ class ConsoleManager +│ │ ├── 🏷️ class LoggerConfig +│ │ ├── 🏷️ class ExceptionHookManager +│ │ ├── 🏷️ class Logger +│ │ └── 🏷️ class ConsolePrinter │ └── 📄 utils.py ├── 📄 __init__.py └── 📄 __main__.py diff --git a/docs/architecture/package-structure-short.md b/docs/architecture/package-structure-short.md index d664e1b4..4158458e 100644 --- a/docs/architecture/package-structure-short.md +++ b/docs/architecture/package-structure-short.md @@ -46,6 +46,22 @@ │ ├── 📄 __init__.py │ ├── 📄 crystallography.py │ └── 📄 space_groups.py +├── 📁 display +│ ├── 📁 plotters +│ │ ├── 📄 __init__.py +│ │ ├── 📄 ascii.py +│ │ ├── 📄 base.py +│ │ └── 📄 plotly.py +│ ├── 📁 tablers +│ │ ├── 📄 __init__.py +│ │ ├── 📄 base.py +│ │ ├── 📄 pandas.py +│ │ └── 📄 rich.py +│ ├── 📄 __init__.py +│ ├── 📄 base.py +│ ├── 📄 plotting.py +│ ├── 📄 tables.py +│ └── 📄 utils.py ├── 📁 experiments │ ├── 📁 categories │ │ ├── 📁 background @@ -96,14 +112,6 @@ │ └── 📁 cif │ ├── 📄 handler.py │ └── 📄 serialize.py -├── 📁 plotting -│ ├── 📁 plotters -│ │ ├── 📄 __init__.py -│ │ ├── 📄 plotter_ascii.py -│ │ ├── 📄 plotter_base.py -│ │ └── 📄 plotter_plotly.py -│ ├── 📄 __init__.py -│ └── 📄 plotting.py ├── 📁 project │ ├── 📄 __init__.py │ ├── 📄 project.py @@ -125,7 +133,7 @@ │ └── 📄 summary.py ├── 📁 utils │ ├── 📄 __init__.py -│ ├── 📄 formatting.py +│ ├── 📄 environment.py │ ├── 📄 logging.py │ └── 📄 utils.py ├── 📄 __init__.py diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index ba5f16e8..fdea2145 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -86,12 +86,13 @@ nav: - 2025 DMSC: tutorials/dmsc-summer-school-2025_analysis-powder-diffraction.ipynb - API Reference: - API Reference: api-reference/index.md + - analysis: api-reference/analysis.md - core: api-reference/core.md - crystallography: api-reference/crystallography.md - - utils: api-reference/utils.md - - plotting: api-reference/plotting.md + - display: api-reference/display.md + - experiments: api-reference/experiments.md + - io: api-reference/io.md - project: api-reference/project.md - sample_models: api-reference/sample_models.md - - experiments: api-reference/experiments.md - - analysis: api-reference/analysis.md - summary: api-reference/summary.md + - utils: api-reference/utils.md diff --git a/pixi.lock b/pixi.lock index 0b02e764..f1e793f7 100644 --- a/pixi.lock +++ b/pixi.lock @@ -30,18 +30,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-h2b335a9_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.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cc/e6/33d305e6cce0a8daeb79c7d8d6547d6e5f27f4e35fa4883fc9c9eb638596/aiohttp-3.13.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -97,9 +97,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -110,16 +110,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -148,7 +148,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/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -178,7 +178,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3d/3c/b56076bb35303d0733fc47b110a1c9cce081a05ae2e886575a3587c1ee76/psutil-7.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl @@ -211,7 +211,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl @@ -231,15 +231,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9b/83/a0bdf4abf86ede79b427778fe27e2b4a022c98a7a8ea1745dcd6c6561f17/uv-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -256,7 +255,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.4-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda @@ -268,19 +267,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h2bd861f_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.2-h7cca4af_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/92/c8/1cf495bac85cf71b80fad5f6d7693e84894f11b9fe876b64b0a1e7cbf32f/aiohttp-3.13.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -336,9 +335,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -349,16 +348,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -387,7 +386,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/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -417,7 +416,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/51/30/f97f8fb1f9ecfbeae4b5ca738dcae66ab28323b5cfbc96cb5565f3754056/psutil-7.1.1-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 @@ -450,7 +449,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl @@ -470,15 +469,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4e/6c/3508d67f80aac0ddb5806680a6735ff6cb5a14e9b697e5ae145b01050880/uv-0.9.5-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -495,7 +493,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.4-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda @@ -507,19 +505,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.9-h09175d0_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.2-h1d1bf99_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/19/23c6b81cca587ec96943d977a58d11d05a82837022e65cd5502d665a7d11/aiohttp-3.13.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -574,9 +572,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -587,16 +585,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -625,7 +623,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/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -655,7 +653,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7b/98/b8d1f61ebf35f4dbdbaabadf9208282d8adc820562f0257e5e6e79e67bf2/psutil-7.1.1-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 @@ -688,7 +686,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl @@ -708,15 +706,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b2/26/bd6438cf6d84a6b0b608bcbe9f353d8e424f8fe3b1b73a768984a76bf80b/uv-0.9.5-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -732,7 +729,6 @@ environments: 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-2025.10.5-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -743,15 +739,15 @@ environments: - 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.50.4-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.0-h06f855e_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.1-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-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.3-hfa2b4ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.4-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-hdf00ec1_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-2021.13.0-h18a62a1_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda @@ -761,12 +757,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cc/00/f3a92c592a845ebb2f47d102a67f35f0925cb854c5e7386f1a3a1fdff2ab/aiohttp-3.13.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -822,9 +818,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -835,16 +831,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -873,7 +869,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/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -902,7 +898,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/be/f3/11fd213fff15427bc2853552138760c720fd65032d99edfb161910d04127/psutil-7.1.1-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 @@ -922,7 +918,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -936,7 +931,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl @@ -956,15 +951,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/14/0f07d0b2e561548b4e3006208480a5fce8cdaae5247d85efbfb56e8e596b/uv-0.9.5-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1015,12 +1009,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e8/a2/79eb466786a7f11a0292c353a8a9b95e88268c48c389239d7531d66dbb48/aiohttp-3.13.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -1076,9 +1070,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -1089,16 +1083,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -1127,7 +1121,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/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1158,7 +1152,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3d/3c/b56076bb35303d0733fc47b110a1c9cce081a05ae2e886575a3587c1ee76/psutil-7.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl @@ -1191,7 +1185,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl @@ -1210,15 +1204,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9b/83/a0bdf4abf86ede79b427778fe27e2b4a022c98a7a8ea1745dcd6c6561f17/uv-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1235,7 +1228,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.4-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda @@ -1253,13 +1246,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/f9/2d6d93fd57ab4726e18a7cdab083772eda8302d682620fbf2aef48322351/aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fc/f8/7f5b7f7184d7c80e421dbaecbd13e0b2a0bb8663fd0406864f9a167a438c/aiohttp-3.13.1-cp311-cp311-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -1315,9 +1308,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -1328,16 +1321,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -1366,7 +1359,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/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.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/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1397,7 +1390,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/51/30/f97f8fb1f9ecfbeae4b5ca738dcae66ab28323b5cfbc96cb5565f3754056/psutil-7.1.1-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 @@ -1430,7 +1423,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl @@ -1449,15 +1442,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4e/6c/3508d67f80aac0ddb5806680a6735ff6cb5a14e9b697e5ae145b01050880/uv-0.9.5-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1474,7 +1466,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.4-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda @@ -1492,13 +1484,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/a6/e1c061b079fed04ffd6777950c82f2e8246fd08b7b3c4f56fdd47f697e5a/aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3e/af/fb78d028b9642dd33ff127d9a6a151586f33daff631b05250fecd0ab23f8/aiohttp-3.13.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -1553,9 +1545,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -1566,16 +1558,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -1604,7 +1596,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/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1635,7 +1627,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7b/98/b8d1f61ebf35f4dbdbaabadf9208282d8adc820562f0257e5e6e79e67bf2/psutil-7.1.1-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 @@ -1668,7 +1660,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl @@ -1687,15 +1679,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b2/26/bd6438cf6d84a6b0b608bcbe9f353d8e424f8fe3b1b73a768984a76bf80b/uv-0.9.5-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1711,7 +1702,6 @@ environments: 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-2025.10.5-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -1721,10 +1711,10 @@ environments: - 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.50.4-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.0-h06f855e_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.1-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-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.3-hfa2b4ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.4-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda @@ -1740,12 +1730,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/bd/485d98b372a2cd6998484a93ddd401ec6b6031657661c36846a10e2a1f6e/aiohttp-3.13.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ac/d2/d21b8ab6315a5d588c550ab285b4f02ae363edf012920e597904c5a56608/aiohttp-3.13.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -1801,9 +1791,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -1814,16 +1804,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -1852,7 +1842,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/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -1882,7 +1872,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/be/f3/11fd213fff15427bc2853552138760c720fd65032d99edfb161910d04127/psutil-7.1.1-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 @@ -1902,7 +1892,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -1916,7 +1905,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl @@ -1935,15 +1924,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/14/0f07d0b2e561548b4e3006208480a5fce8cdaae5247d85efbfb56e8e596b/uv-0.9.5-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -1986,18 +1974,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.9.0-heeeca48_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-h2b335a9_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.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cc/e6/33d305e6cce0a8daeb79c7d8d6547d6e5f27f4e35fa4883fc9c9eb638596/aiohttp-3.13.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -2053,9 +2041,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -2066,16 +2054,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -2104,7 +2092,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/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2134,7 +2122,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3d/3c/b56076bb35303d0733fc47b110a1c9cce081a05ae2e886575a3587c1ee76/psutil-7.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl @@ -2167,7 +2155,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl @@ -2187,15 +2175,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9b/83/a0bdf4abf86ede79b427778fe27e2b4a022c98a7a8ea1745dcd6c6561f17/uv-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2212,7 +2199,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h500dc9f_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.4-h3d58e20_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libexpat-2.7.1-h21dd04a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libffi-3.4.6-h281671d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/liblzma-5.8.1-hd471939_2.conda @@ -2224,19 +2211,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/nodejs-24.9.0-h09bb5a9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.5.4-h230baf5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h2bd861f_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.2-h7cca4af_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/92/c8/1cf495bac85cf71b80fad5f6d7693e84894f11b9fe876b64b0a1e7cbf32f/aiohttp-3.13.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -2292,9 +2279,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -2305,16 +2292,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -2343,7 +2330,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/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2373,7 +2360,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/51/30/f97f8fb1f9ecfbeae4b5ca738dcae66ab28323b5cfbc96cb5565f3754056/psutil-7.1.1-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 @@ -2406,7 +2393,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl @@ -2426,15 +2413,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4e/6c/3508d67f80aac0ddb5806680a6735ff6cb5a14e9b697e5ae145b01050880/uv-0.9.5-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2451,7 +2437,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.10.5-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.4-hf598326_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.1-hec049ff_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.6-h1da3d7d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.1-h39f12f2_2.conda @@ -2463,19 +2449,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-24.4.1-hab9d20b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.5.4-h5503f6c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.9-h09175d0_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.2-h1d1bf99_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/19/23c6b81cca587ec96943d977a58d11d05a82837022e65cd5502d665a7d11/aiohttp-3.13.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -2530,9 +2516,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -2543,16 +2529,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -2581,7 +2567,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/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2611,7 +2597,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7b/98/b8d1f61ebf35f4dbdbaabadf9208282d8adc820562f0257e5e6e79e67bf2/psutil-7.1.1-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 @@ -2644,7 +2630,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl @@ -2664,15 +2650,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b2/26/bd6438cf6d84a6b0b608bcbe9f353d8e424f8fe3b1b73a768984a76bf80b/uv-0.9.5-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2688,7 +2673,6 @@ environments: 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-2025.10.5-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-35_h5709861_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-35_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.1-hac47afa_0.conda @@ -2699,15 +2683,15 @@ environments: - 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.50.4-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.0-h06f855e_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.1-h692994f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-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.3-hfa2b4ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.4-hfa2b4ca_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h57928b3_16.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-24.9.0-he453025_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.5.4-h725018a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.2-pyh145f28c_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-hdf00ec1_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-2021.13.0-h18a62a1_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_2.conda @@ -2717,12 +2701,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/cc/00/f3a92c592a845ebb2f47d102a67f35f0925cb854c5e7386f1a3a1fdff2ab/aiohttp-3.13.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/13/ac/19dbba27e891f39feb4170b884da449ee2699ef4ebb88eefeda364bbbbcf/asteval-1.0.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl @@ -2778,9 +2762,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-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/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -2791,16 +2775,16 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-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/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl @@ -2829,7 +2813,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/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl @@ -2858,7 +2842,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/be/f3/11fd213fff15427bc2853552138760c720fd65032d99edfb161910d04127/psutil-7.1.1-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 @@ -2878,7 +2862,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/5b/821733876bad200638237d1cab671b46cfdafb73c0b4094f59c5947155ae/python_socketio-5.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2892,7 +2875,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl @@ -2912,15 +2895,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/14/0f07d0b2e561548b4e3006208480a5fce8cdaae5247d85efbfb56e8e596b/uv-0.9.5-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/f9/cc7f78679fdee256e3aaa9e0c431f930142dc0824f999bb7edf4b22387fb/varname-0.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl @@ -2960,10 +2942,10 @@ packages: version: 2.6.1 sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/3e/af/fb78d028b9642dd33ff127d9a6a151586f33daff631b05250fecd0ab23f8/aiohttp-3.13.1-cp311-cp311-macosx_11_0_arm64.whl name: aiohttp - version: 3.13.0 - sha256: 059978d2fddc462e9211362cbc8446747ecd930537fa559d3d25c256f032ff54 + version: 3.13.1 + sha256: bfc28038cd86fb1deed5cc75c8fda45c6b0f5c51dfd76f8c63d3d22dc1ab3d1b requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2976,12 +2958,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/92/c8/1cf495bac85cf71b80fad5f6d7693e84894f11b9fe876b64b0a1e7cbf32f/aiohttp-3.13.1-cp313-cp313-macosx_10_13_x86_64.whl name: aiohttp - version: 3.13.0 - sha256: d169c47e40c911f728439da853b6fd06da83761012e6e76f11cb62cddae7282b + version: 3.13.1 + sha256: 4bef5b83296cebb8167707b4f8d06c1805db0af632f7a72d7c5288a84667e7c3 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -2994,12 +2976,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/3e/bd/485d98b372a2cd6998484a93ddd401ec6b6031657661c36846a10e2a1f6e/aiohttp-3.13.0-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/a8/19/23c6b81cca587ec96943d977a58d11d05a82837022e65cd5502d665a7d11/aiohttp-3.13.1-cp313-cp313-macosx_11_0_arm64.whl name: aiohttp - version: 3.13.0 - sha256: 0f19f7798996d4458c669bd770504f710014926e9970f4729cf55853ae200469 + version: 3.13.1 + sha256: 27af0619c33f9ca52f06069ec05de1a357033449ab101836f431768ecfa63ff5 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -3012,12 +2994,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ac/d2/d21b8ab6315a5d588c550ab285b4f02ae363edf012920e597904c5a56608/aiohttp-3.13.1-cp311-cp311-win_amd64.whl name: aiohttp - version: 3.13.0 - sha256: cc6d5fc5edbfb8041d9607f6a417997fa4d02de78284d386bea7ab767b5ea4f3 + version: 3.13.1 + sha256: 77a2f5cc28cf4704cc157be135c6a6cfb38c9dea478004f1c0fd7449cf445c28 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -3030,12 +3012,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/89/a6/e1c061b079fed04ffd6777950c82f2e8246fd08b7b3c4f56fdd47f697e5a/aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/cc/00/f3a92c592a845ebb2f47d102a67f35f0925cb854c5e7386f1a3a1fdff2ab/aiohttp-3.13.1-cp313-cp313-win_amd64.whl name: aiohttp - version: 3.13.0 - sha256: 3e6a38366f7f0d0f6ed7a1198055150c52fda552b107dad4785c0852ad7685d1 + version: 3.13.1 + sha256: ef56ffe60e8d97baac123272bde1ab889ee07d3419606fae823c80c2b86c403e requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -3048,12 +3030,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ae/f9/2d6d93fd57ab4726e18a7cdab083772eda8302d682620fbf2aef48322351/aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/cc/e6/33d305e6cce0a8daeb79c7d8d6547d6e5f27f4e35fa4883fc9c9eb638596/aiohttp-3.13.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp - version: 3.13.0 - sha256: 4696665b2713021c6eba3e2b882a86013763b442577fe5d2056a42111e732eca + version: 3.13.1 + sha256: 010dc9b7110f055006acd3648d5d5955bb6473b37c3663ec42a1b4cba7413e6b requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -3066,12 +3048,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e8/a2/79eb466786a7f11a0292c353a8a9b95e88268c48c389239d7531d66dbb48/aiohttp-3.13.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: aiohttp - version: 3.13.0 - sha256: 7897298b3eedc790257fef8a6ec582ca04e9dbe568ba4a9a890913b925b8ea21 + version: 3.13.1 + sha256: 0e4b4e607fbd4964d65945a7b9d1e7f98b0d5545736ea613f77d5a2a37ff1e46 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -3084,12 +3066,12 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/fc/f8/7f5b7f7184d7c80e421dbaecbd13e0b2a0bb8663fd0406864f9a167a438c/aiohttp-3.13.1-cp311-cp311-macosx_10_9_x86_64.whl name: aiohttp - version: 3.13.0 - sha256: 564b36512a7da3b386143c611867e3f7cfb249300a1bf60889bd9985da67ab77 + version: 3.13.1 + sha256: 6c20eb646371a5a57a97de67e52aac6c47badb1564e719b3601bbb557a2e8fd0 requires_dist: - aiohappyeyeballs>=2.5.0 - aiosignal>=1.4.0 @@ -3102,7 +3084,7 @@ packages: - aiodns>=3.3.0 ; extra == 'speedups' - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' - - zstandard ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl name: aiosignal @@ -3167,13 +3149,14 @@ packages: - cffi>=1.0.1 ; python_full_version < '3.14' - cffi>=2.0.0b1 ; python_full_version >= '3.14' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl name: arrow - version: 1.3.0 - sha256: c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80 + version: 1.4.0 + sha256: 749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205 requires_dist: - python-dateutil>=2.7.0 - - types-python-dateutil>=2.8.10 + - backports-zoneinfo==0.2.1 ; python_full_version < '3.9' + - tzdata ; python_full_version >= '3.9' - doc8 ; extra == 'doc' - sphinx>=7.0.0 ; extra == 'doc' - sphinx-autobuild ; extra == 'doc' @@ -3184,7 +3167,7 @@ packages: - pytest ; extra == 'test' - pytest-cov ; extra == 'test' - pytest-mock ; extra == 'test' - - pytz==2021.1 ; extra == 'test' + - pytz==2025.2 ; extra == 'test' - simplejson==3.* ; extra == 'test' requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl @@ -4627,18 +4610,6 @@ packages: purls: [] size: 11857802 timestamp: 1720853997952 -- conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - sha256: 1d04369a1860a1e9e371b9fc82dd0092b616adcf057d6c88371856669280e920 - md5: 8579b6bb8d18be7c0b27fb08adeeeb40 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: MIT - license_family: MIT - purls: [] - size: 14544252 - timestamp: 1720853966338 - pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl name: identify version: 2.6.15 @@ -4656,11 +4627,11 @@ packages: - pytest>=8.3.2 ; extra == 'all' - flake8>=7.1.1 ; extra == 'all' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl name: iniconfig - version: 2.1.0 - sha256: 9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - requires_python: '>=3.8' + version: 2.3.0 + sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl name: interrogate version: 1.7.0 @@ -4689,10 +4660,10 @@ packages: - pytest-mock ; extra == 'tests' - coverage[toml] ; extra == 'tests' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl name: ipykernel - version: 6.30.1 - sha256: aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4 + version: 6.31.0 + sha256: abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af requires_dist: - appnope>=0.1.2 ; sys_platform == 'darwin' - comm>=0.1.1 @@ -4919,13 +4890,12 @@ packages: - pytest-timeout ; extra == 'test' - pytest<8.2.0 ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl name: jupyter-core - version: 5.8.1 - sha256: c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0 + version: 5.9.1 + sha256: ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407 requires_dist: - platformdirs>=2.5 - - pywin32>=300 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' - traitlets>=5.3 - intersphinx-registry ; extra == 'docs' - myst-parser ; extra == 'docs' @@ -4938,7 +4908,7 @@ packages: - pytest-cov ; extra == 'test' - pytest-timeout ; extra == 'test' - pytest<9 ; extra == 'test' - requires_python: '>=3.8' + requires_python: '>=3.10' - 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 @@ -5046,10 +5016,10 @@ packages: - pytest-timeout ; extra == 'test' - pytest>=7.0 ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl name: jupyterlab - version: 4.4.9 - sha256: 394c902827350c017430a8370b9f40c03c098773084bc53930145c146d3d2cb2 + version: 4.4.10 + sha256: 65939ab4c8dcd0c42185c2d0d1a9d60b254dc8c46fc4fdb286b63c51e9358e07 requires_dist: - async-lru>=1.0.0 - httpx>=0.25.0,<1 @@ -5113,10 +5083,10 @@ packages: version: 0.3.0 sha256: 841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl name: jupyterlab-server - version: 2.27.3 - sha256: e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4 + version: 2.28.0 + sha256: e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968 requires_dist: - babel>=2.10 - importlib-metadata>=4.8.3 ; python_full_version < '3.10' @@ -5155,10 +5125,10 @@ packages: name: jupyterquiz version: 2.9.6.2 sha256: f60f358c809d06a38c423c804740c1113c8840e130227aef50694a9201474bef -- pypi: https://files.pythonhosted.org/packages/36/86/751ec86adb66104d15e650b704f89dddd64ba29283178b9651b9bc84b624/jupytext-1.17.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/bd/0d/2d240e7098e0cafba4d25e9530e7596b1bb1bd4476e41b10346bcaaa36d6/jupytext-1.18.1-py3-none-any.whl name: jupytext - version: 1.17.3 - sha256: 09b0a94cd904416e823a5ba9f41bd181031215b6fc682d2b5c18e68354feb17c + version: 1.18.1 + sha256: 24f999400726a1c658beae55e15fdd2a6255ab1a418697864cd779874e6011ab requires_dist: - markdown-it-py>=1.0 - mdit-py-plugins @@ -5324,26 +5294,26 @@ packages: purls: [] size: 66398 timestamp: 1757003514529 -- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.3-h3d58e20_0.conda - sha256: 9bba2ce10e1c390a4091ca48fab0c71c010f6526c27ac2da53399940ad4c113f - md5: 432d125a340932454d777b66b09c32a1 +- conda: https://conda.anaconda.org/conda-forge/osx-64/libcxx-21.1.4-h3d58e20_0.conda + sha256: 64f58f7ad9076598ae4a19f383f6734116d96897032c77de599660233f2924f9 + md5: 17c4292004054f6783b16b55b499f086 depends: - __osx >=10.13 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 571632 - timestamp: 1760166417842 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.3-hf598326_0.conda - sha256: b9bad452e3e1d0cc597d907681461341209cb7576178d5c1933026a650b381d1 - md5: e976227574dfcd0048324576adf8d60d + size: 571252 + timestamp: 1761043932993 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-21.1.4-hf598326_0.conda + sha256: df55e80dda21f2581366f66cf18a6c11315d611f6fb01e56011c5199f983c0d9 + md5: 6002a2ba796f1387b6a5c6d77051d1db depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 568715 - timestamp: 1760166479630 + size: 567892 + timestamp: 1761043967532 - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.1-hecca717_0.conda sha256: da2080da8f0288b95dd86765c801c6e166c4619b910b11f9a8446fb852438dc2 md5: 4211416ecba1866fab0c6470986c22d6 @@ -5728,28 +5698,28 @@ packages: purls: [] size: 100393 timestamp: 1702724383534 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.0-ha29bfb0_1.conda - sha256: 8890c03908a407649ac99257b63176b61d10dfa3468aa3db1994ac0973dc2803 - md5: 1d6e5fbbe84eebcd62e7cdccec799ce8 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-h5d26750_0.conda + sha256: f507960adf64ee9c9c7b7833d8b11980765ebd2bf5345f73d5a3b21b259eaed5 + md5: 9176ee05643a1bfe7f2e7b4c921d2c3d depends: - - icu >=75.1,<76.0a0 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.1,<6.0a0 - - libxml2-16 2.15.0 h06f855e_1 + - libxml2-16 2.15.1 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: 43274 - timestamp: 1758641414853 -- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.0-h06f855e_1.conda - sha256: f29159eef5af2adffe2fef2d89ff2f6feda07e194883f47a4cf366e9608fb91b - md5: a5d1a1f8745fcd93f39a4b80f389962f + size: 43209 + timestamp: 1761016354235 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.1-h692994f_0.conda + sha256: 04129dc2df47a01c55e5ccf8a18caefab94caddec41b3b10fbc409e980239eb9 + md5: 70ca4626111579c3cd63a7108fe737f9 depends: - - icu >=75.1,<76.0a0 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.1,<6.0a0 - libzlib >=1.3.1,<2.0a0 @@ -5757,12 +5727,13 @@ packages: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - libxml2 2.15.0 + - icu <0.0a0 + - libxml2 2.15.1 license: MIT license_family: MIT purls: [] - size: 518883 - timestamp: 1758641386772 + size: 518135 + timestamp: 1761016320405 - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 md5: edb0dca6bc32e4f4789199455a1dbeb8 @@ -5814,21 +5785,20 @@ packages: purls: [] size: 55476 timestamp: 1727963768015 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.3-hfa2b4ca_0.conda - sha256: 54826ea90c80ca04640b0fc1a0b3aabfd0f4e60e03c270b2a919a3655f21bc78 - md5: b1dd38bdf96540a6dedf0d196108c9a1 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-21.1.4-hfa2b4ca_0.conda + sha256: 397d1874330592e57c6378a83dff194c6d1875cab44a41f9fdee8c3fe20bbe6b + md5: 5d56fdf8c9dc4c385704317e6743fca4 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - intel-openmp <0.0a0 - - openmp 21.1.3|21.1.3.* + - openmp 21.1.4|21.1.4.* license: Apache-2.0 WITH LLVM-exception - license_family: APACHE purls: [] - size: 347945 - timestamp: 1760282911326 + size: 347267 + timestamp: 1761131531490 - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl name: lmfit version: 1.3.4 @@ -6714,10 +6684,10 @@ packages: requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl name: narwhals - version: 2.8.0 - sha256: 6304856676ba4a79fd34148bda63aed8060dd6edb1227edf3659ce5e091de73c + version: 2.9.0 + sha256: c59f7de4763004ae81691ce16df71b4e55aead0ead7ccde8c8f2ef8c9559c765 requires_dist: - cudf>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' @@ -8292,10 +8262,10 @@ packages: version: 0.4.1 sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/3d/3c/b56076bb35303d0733fc47b110a1c9cce081a05ae2e886575a3587c1ee76/psutil-7.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: psutil - version: 7.1.0 - sha256: 5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5 + version: 7.1.1 + sha256: 92ebc58030fb054fa0f26c3206ef01c31c29d67aee1367e3483c16665c25c8d2 requires_dist: - pytest ; extra == 'dev' - pytest-instafail ; extra == 'dev' @@ -8318,6 +8288,7 @@ packages: - sphinx-rtd-theme ; extra == 'dev' - toml-sort ; extra == 'dev' - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - wheel ; extra == 'dev' @@ -8334,10 +8305,10 @@ packages: - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/51/30/f97f8fb1f9ecfbeae4b5ca738dcae66ab28323b5cfbc96cb5565f3754056/psutil-7.1.1-cp36-abi3-macosx_10_9_x86_64.whl name: psutil - version: 7.1.0 - sha256: 76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13 + version: 7.1.1 + sha256: 8fa59d7b1f01f0337f12cd10dbd76e4312a4d3c730a4fedcbdd4e5447a8b8460 requires_dist: - pytest ; extra == 'dev' - pytest-instafail ; extra == 'dev' @@ -8360,6 +8331,7 @@ packages: - sphinx-rtd-theme ; extra == 'dev' - toml-sort ; extra == 'dev' - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - wheel ; extra == 'dev' @@ -8376,10 +8348,10 @@ packages: - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/7b/98/b8d1f61ebf35f4dbdbaabadf9208282d8adc820562f0257e5e6e79e67bf2/psutil-7.1.1-cp36-abi3-macosx_11_0_arm64.whl name: psutil - version: 7.1.0 - sha256: 8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3 + version: 7.1.1 + sha256: 2a95104eae85d088891716db676f780c1404fc15d47fde48a46a5d61e8f5ad2c requires_dist: - pytest ; extra == 'dev' - pytest-instafail ; extra == 'dev' @@ -8402,6 +8374,7 @@ packages: - sphinx-rtd-theme ; extra == 'dev' - toml-sort ; extra == 'dev' - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - wheel ; extra == 'dev' @@ -8418,10 +8391,10 @@ packages: - wheel ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' - wmi ; os_name == 'nt' and platform_python_implementation != 'PyPy' and extra == 'test' requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/be/f3/11fd213fff15427bc2853552138760c720fd65032d99edfb161910d04127/psutil-7.1.1-cp37-abi3-win_amd64.whl name: psutil - version: 7.1.0 - sha256: 57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d + version: 7.1.1 + sha256: 9b4f17c5f65e44f69bd3a3406071a47b79df45cf2236d1f717970afcb526bcd3 requires_dist: - pytest ; extra == 'dev' - pytest-instafail ; extra == 'dev' @@ -8444,6 +8417,7 @@ packages: - sphinx-rtd-theme ; extra == 'dev' - toml-sort ; extra == 'dev' - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - wheel ; extra == 'dev' @@ -8659,10 +8633,10 @@ packages: purls: [] size: 30812188 timestamp: 1760365816536 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.8-h2b335a9_101_cp313.conda - build_number: 101 - sha256: b429867f0faf5b9b71e2ebdbe8fedd6f84f4ba53fd2010a1f1458e1e1a038b98 - md5: ae8cf86b9140c7b6a6593a582a8eab8a +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.9-h2b335a9_100_cp313.conda + build_number: 100 + sha256: 317ee7a38f4cc97336a2aedf9c79e445adf11daa0d082422afa63babcd5117e4 + md5: 78ba5c3a7aecc68ab3a9f396d3b69d06 depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 @@ -8683,8 +8657,8 @@ packages: - tzdata license: Python-2.0 purls: [] - size: 37149783 - timestamp: 1760366432739 + size: 37323303 + timestamp: 1760612239629 python_site_packages_path: lib/python3.13/site-packages - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.11.14-h3999593_1_cpython.conda build_number: 1 @@ -8709,10 +8683,10 @@ packages: purls: [] size: 15565919 timestamp: 1760366149530 -- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.8-h2bd861f_101_cp313.conda - build_number: 101 - sha256: 1e127a5a1b35e4f8187d9810f7eaf00993cc6854b7cf6b38245d4f5c63f7522a - md5: 90ec169b37f0e65c5ccceeb7a00d5696 +- conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.13.9-h2bd861f_100_cp313.conda + build_number: 100 + sha256: c5ae352b7ac8412ed0e9ca8cac2f36d767e96d8e3efb014f47fd103be7447f22 + md5: 9f7e2b7871a35025f30a890492a36578 depends: - __osx >=10.13 - bzip2 >=1.0.8,<2.0a0 @@ -8730,8 +8704,8 @@ packages: - tzdata license: Python-2.0 purls: [] - size: 17462902 - timestamp: 1760366920956 + size: 17336745 + timestamp: 1760613619143 python_site_packages_path: lib/python3.13/site-packages - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.14-hec0b533_1_cpython.conda build_number: 1 @@ -8756,10 +8730,10 @@ packages: purls: [] size: 14794480 timestamp: 1760366123572 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.8-h09175d0_101_cp313.conda - build_number: 101 - sha256: 5ff88415341058814a035375a6d9a0616769e280eebf72cddf8a8a426f572cec - md5: 71824735260cf57df846eb88aeb4fd99 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.9-h09175d0_100_cp313.conda + build_number: 100 + sha256: ba1121e96d034129832eff1bde7bba35f186acfc51fce1d7bacd29a544906f1b + md5: a2e4526d795a64fbd9581333482e07ee depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 @@ -8777,8 +8751,8 @@ packages: - tzdata license: Python-2.0 purls: [] - size: 12026833 - timestamp: 1760366828517 + size: 12802912 + timestamp: 1760613485744 python_site_packages_path: lib/python3.13/site-packages - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.14-h30ce641_1_cpython.conda build_number: 1 @@ -8803,10 +8777,10 @@ packages: purls: [] size: 18568514 timestamp: 1760364337404 -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.8-hdf00ec1_101_cp313.conda - build_number: 101 - sha256: 944fcdc88b452972a829a70f115e24c120bc573d6a34810e0a418c1ddcd73553 - md5: ce6c7617eccf6671a93c867c37cd8fa4 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.9-hdf00ec1_100_cp313.conda + build_number: 100 + sha256: 2d6d9d5c641d4a11b5bef148813b83eef4e2dffd68e1033362bad85924837f29 + md5: 89c006f6748c7e0fc7950ae0c80df0d5 depends: - bzip2 >=1.0.8,<2.0a0 - libexpat >=2.7.1,<3.0a0 @@ -8824,8 +8798,8 @@ packages: - vc14_runtime >=14.44.35208 license: Python-2.0 purls: [] - size: 16639232 - timestamp: 1760364470278 + size: 16503717 + timestamp: 1760610876821 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 @@ -8899,14 +8873,6 @@ packages: name: pytz version: '2025.2' sha256: 5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 -- pypi: https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl - name: pywin32 - version: '311' - sha256: 3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503 -- pypi: https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl - name: pywin32 - version: '311' - sha256: 718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d - pypi: https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl name: pywinpty version: 3.0.2 @@ -9146,25 +9112,25 @@ packages: version: 0.27.1 sha256: cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl name: ruff - version: 0.14.0 - sha256: eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e + version: 0.14.1 + sha256: 59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl name: ruff - version: 0.14.0 - sha256: ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2 + version: 0.14.1 + sha256: d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ruff - version: 0.14.0 - sha256: 703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8 + version: 0.14.1 + sha256: cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl name: ruff - version: 0.14.0 - sha256: 838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8 + version: 0.14.1 + sha256: f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224 requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl name: scipy @@ -10132,21 +10098,16 @@ packages: name: trove-classifiers version: 2025.9.11.17 sha256: 5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd -- pypi: https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl name: typer - version: 0.19.2 - sha256: 755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9 + version: 0.20.0 + sha256: 5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a requires_dist: - click>=8.0.0 - typing-extensions>=3.7.4.3 - shellingham>=1.3.0 - rich>=10.11.0 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/da/af/5d24b8d49ef358468ecfdff5c556adf37f4fd28e336b96f923661a808329/types_python_dateutil-2.9.0.20251008-py3-none-any.whl - name: types-python-dateutil - version: 2.9.0.20251008 - sha256: b9a5232c8921cf7661b29c163ccc56055c418ab2c6eabe8f917cbcc73a4c4157 - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl name: typing-extensions version: 4.15.0 @@ -10230,25 +10191,25 @@ packages: - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' - zstandard>=0.18.0 ; extra == 'zstd' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/16/25/6df8be6cd549200e80d19374579689fda39b18735afde841345284fb113d/uv-0.9.3-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/4e/6c/3508d67f80aac0ddb5806680a6735ff6cb5a14e9b697e5ae145b01050880/uv-0.9.5-py3-none-macosx_10_12_x86_64.whl name: uv - version: 0.9.3 - sha256: 741e80c4230e1b9a5d0869aca2fb082b3832b251ef61537bc9278364b8e74df2 + version: 0.9.5 + sha256: 922cd784cce36bbdc7754b590d28c276698c85791c18cd4c6a7e917db4480440 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/82/45/488417c6c0127c00bcdfac3556ae2ea0597df8245fe5f9bcfda35ebdbe85/uv-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/9b/83/a0bdf4abf86ede79b427778fe27e2b4a022c98a7a8ea1745dcd6c6561f17/uv-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: uv - version: 0.9.3 - sha256: 73ae4bbc7d555ba1738da08c64b55f21ab0ea0ff85636708cebaf460d98a440d + version: 0.9.5 + sha256: 6507bbbcd788553ec4ad5a96fa19364dc0f58b023e31d79868773559a83ec181 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/87/cd/667f6249a9a3a8d2d7ba1aa72db6b1fc6cdaf7b0d7aeda43478702e2a13e/uv-0.9.3-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/af/14/0f07d0b2e561548b4e3006208480a5fce8cdaae5247d85efbfb56e8e596b/uv-0.9.5-py3-none-win_amd64.whl name: uv - version: 0.9.3 - sha256: 55516bf85c44a00b948472ddda80d7c5cd9990e9b5c085dc5005da93f40266a7 + version: 0.9.5 + sha256: 48a12390421f91af8a8993cf15c38297c0bb121936046286e287975b2fbf1789 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/d0/1a/8e68d0020c29f6f329a265773c23b0c01e002794ea884b8bdbd594c7ea97/uv-0.9.3-py3-none-macosx_10_12_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b2/26/bd6438cf6d84a6b0b608bcbe9f353d8e424f8fe3b1b73a768984a76bf80b/uv-0.9.5-py3-none-macosx_11_0_arm64.whl name: uv - version: 0.9.3 - sha256: 596a982c5a061d58412824a2ebe2960b52db23f1b1658083ba9c0e7ae390308a + version: 0.9.5 + sha256: 8603bb902e578463c50c3ddd4ee376ba4172ccdf4979787f8948747d1bb0e18b requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/a4/39/6983dd79f01aaa4c75d9ffa550fa393f0c4c28f7ccd6956e4188c62cefbc/validate_pyproject-0.24.1-py3-none-any.whl name: validate-pyproject diff --git a/pixi.toml b/pixi.toml index da1fc721..147e1c1b 100644 --- a/pixi.toml +++ b/pixi.toml @@ -177,6 +177,7 @@ docs-notebooks = 'mv tutorials/*.ipynb docs/tutorials/' docs-config = 'python tools/create_mkdocs_yml.py' 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', diff --git a/pyproject.toml b/pyproject.toml index bfe639e0..d262f0a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,10 +74,11 @@ docs = [ 'pyyaml', # YAML parser ] visualization = [ - 'darkdetect', # Detecting dark mode - 'pandas', # Displaying tables in juptyer notebooks - 'plotly', # Interactive plots - 'py3Dmol', # Visualisation of crystal structures + 'darkdetect', # Detecting dark mode + 'jupyter_dark_detect', # Jupyter notebook dark mode support + 'pandas', # Displaying tables in juptyer notebooks + 'plotly', # Interactive plots + 'py3Dmol', # Visualisation of crystal structures ] all = [ 'easydiffraction[dev]', diff --git a/pytest.ini b/pytest.ini index bb1f5ba8..a7933d6a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -8,3 +8,6 @@ testpaths = 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 ac7a581c..b66c16f6 100644 --- a/src/easydiffraction/__init__.py +++ b/src/easydiffraction/__init__.py @@ -1,42 +1,28 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause -from importlib import import_module - -from easydiffraction.utils.formatting import chapter -from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.formatting import section +from easydiffraction.experiments.experiment.factory import ExperimentFactory +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 - -Logger.configure() - -_LAZY_ENTRIES = [ - ('easydiffraction.project.project', 'Project'), - ('easydiffraction.experiments.experiment.factory', 'ExperimentFactory'), - ('easydiffraction.sample_models.sample_model.factory', 'SampleModelFactory'), - ('easydiffraction.utils.utils', 'download_from_repository'), - ('easydiffraction.utils.utils', 'fetch_tutorials'), - ('easydiffraction.utils.utils', 'list_tutorials'), - ('easydiffraction.utils.utils', 'get_value_from_xye_header'), - ('easydiffraction.utils.utils', 'show_version'), -] - -_LAZY_MAP = {attr_name: module_name for module_name, attr_name in _LAZY_ENTRIES} - -__all__ = list(_LAZY_MAP.keys()) + [ +from easydiffraction.utils.utils import download_from_repository +from easydiffraction.utils.utils import fetch_tutorials +from easydiffraction.utils.utils import get_value_from_xye_header +from easydiffraction.utils.utils import list_tutorials +from easydiffraction.utils.utils import show_version + +__all__ = [ + 'Project', + 'ExperimentFactory', + 'SampleModelFactory', + 'download_from_repository', + 'fetch_tutorials', + 'list_tutorials', + 'get_value_from_xye_header', + 'show_version', 'Logger', 'log', - 'chapter', - 'section', - 'paragraph', + 'console', ] - - -def __getattr__(name): - if name not in _LAZY_MAP: - raise AttributeError() - module_name = _LAZY_MAP[name] - module = import_module(module_name) - attr = getattr(module, name) - return attr diff --git a/src/easydiffraction/analysis/analysis.py b/src/easydiffraction/analysis/analysis.py index b0bbc732..e3c6fb4b 100644 --- a/src/easydiffraction/analysis/analysis.py +++ b/src/easydiffraction/analysis/analysis.py @@ -15,10 +15,12 @@ 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.display.tables import TableRenderer from easydiffraction.experiments.experiments import Experiments -from easydiffraction.utils.formatting import paragraph -from easydiffraction.utils.formatting import warning +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 @@ -73,35 +75,37 @@ def _get_params_as_dataframe( Returns: A pandas DataFrame containing parameter information. """ - rows = [] + records = [] for param in params: - common_attrs = {} + record = {} + # TODO: Merge into one. Add field if attr exists + # TODO: f'{param.value!r}' for StringDescriptor? + if isinstance(param, (StringDescriptor, NumericDescriptor, Parameter)): + record = { + ('fittable', 'left'): False, + ('datablock', 'left'): param._identity.datablock_entry_name, + ('category', 'left'): param._identity.category_code, + ('entry', 'left'): param._identity.category_entry_name or '', + ('parameter', 'left'): param.name, + ('value', 'right'): param.value, + } if isinstance(param, (NumericDescriptor, Parameter)): - common_attrs = { - 'datablock': param._identity.datablock_entry_name, - 'category': param._identity.category_code, - 'entry': param._identity.category_entry_name, - 'parameter': param.name, - 'value': param.value, - 'units': param.units, - 'fittable': False, + record = record | { + ('units', 'left'): param.units, } - param_attrs = {} if isinstance(param, Parameter): - param_attrs = { - 'fittable': True, - 'free': param.free, - 'min': param.fit_min, - 'max': param.fit_max, - 'uncertainty': f'{param.uncertainty:.4f}' if param.uncertainty else '', - 'value': f'{param.value:.4f}', - 'units': param.units, + record = record | { + ('fittable', 'left'): True, + ('free', 'left'): param.free, + ('min', 'right'): param.fit_min, + ('max', 'right'): param.fit_max, + ('uncertainty', 'right'): param.uncertainty or '', } - row = common_attrs | param_attrs - rows.append(row) + records.append(record) - dataframe = pd.DataFrame(rows) - return dataframe + df = pd.DataFrame.from_records(records) + df.columns = pd.MultiIndex.from_tuples(df.columns) + return df def show_all_params(self) -> None: """Print a table with all parameters for sample models and @@ -111,10 +115,12 @@ def show_all_params(self) -> None: experiments_params = self.project.experiments.parameters if not sample_models_params and not experiments_params: - print(warning('No parameters found.')) + log.warning('No parameters found.') return - columns_headers = [ + tabler = TableRenderer.get() + + filtered_headers = [ 'datablock', 'category', 'entry', @@ -122,36 +128,16 @@ def show_all_params(self) -> None: 'value', 'fittable', ] - columns_alignment = [ - 'left', - 'left', - 'left', - 'left', - 'right', - 'left', - ] - - sample_models_dataframe = self._get_params_as_dataframe(sample_models_params) - sample_models_dataframe = sample_models_dataframe[columns_headers] - print(paragraph('All parameters for all sample models (🧩 data blocks)')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=sample_models_dataframe, - show_index=True, - ) + console.paragraph('All parameters for all sample models (🧩 data blocks)') + df = self._get_params_as_dataframe(sample_models_params) + filtered_df = df[filtered_headers] + tabler.render(filtered_df) - experiments_dataframe = self._get_params_as_dataframe(experiments_params) - experiments_dataframe = experiments_dataframe[columns_headers] - - print(paragraph('All parameters for all experiments (🔬 data blocks)')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=experiments_dataframe, - show_index=True, - ) + console.paragraph('All parameters for all experiments (🔬 data blocks)') + df = self._get_params_as_dataframe(experiments_params) + filtered_df = df[filtered_headers] + tabler.render(filtered_df) def show_fittable_params(self) -> None: """Print a table with parameters that can be included in @@ -161,10 +147,12 @@ def show_fittable_params(self) -> None: experiments_params = self.project.experiments.fittable_parameters if not sample_models_params and not experiments_params: - print(warning('No fittable parameters found.')) + log.warning('No fittable parameters found.') return - columns_headers = [ + tabler = TableRenderer.get() + + filtered_headers = [ 'datablock', 'category', 'entry', @@ -174,38 +162,16 @@ def show_fittable_params(self) -> None: 'units', 'free', ] - columns_alignment = [ - 'left', - 'left', - 'left', - 'left', - 'right', - 'right', - 'left', - 'left', - ] - sample_models_dataframe = self._get_params_as_dataframe(sample_models_params) - sample_models_dataframe = sample_models_dataframe[columns_headers] + console.paragraph('Fittable parameters for all sample models (🧩 data blocks)') + df = self._get_params_as_dataframe(sample_models_params) + filtered_df = df[filtered_headers] + tabler.render(filtered_df) - print(paragraph('Fittable parameters for all sample models (🧩 data blocks)')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=sample_models_dataframe, - show_index=True, - ) - - experiments_dataframe = self._get_params_as_dataframe(experiments_params) - experiments_dataframe = experiments_dataframe[columns_headers] - - print(paragraph('Fittable parameters for all experiments (🔬 data blocks)')) - render_table( - columns_headers=columns_headers, - columns_alignment=columns_alignment, - columns_data=experiments_dataframe, - show_index=True, - ) + console.paragraph('Fittable parameters for all experiments (🔬 data blocks)') + df = self._get_params_as_dataframe(experiments_params) + filtered_df = df[filtered_headers] + tabler.render(filtered_df) def show_free_params(self) -> None: """Print a table with only currently-free (varying) @@ -216,10 +182,12 @@ def show_free_params(self) -> None: free_params = sample_models_params + experiments_params if not free_params: - print(warning('No free parameters found.')) + log.warning('No free parameters found.') return - columns_headers = [ + tabler = TableRenderer.get() + + filtered_headers = [ 'datablock', 'category', 'entry', @@ -230,41 +198,84 @@ def show_free_params(self) -> None: 'max', 'units', ] + + console.paragraph( + 'Free parameters for both sample models (🧩 data blocks) ' + 'and experiments (🔬 data blocks)' + ) + df = self._get_params_as_dataframe(free_params) + filtered_df = df[filtered_headers] + tabler.render(filtered_df) + + def how_to_access_parameters(self) -> None: + """Show Python access paths for all parameters. + + The output explains how to reference specific parameters in + code. + """ + sample_models_params = self.project.sample_models.parameters + experiments_params = self.project.experiments.parameters + all_params = { + 'sample_models': sample_models_params, + 'experiments': experiments_params, + } + + if not all_params: + log.warning('No parameters found.') + return + + columns_headers = [ + 'datablock', + 'category', + 'entry', + 'parameter', + 'How to Access in Python Code', + ] + columns_alignment = [ 'left', 'left', 'left', 'left', - 'right', - 'right', - 'right', - 'right', 'left', ] - dataframe = self._get_params_as_dataframe(free_params) - dataframe = dataframe[columns_headers] + columns_data = [] + project_varname = self.project._varname + for datablock_code, params in all_params.items(): + for param in params: + if isinstance(param, (StringDescriptor, NumericDescriptor, Parameter)): + datablock_entry_name = param._identity.datablock_entry_name + category_code = param._identity.category_code + category_entry_name = param._identity.category_entry_name or '' + param_key = param.name + code_variable = ( + f'{project_varname}.{datablock_code}' + f"['{datablock_entry_name}'].{category_code}" + ) + if category_entry_name: + code_variable += f"['{category_entry_name}']" + code_variable += f'.{param_key}' + columns_data.append([ + datablock_entry_name, + category_code, + category_entry_name, + param_key, + code_variable, + ]) - print( - paragraph( - 'Free parameters for both sample models (🧩 data blocks) ' - 'and experiments (🔬 data blocks)' - ) - ) + console.paragraph('How to access parameters') render_table( columns_headers=columns_headers, columns_alignment=columns_alignment, - columns_data=dataframe, - show_index=True, + columns_data=columns_data, ) - def how_to_access_parameters(self) -> None: - """Show Python access paths and CIF unique IDs for all - parameters. + def show_parameter_cif_uids(self) -> None: + """Show CIF unique IDs for all parameters. - The output explains how to reference specific parameters in code - and which unique identifiers are used when creating CIF-based - constraints. + The output explains which unique identifiers are used when + creating CIF-based constraints. """ sample_models_params = self.project.sample_models.parameters experiments_params = self.project.experiments.parameters @@ -274,7 +285,7 @@ def how_to_access_parameters(self) -> None: } if not all_params: - print(warning('No parameters found.')) + log.warning('No parameters found.') return columns_headers = [ @@ -282,7 +293,6 @@ def how_to_access_parameters(self) -> None: 'category', 'entry', 'parameter', - 'How to Access in Python Code', 'Unique Identifier for CIF Constraints', ] @@ -292,49 +302,38 @@ def how_to_access_parameters(self) -> None: 'left', 'left', 'left', - 'left', ] columns_data = [] - project_varname = self.project._varname - for datablock_type, params in all_params.items(): + for _, params in all_params.items(): for param in params: - if isinstance(param, (NumericDescriptor, Parameter)): + if isinstance(param, (StringDescriptor, NumericDescriptor, Parameter)): datablock_entry_name = param._identity.datablock_entry_name category_code = param._identity.category_code - category_entry_name = param._identity.category_entry_name + category_entry_name = param._identity.category_entry_name or '' param_key = param.name - code_variable = ( - f'{project_varname}.{datablock_type}' - f"['{datablock_entry_name}'].{category_code}" - ) - if category_entry_name: - code_variable += f"['{category_entry_name}']" - code_variable += f'.{param_key}' cif_uid = param._cif_handler.uid columns_data.append([ datablock_entry_name, category_code, category_entry_name, param_key, - code_variable, cif_uid, ]) - print(paragraph('How to access parameters')) + console.paragraph('Show parameter CIF unique identifiers') render_table( columns_headers=columns_headers, columns_alignment=columns_alignment, columns_data=columns_data, - show_index=True, ) def show_current_calculator(self) -> None: """Print the name of the currently selected calculator engine. """ - print(paragraph('Current calculator')) - print(self.current_calculator) + console.paragraph('Current calculator') + console.print(self.current_calculator) @staticmethod def show_supported_calculators() -> None: @@ -360,13 +359,13 @@ def current_calculator(self, calculator_name: str) -> None: return self.calculator = calculator self._calculator_key = calculator_name - print(paragraph('Current calculator changed to')) - print(self.current_calculator) + 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.""" - print(paragraph('Current minimizer')) - print(self.current_minimizer) + console.paragraph('Current minimizer') + console.print(self.current_minimizer) @staticmethod def show_available_minimizers() -> None: @@ -389,8 +388,8 @@ def current_minimizer(self, selection: str) -> None: 'lmfit (leastsq)'. """ self.fitter = Fitter(selection) - print(paragraph('Current minimizer changed to')) - print(self.current_minimizer) + console.paragraph('Current minimizer changed to') + console.print(self.current_minimizer) @property def fit_mode(self) -> str: @@ -419,8 +418,8 @@ def fit_mode(self, strategy: str) -> None: self.joint_fit_experiments = JointFitExperiments() for id in self.project.experiments.names: self.joint_fit_experiments.add_from_args(id=id, weight=0.5) - print(paragraph('Current fit mode changed to')) - print(self._fit_mode) + 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 @@ -446,7 +445,7 @@ def show_available_fit_modes(self) -> None: description = item['Description'] columns_data.append([strategy, description]) - print(paragraph('Available fit modes')) + console.paragraph('Available fit modes') render_table( columns_headers=columns_headers, columns_alignment=columns_alignment, @@ -455,8 +454,8 @@ def show_available_fit_modes(self) -> None: def show_current_fit_mode(self) -> None: """Print the currently active fitting strategy.""" - print(paragraph('Current fit mode')) - print(self.fit_mode) + console.paragraph('Current fit mode') + console.print(self.fit_mode) def calculate_pattern(self, expt_name: str) -> None: """Calculate and store the diffraction pattern for an @@ -476,7 +475,7 @@ def show_constraints(self) -> None: constraints_dict = dict(self.constraints) if not self.constraints._items: - print(warning('No constraints defined.')) + log.warning('No constraints defined.') return rows = [] @@ -492,7 +491,7 @@ def show_constraints(self) -> None: alignments = ['left', 'left', 'left'] rows = [[row[header] for header in headers] for row in rows] - print(paragraph('User defined constraints')) + console.paragraph('User defined constraints') render_table( columns_headers=headers, columns_alignment=alignments, @@ -504,7 +503,7 @@ def apply_constraints(self): project. """ if not self.constraints._items: - print(warning('No constraints defined.')) + log.warning('No constraints defined.') return self.constraints_handler.set_aliases(self.aliases) @@ -522,27 +521,23 @@ def fit(self): """ sample_models = self.project.sample_models if not sample_models: - print('No sample models found in the project. Cannot run fit.') + log.warning('No sample models found in the project. Cannot run fit.') return experiments = self.project.experiments if not experiments: - print('No experiments found in the project. Cannot run fit.') + log.warning('No experiments found in the project. Cannot run fit.') return calculator = self.calculator if not calculator: - print('No calculator is set. Cannot run fit.') + log.warning('No calculator is set. Cannot run fit.') return # Run the fitting process - experiment_ids = experiments.names - if self.fit_mode == 'joint': - print( - paragraph( - f"Using all experiments 🔬 {experiment_ids} for '{self.fit_mode}' fitting" - ) + console.paragraph( + f"Using all experiments 🔬 {experiments.names} for '{self.fit_mode}' fitting" ) self.fitter.fit( sample_models, @@ -552,8 +547,8 @@ def fit(self): ) elif self.fit_mode == 'single': for expt_name in experiments.names: - print( - paragraph(f"Using experiment 🔬 '{expt_name}' for '{self.fit_mode}' fitting") + console.paragraph( + f"Using experiment 🔬 '{expt_name}' for '{self.fit_mode}' fitting" ) experiment = experiments[expt_name] dummy_experiments = Experiments() # TODO: Find a better name @@ -580,5 +575,6 @@ def show_as_cif(self) -> None: view. """ cif_text: str = self.as_cif() - paragraph_title: str = paragraph('Analysis 🧮 info as cif') - render_cif(cif_text, paragraph_title) + paragraph_title: str = 'Analysis 🧮 info as cif' + console.paragraph(paragraph_title) + render_cif(cif_text) diff --git a/src/easydiffraction/analysis/calculators/factory.py b/src/easydiffraction/analysis/calculators/factory.py index e4e447c2..5392c5a6 100644 --- a/src/easydiffraction/analysis/calculators/factory.py +++ b/src/easydiffraction/analysis/calculators/factory.py @@ -11,8 +11,8 @@ from easydiffraction.analysis.calculators.crysfml import CrysfmlCalculator from easydiffraction.analysis.calculators.cryspy import CryspyCalculator from easydiffraction.analysis.calculators.pdffit import PdffitCalculator -from easydiffraction.utils.formatting import error -from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.logging import console +from easydiffraction.utils.logging import log from easydiffraction.utils.utils import render_table @@ -77,7 +77,7 @@ def show_supported_calculators(cls) -> None: description: str = config.get('description', 'No description provided.') columns_data.append([name, description]) - print(paragraph('Supported calculators')) + console.paragraph('Supported calculators') render_table( columns_headers=columns_headers, columns_alignment=columns_alignment, @@ -96,8 +96,10 @@ def create_calculator(cls, calculator_name: str) -> Optional[CalculatorBase]: """ config = cls._supported_calculators().get(calculator_name) if not config: - print(error(f"Unknown calculator '{calculator_name}'")) - print(f'Supported calculators: {cls.list_supported_calculators()}') + log.warning( + f"Unknown calculator '{calculator_name}', " + f'Supported calculators: {cls.list_supported_calculators()}' + ) return None return config['class']() diff --git a/src/easydiffraction/analysis/categories/aliases.py b/src/easydiffraction/analysis/categories/aliases.py index 5a7faaa6..cb171742 100644 --- a/src/easydiffraction/analysis/categories/aliases.py +++ b/src/easydiffraction/analysis/categories/aliases.py @@ -67,7 +67,7 @@ def __init__( ) self._identity.category_code = 'alias' - self._identity.category_entry_name = lambda: self.label.value + self._identity.category_entry_name = lambda: str(self.label.value) @property def label(self): diff --git a/src/easydiffraction/analysis/categories/constraints.py b/src/easydiffraction/analysis/categories/constraints.py index f99f93b1..ff9d77b5 100644 --- a/src/easydiffraction/analysis/categories/constraints.py +++ b/src/easydiffraction/analysis/categories/constraints.py @@ -63,7 +63,7 @@ def __init__( ) self._identity.category_code = 'constraint' - self._identity.category_entry_name = lambda: self.lhs_alias.value + self._identity.category_entry_name = lambda: str(self.lhs_alias.value) @property def lhs_alias(self): diff --git a/src/easydiffraction/analysis/categories/joint_fit_experiments.py b/src/easydiffraction/analysis/categories/joint_fit_experiments.py index 119e413d..a6422236 100644 --- a/src/easydiffraction/analysis/categories/joint_fit_experiments.py +++ b/src/easydiffraction/analysis/categories/joint_fit_experiments.py @@ -65,7 +65,7 @@ def __init__( ) self._identity.category_code = 'joint_fit_experiment' - self._identity.category_entry_name = lambda: self.id.value + self._identity.category_entry_name = lambda: str(self.id.value) @property def id(self): diff --git a/src/easydiffraction/analysis/fit_helpers/reporting.py b/src/easydiffraction/analysis/fit_helpers/reporting.py index 76e8c9f6..dc3edf6e 100644 --- a/src/easydiffraction/analysis/fit_helpers/reporting.py +++ b/src/easydiffraction/analysis/fit_helpers/reporting.py @@ -5,11 +5,11 @@ from typing import List from typing import Optional -from easydiffraction import paragraph from easydiffraction.analysis.fit_helpers.metrics import calculate_r_factor from easydiffraction.analysis.fit_helpers.metrics import calculate_r_factor_squared from easydiffraction.analysis.fit_helpers.metrics import calculate_rb_factor from easydiffraction.analysis.fit_helpers.metrics import calculate_weighted_r_factor +from easydiffraction.utils.logging import console from easydiffraction.utils.utils import render_table @@ -96,19 +96,19 @@ def display_results( if f_obs is not None and f_calc is not None: br = calculate_rb_factor(f_obs, f_calc) * 100 - print(paragraph('Fit results')) - print(f'{status_icon} Success: {self.success}') - print(f'⏱️ Fitting time: {self.fitting_time:.2f} seconds') - print(f'📏 Goodness-of-fit (reduced χ²): {self.reduced_chi_square:.2f}') + console.paragraph('Fit results') + console.print(f'{status_icon} Success: {self.success}') + console.print(f'⏱️ Fitting time: {self.fitting_time:.2f} seconds') + console.print(f'📏 Goodness-of-fit (reduced χ²): {self.reduced_chi_square:.2f}') if rf is not None: - print(f'📏 R-factor (Rf): {rf:.2f}%') + console.print(f'📏 R-factor (Rf): {rf:.2f}%') if rf2 is not None: - print(f'📏 R-factor squared (Rf²): {rf2:.2f}%') + console.print(f'📏 R-factor squared (Rf²): {rf2:.2f}%') if wr is not None: - print(f'📏 Weighted R-factor (wR): {wr:.2f}%') + console.print(f'📏 Weighted R-factor (wR): {wr:.2f}%') if br is not None: - print(f'📏 Bragg R-factor (BR): {br:.2f}%') - print('📈 Fitted parameters:') + console.print(f'📏 Bragg R-factor (BR): {br:.2f}%') + console.print('📈 Fitted parameters:') headers = [ 'datablock', @@ -175,5 +175,4 @@ def display_results( columns_headers=headers, columns_alignment=alignments, columns_data=rows, - show_index=True, ) diff --git a/src/easydiffraction/analysis/fit_helpers/tracking.py b/src/easydiffraction/analysis/fit_helpers/tracking.py index 94629592..9b98e68c 100644 --- a/src/easydiffraction/analysis/fit_helpers/tracking.py +++ b/src/easydiffraction/analysis/fit_helpers/tracking.py @@ -2,11 +2,15 @@ # SPDX-License-Identifier: BSD-3-Clause import time +from contextlib import suppress +from typing import Any from typing import List from typing import Optional import numpy as np +from easydiffraction.utils.logging import console + try: from IPython.display import HTML from IPython.display import DisplayHandle @@ -16,29 +20,60 @@ clear_output = None from easydiffraction.analysis.fit_helpers.metrics import calculate_reduced_chi_square -from easydiffraction.utils.utils import is_notebook +from easydiffraction.utils.environment import in_jupyter from easydiffraction.utils.utils import render_table +try: + from rich.live import Live +except Exception: # pragma: no cover - rich always available in app env + Live = None # type: ignore[assignment] + +from easydiffraction.utils.logging import ConsoleManager + SIGNIFICANT_CHANGE_THRESHOLD = 0.01 # 1% threshold -FIXED_WIDTH = 17 DEFAULT_HEADERS = ['iteration', 'χ²', 'improvement [%]'] DEFAULT_ALIGNMENTS = ['center', 'center', 'center'] -def format_cell( - cell: str, - width: int = FIXED_WIDTH, - align: str = 'center', -) -> str: - cell_str = str(cell) - if align == 'center': - return cell_str.center(width) - elif align == 'left': - return cell_str.ljust(width) - elif align == 'right': - return cell_str.rjust(width) - else: - return cell_str +class _TerminalLiveHandle: + """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: + self._live = live + + def update(self, renderable) -> None: + self._live.update(renderable, refresh=True) + + def close(self) -> None: + with suppress(Exception): + self._live.stop() + + +def _make_display_handle() -> Any | 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. + """ + if in_jupyter() and display is not None and HTML is not None: + h = DisplayHandle() + # Create an empty placeholder area to update in place + h.display(HTML('')) + return h + if Live is not None: + # Reuse the shared Console to coordinate with logging output + # and keep consistent width + live = Live(console=ConsoleManager.get(), auto_refresh=True) + live.start() + return _TerminalLiveHandle(live) + return None class FitProgressTracker: @@ -59,7 +94,8 @@ def __init__(self) -> None: self._fitting_time: Optional[float] = None self._df_rows: List[List[str]] = [] - self._display_handle: Optional[DisplayHandle] = None + self._display_handle: Optional[Any] = None + self._live: Optional[Any] = None def reset(self) -> None: """Reset internal state before a new optimization run.""" @@ -169,38 +205,20 @@ def start_tracking(self, minimizer_name: str) -> None: Args: minimizer_name: Name of the minimizer used for the run. """ - print(f"🚀 Starting fit process with '{minimizer_name}'...") - print('📈 Goodness-of-fit (reduced χ²) change:') - - if is_notebook() and display is not None: - # Reset the DataFrame rows - self._df_rows = [] - - # Recreate display handle for updating the table - self._display_handle = DisplayHandle() - - # Create placeholder for display - self._display_handle.display(HTML('')) - - # Show empty table with headers - render_table( - columns_data=self._df_rows, - columns_alignment=DEFAULT_ALIGNMENTS, - columns_headers=DEFAULT_HEADERS, - display_handle=self._display_handle, - ) - else: - # Top border - print('╒' + '╤'.join(['═' * FIXED_WIDTH for _ in DEFAULT_HEADERS]) + '╕') - - # Header row (all centered) - header_row = ( - '│' + '│'.join([format_cell(h, align='center') for h in DEFAULT_HEADERS]) + '│' - ) - print(header_row) - - # Separator - print('╞' + '╪'.join(['═' * FIXED_WIDTH for _ in DEFAULT_HEADERS]) + '╡') + console.print(f"🚀 Starting fit process with '{minimizer_name}'...") + console.print('📈 Goodness-of-fit (reduced χ²) change:') + + # Reset rows and create an environment-appropriate handle + self._df_rows = [] + self._display_handle = _make_display_handle() + + # Initial empty table; subsequent updates will reuse the handle + render_table( + columns_headers=DEFAULT_HEADERS, + columns_alignment=DEFAULT_ALIGNMENTS, + columns_data=self._df_rows, + display_handle=self._display_handle, + ) def add_tracking_info(self, row: List[str]) -> None: """Append a formatted row to the progress display. @@ -208,29 +226,15 @@ def add_tracking_info(self, row: List[str]) -> None: Args: row: Columns corresponding to DEFAULT_HEADERS. """ - if is_notebook() and display is not None: - # Add row to DataFrame - self._df_rows.append(row) - - # Show fully updated table - render_table( - columns_data=self._df_rows, - columns_alignment=DEFAULT_ALIGNMENTS, - columns_headers=DEFAULT_HEADERS, - display_handle=self._display_handle, - ) - else: - # Alignments for each column - formatted_row = ( - '│' - + '│'.join([ - format_cell(cell, align=DEFAULT_ALIGNMENTS[i]) for i, cell in enumerate(row) - ]) - + '│' - ) - - # Print the new row - print(formatted_row) + # 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, + columns_data=self._df_rows, + display_handle=self._display_handle, + ) def finish_tracking(self) -> None: """Finalize progress display and print best result summary.""" @@ -242,14 +246,14 @@ def finish_tracking(self) -> None: ] self.add_tracking_info(row) - # Bottom border for terminal only - if not is_notebook() or display is None: - # Bottom border for terminal only - print('╘' + '╧'.join(['═' * FIXED_WIDTH for _ in range(len(row))]) + '╛') + # Close terminal live if used + if self._display_handle is not None and hasattr(self._display_handle, 'close'): + with suppress(Exception): + self._display_handle.close() # Print best result - print( + console.print( f'🏆 Best goodness-of-fit (reduced χ²) is {self._best_chi2:.2f} ' f'at iteration {self._best_iteration}' ) - print('✅ Fitting complete.') + console.print('✅ Fitting complete.') diff --git a/src/easydiffraction/analysis/minimizers/factory.py b/src/easydiffraction/analysis/minimizers/factory.py index 3d5d6aba..e882ed62 100644 --- a/src/easydiffraction/analysis/minimizers/factory.py +++ b/src/easydiffraction/analysis/minimizers/factory.py @@ -10,7 +10,7 @@ from easydiffraction.analysis.minimizers.base import MinimizerBase from easydiffraction.analysis.minimizers.dfols import DfolsMinimizer from easydiffraction.analysis.minimizers.lmfit import LmfitMinimizer -from easydiffraction.utils.formatting import paragraph +from easydiffraction.utils.logging import console from easydiffraction.utils.utils import render_table @@ -67,7 +67,7 @@ def show_available_minimizers(cls) -> None: description: str = config.get('description', 'No description provided.') columns_data.append([name, description]) - print(paragraph('Supported minimizers')) + console.paragraph('Supported minimizers') render_table( columns_headers=columns_headers, columns_alignment=columns_alignment, diff --git a/src/easydiffraction/core/diagnostic.py b/src/easydiffraction/core/diagnostic.py index 38da87a1..846c8ed1 100644 --- a/src/easydiffraction/core/diagnostic.py +++ b/src/easydiffraction/core/diagnostic.py @@ -8,7 +8,7 @@ import difflib -from easydiffraction import log +from easydiffraction.utils.logging import log class Diagnostics: @@ -165,7 +165,7 @@ def validated(name, value, stage: str | None = None): @staticmethod def _log_error(msg, exc_type=Exception): """Emit an error-level message via shared logger.""" - log.error(message=msg, exc_type=exc_type) + log.error(msg, exc_type=exc_type) @staticmethod def _log_error_with_fallback( @@ -179,12 +179,12 @@ def _log_error_with_fallback( msg += f' Keeping current {current!r}.' else: msg += f' Using default {default!r}.' - log.error(message=msg, exc_type=exc_type) + log.error(msg, exc_type=exc_type) @staticmethod def _log_debug(msg): """Emit a debug-level message via shared logger.""" - log.debug(message=msg) + log.debug(msg) # ============================================================== # Suggestion and allowed value helpers diff --git a/src/easydiffraction/crystallography/crystallography.py b/src/easydiffraction/crystallography/crystallography.py index c5a7fc9f..381764a7 100644 --- a/src/easydiffraction/crystallography/crystallography.py +++ b/src/easydiffraction/crystallography/crystallography.py @@ -14,6 +14,7 @@ from sympy import sympify from easydiffraction.crystallography.space_groups import SPACE_GROUPS +from easydiffraction.utils.logging import log def apply_cell_symmetry_constraints( @@ -33,13 +34,13 @@ def apply_cell_symmetry_constraints( it_number = get_it_number_by_name_hm_short(name_hm) if it_number is None: error_msg = f"Failed to get IT_number for name_H-M '{name_hm}'" - print(error_msg) + log.error(error_msg) # TODO: ValueError? Diagnostics? return cell crystal_system = get_crystal_system_by_it_number(it_number) if crystal_system is None: error_msg = f"Failed to get crystal system for IT_number '{it_number}'" - print(error_msg) + log.error(error_msg) # TODO: ValueError? Diagnostics? return cell if crystal_system == 'cubic': @@ -78,7 +79,7 @@ def apply_cell_symmetry_constraints( else: error_msg = f'Unknown or unsupported crystal system: {crystal_system}' - print(error_msg) + log.error(error_msg) # TODO: ValueError? Diagnostics? return cell @@ -104,13 +105,13 @@ def apply_atom_site_symmetry_constraints( it_number = get_it_number_by_name_hm_short(name_hm) if it_number is None: error_msg = f"Failed to get IT_number for name_H-M '{name_hm}'" - print(error_msg) + log.error(error_msg) # TODO: ValueError? Diagnostics? return atom_site it_coordinate_system_code = coord_code if it_coordinate_system_code is None: error_msg = 'IT_coordinate_system_code is not set' - print(error_msg) + log.error(error_msg) # TODO: ValueError? Diagnostics? return atom_site space_group_entry = SPACE_GROUPS[(it_number, it_coordinate_system_code)] diff --git a/src/easydiffraction/display/__init__.py b/src/easydiffraction/display/__init__.py new file mode 100644 index 00000000..43a2b73b --- /dev/null +++ b/src/easydiffraction/display/__init__.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Display subsystem for tables and plots. + +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`. +""" + +# TODO: The following works in Jupyter, but breaks MkDocs builds. +# Disable for now. +# from easydiffraction.display.utils import JupyterScrollManager +# JupyterScrollManager.disable_jupyter_scroll() diff --git a/src/easydiffraction/display/base.py b/src/easydiffraction/display/base.py new file mode 100644 index 00000000..603e6b57 --- /dev/null +++ b/src/easydiffraction/display/base.py @@ -0,0 +1,135 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Common base classes for display components and their factories.""" + +from __future__ import annotations + +from abc import ABC +from abc import abstractmethod +from typing import Any +from typing import List +from typing import Tuple + +import pandas as pd + +from easydiffraction.core.singletons import SingletonBase +from easydiffraction.utils.logging import console +from easydiffraction.utils.logging import log + + +class RendererBase(SingletonBase, ABC): + """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): + self._engine = self._default_engine() + self._backend = self._factory().create(self._engine) + + @classmethod + @abstractmethod + def _factory(cls) -> type[RendererFactoryBase]: + """Return the factory class for this renderer type.""" + raise NotImplementedError + + @classmethod + @abstractmethod + def _default_engine(cls) -> str: + """Return the default engine name for this renderer.""" + raise NotImplementedError + + @property + def engine(self) -> str: + return self._engine + + @engine.setter + def engine(self, new_engine: str) -> None: + if new_engine == self._engine: + log.info(f"Engine is already set to '{new_engine}'. No change made.") + return + try: + self._backend = self._factory().create(new_engine) + except ValueError as exc: + # Log a friendly message and leave engine unchanged + log.warning(str(exc)) + return + else: + self._engine = new_engine + console.paragraph('Current engine changed to') + console.print(f"'{self._engine}'") + + @abstractmethod + def show_config(self) -> None: + """Display the current renderer configuration.""" + raise NotImplementedError + + def show_supported_engines(self) -> None: + """List supported engines with descriptions in a table.""" + headers = [ + ('Engine', 'left'), + ('Description', 'left'), + ] + rows = self._factory().descriptions() + df = pd.DataFrame(rows, columns=pd.MultiIndex.from_tuples(headers)) + console.paragraph('Supported engines') + # Delegate table rendering to the TableRenderer singleton + from easydiffraction.display.tables import TableRenderer # local import to avoid cycles + + TableRenderer.get().render(df) + + def show_current_engine(self) -> None: + """Display the currently selected engine.""" + console.paragraph('Current engine') + console.print(f"'{self._engine}'") + + +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. + + Args: + engine_name: Identifier of the engine to instantiate as + listed in ``_registry()``. + + Returns: + A new backend instance corresponding to ``engine_name``. + + Raises: + ValueError: If the engine name is not supported. + """ + registry = cls._registry() + if engine_name not in registry: + supported = list(registry.keys()) + raise ValueError(f"Unsupported engine '{engine_name}'. Supported engines: {supported}") + engine_class = registry[engine_name]['class'] + return engine_class() + + @classmethod + def supported_engines(cls) -> List[str]: + """Return a list of supported engine identifiers.""" + return list(cls._registry().keys()) + + @classmethod + def descriptions(cls) -> List[Tuple[str, str]]: + """Return pairs of engine name and human-friendly + description. + """ + 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. + + The returned mapping should have keys as engine names and values + as a config dict with 'description' and 'class'. Lazy imports + are allowed to avoid circular dependencies. + """ + raise NotImplementedError diff --git a/src/easydiffraction/display/plotters/__init__.py b/src/easydiffraction/display/plotters/__init__.py new file mode 100644 index 00000000..0801e6c8 --- /dev/null +++ b/src/easydiffraction/display/plotters/__init__.py @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""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. +""" diff --git a/src/easydiffraction/plotting/plotters/plotter_ascii.py b/src/easydiffraction/display/plotters/ascii.py similarity index 57% rename from src/easydiffraction/plotting/plotters/plotter_ascii.py rename to src/easydiffraction/display/plotters/ascii.py index 2694eace..f7772cd2 100644 --- a/src/easydiffraction/plotting/plotters/plotter_ascii.py +++ b/src/easydiffraction/display/plotters/ascii.py @@ -1,12 +1,18 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""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. +""" import asciichartpy -from easydiffraction.plotting.plotters.plotter_base import DEFAULT_HEIGHT -from easydiffraction.plotting.plotters.plotter_base import SERIES_CONFIG -from easydiffraction.plotting.plotters.plotter_base import PlotterBase -from easydiffraction.utils.formatting import paragraph +from easydiffraction.display.plotters.base import DEFAULT_HEIGHT +from easydiffraction.display.plotters.base import SERIES_CONFIG +from easydiffraction.display.plotters.base import PlotterBase +from easydiffraction.utils.logging import console DEFAULT_COLORS = { 'meas': asciichartpy.blue, @@ -19,6 +25,17 @@ 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. + + 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'``). + + Returns: + A formatted legend string with color escapes. + """ color_start = DEFAULT_COLORS[label] color_end = asciichartpy.reset line = '────' @@ -46,9 +63,8 @@ def plot( title: Figure title printed above the chart. height: Number of text rows to allocate for the chart. """ - # Intentionally unused; kept for a consistent plotting API + # Intentionally unused; kept for a consistent display API del axes_labels - title = paragraph(title) legend = '\n'.join([self._get_legend_item(label) for label in labels]) if height is None: @@ -59,7 +75,12 @@ def plot( chart = asciichartpy.plot(y_series, config) - print(f'{title}') - print(f'Displaying data for selected x-range from {x[0]} to {x[-1]} ({len(x)} points)') - print(f'Legend:\n{legend}') - print(chart) + console.paragraph(f'{title}') # TODO: f''? + console.print( + f'Displaying data for selected x-range from {x[0]} to {x[-1]} ({len(x)} points)' + ) + console.print(f'Legend:\n{legend}') + + padded = '\n'.join(' ' + line for line in chart.splitlines()) + + print(padded) diff --git a/src/easydiffraction/plotting/plotters/plotter_base.py b/src/easydiffraction/display/plotters/base.py similarity index 76% rename from src/easydiffraction/plotting/plotters/plotter_base.py rename to src/easydiffraction/display/plotters/base.py index 45bf1068..04ee689d 100644 --- a/src/easydiffraction/plotting/plotters/plotter_base.py +++ b/src/easydiffraction/display/plotters/base.py @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""Abstract base and shared constants for plotting backends.""" from abc import ABC from abc import abstractmethod @@ -8,9 +9,7 @@ from easydiffraction.experiments.experiment.enums import BeamModeEnum from easydiffraction.experiments.experiment.enums import ScatteringTypeEnum -from easydiffraction.utils.utils import is_notebook -DEFAULT_ENGINE = 'plotly' if is_notebook() else 'asciichartpy' DEFAULT_HEIGHT = 9 DEFAULT_MIN = -np.inf DEFAULT_MAX = np.inf @@ -57,9 +56,8 @@ class PlotterBase(ABC): """Abstract base for plotting backends. - Concrete implementations should accept x values, multiple y-series - and a small set of labeling arguments and render a plot to the - chosen medium. + Implementations accept x values, multiple y-series, optional labels + and render a plot to the chosen medium. """ @abstractmethod @@ -75,11 +73,11 @@ def plot( """Render a plot. Args: - x: 1D array-like of x-axis values. + x: 1D array of x-axis values. y_series: Sequence of y arrays to plot. - labels: Series identifiers corresponding to y_series. - axes_labels: Pair of strings for the x and y axis titles. + labels: Identifiers corresponding to y_series. + axes_labels: Pair of strings for the x and y titles. title: Figure title. - height: Backend-specific height in text or pixels. + height: Backend-specific height (text rows or pixels). """ pass diff --git a/src/easydiffraction/plotting/plotters/plotter_plotly.py b/src/easydiffraction/display/plotters/plotly.py similarity index 80% rename from src/easydiffraction/plotting/plotters/plotter_plotly.py rename to src/easydiffraction/display/plotters/plotly.py index 544b991d..c62221ea 100644 --- a/src/easydiffraction/plotting/plotters/plotter_plotly.py +++ b/src/easydiffraction/display/plotters/plotly.py @@ -1,5 +1,11 @@ # SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors # SPDX-License-Identifier: BSD-3-Clause +"""Plotly plotting backend. + +Provides an interactive plotting implementation using Plotly. In +notebooks, figures are displayed inline; in other environments a browser +renderer may be used depending on configuration. +""" import darkdetect import plotly.graph_objects as go @@ -12,9 +18,9 @@ display = None HTML = None -from easydiffraction.plotting.plotters.plotter_base import SERIES_CONFIG -from easydiffraction.plotting.plotters.plotter_base import PlotterBase -from easydiffraction.utils.utils import is_pycharm +from easydiffraction.display.plotters.base import SERIES_CONFIG +from easydiffraction.display.plotters.base import PlotterBase +from easydiffraction.utils.environment import in_pycharm DEFAULT_COLORS = { 'meas': 'rgb(31, 119, 180)', @@ -27,10 +33,21 @@ class PlotlyPlotter(PlotterBase): """Interactive plotter using Plotly for notebooks and browsers.""" pio.templates.default = 'plotly_dark' if darkdetect.isDark() else 'plotly_white' - if is_pycharm(): + if in_pycharm(): pio.renderers.default = 'browser' def _get_trace(self, x, y, label): + """Create a Plotly trace for a single data series. + + 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: + A configured :class:`plotly.graph_objects.Scatter` trace. + """ mode = SERIES_CONFIG[label]['mode'] name = SERIES_CONFIG[label]['name'] color = DEFAULT_COLORS[label] @@ -126,7 +143,7 @@ def plot( fig.update_yaxes(tickformat=',.6~g', separatethousands=True) # Show the figure - if is_pycharm() or display is None or HTML is None: + if in_pycharm() or display is None or HTML is None: fig.show(config=config) else: html_fig = pio.to_html( diff --git a/src/easydiffraction/display/plotting.py b/src/easydiffraction/display/plotting.py new file mode 100644 index 00000000..63695169 --- /dev/null +++ b/src/easydiffraction/display/plotting.py @@ -0,0 +1,440 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Plotting facade for measured and calculated patterns. + +Uses the common :class:`RendererBase` so plotters and tablers share a +consistent configuration surface and engine handling. +""" + +from enum import Enum + +import numpy as np +import pandas as pd + +from easydiffraction.display.base import RendererBase +from easydiffraction.display.base import RendererFactoryBase +from easydiffraction.display.plotters.ascii import AsciiPlotter +from easydiffraction.display.plotters.base import DEFAULT_AXES_LABELS +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.plotly import PlotlyPlotter +from easydiffraction.display.tables import TableRenderer +from easydiffraction.utils.environment import in_jupyter +from easydiffraction.utils.logging import console +from easydiffraction.utils.logging import log + + +class PlotterEngineEnum(str, Enum): + ASCII = 'asciichartpy' + PLOTLY = 'plotly' + + @classmethod + def default(cls) -> 'PlotterEngineEnum': + """Select default engine based on environment.""" + if in_jupyter(): + log.debug('Setting default plotting engine to Plotly for Jupyter') + return cls.PLOTLY + log.debug('Setting default plotting engine to Asciichartpy for console') + return cls.ASCII + + def description(self) -> str: + """Human-readable description for UI listings.""" + if self is PlotterEngineEnum.ASCII: + return 'Console ASCII line charts' + elif self is PlotterEngineEnum.PLOTLY: + return 'Interactive browser-based graphing library' + return '' + + +class Plotter(RendererBase): + """User-facing plotting facade backed by concrete plotters.""" + + def __init__(self): + super().__init__() + # X-axis limits + self._x_min = DEFAULT_MIN + self._x_max = DEFAULT_MAX + # Chart height + self.height = DEFAULT_HEIGHT + + @classmethod + def _factory(cls) -> type[RendererFactoryBase]: # type: ignore[override] + return PlotterFactory + + @classmethod + 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) + + @property + def x_min(self): + """Minimum x-axis limit.""" + return self._x_min + + @x_min.setter + def x_min(self, value): + """Set the minimum x-axis limit. + + Args: + value: Minimum limit or ``None`` to reset to default. + """ + if value is not None: + self._x_min = value + else: + self._x_min = DEFAULT_MIN + + @property + def x_max(self): + """Maximum x-axis limit.""" + return self._x_max + + @x_max.setter + def x_max(self, value): + """Set the maximum x-axis limit. + + Args: + value: Maximum limit or ``None`` to reset to default. + """ + if value is not None: + self._x_max = value + else: + self._x_max = DEFAULT_MAX + + @property + def height(self): + """Plot height (rows for ASCII, pixels for Plotly).""" + return self._height + + @height.setter + def height(self, value): + """Set plot height. + + Args: + value: Height value or ``None`` to reset to default. + """ + if value is not None: + self._height = value + else: + self._height = DEFAULT_HEIGHT + + def plot_meas( + self, + pattern, + expt_name, + expt_type, + x_min=None, + x_max=None, + d_spacing=False, + ): + """Plot measured pattern using the current engine. + + Args: + pattern: Object with ``x`` and ``meas`` arrays (and + ``d`` when ``d_spacing`` is true). + 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. + """ + 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, + ) + 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 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, + title=f"Measured data for experiment 🔬 '{expt_name}'", + height=self.height, + ) + + def plot_calc( + self, + pattern, + expt_name, + expt_type, + x_min=None, + x_max=None, + d_spacing=False, + ): + """Plot calculated pattern using the current engine. + + Args: + pattern: Object with ``x`` and ``calc`` arrays (and + ``d`` when ``d_spacing`` is true). + 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. + """ + 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, + ) + + 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, + title=f"Calculated data for experiment 🔬 '{expt_name}'", + height=self.height, + ) + + def plot_meas_vs_calc( + self, + pattern, + expt_name, + expt_type, + x_min=None, + x_max=None, + show_residual=False, + d_spacing=False, + ): + """Plot measured and calculated series and optional residual. + + Args: + pattern: Object with ``x``, ``meas`` and ``calc`` arrays + (and ``d`` when ``d_spacing`` is true). + 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. + show_residual: If ``True``, add residual series. + d_spacing: If ``True``, plot against d-spacing values. + """ + 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 + 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_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, + ) + + 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}'", + 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.""" + + @classmethod + def _registry(cls) -> dict: + return { + PlotterEngineEnum.ASCII.value: { + 'description': PlotterEngineEnum.ASCII.description(), + 'class': AsciiPlotter, + }, + PlotterEngineEnum.PLOTLY.value: { + 'description': PlotterEngineEnum.PLOTLY.description(), + 'class': PlotlyPlotter, + }, + } diff --git a/src/easydiffraction/display/tablers/__init__.py b/src/easydiffraction/display/tablers/__init__.py new file mode 100644 index 00000000..75e19872 --- /dev/null +++ b/src/easydiffraction/display/tablers/__init__.py @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Tabular rendering backends. + +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. +""" diff --git a/src/easydiffraction/display/tablers/base.py b/src/easydiffraction/display/tablers/base.py new file mode 100644 index 00000000..55233309 --- /dev/null +++ b/src/easydiffraction/display/tablers/base.py @@ -0,0 +1,109 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors +# SPDX-License-Identifier: BSD-3-Clause +"""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. +""" + +from __future__ import annotations + +from abc import ABC +from abc import abstractmethod +from typing import Any + +from IPython import get_ipython +from jupyter_dark_detect import is_dark +from rich.color import Color + + +class TableBackendBase(ABC): + """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. + """ + + FLOAT_PRECISION = 5 + RICH_BORDER_DARK_THEME = 'grey35' + RICH_BORDER_LIGHT_THEME = 'grey85' + + 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. + + Args: + value: Cell value to format. + + Returns: + 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. + + If not running inside Jupyter, return a sane default (True). + """ + default = True + + in_jupyter = ( + get_ipython() is not None and get_ipython().__class__.__name__ == 'ZMQInteractiveShell' + ) + + if not in_jupyter: + return default + + return is_dark() + + def _rich_to_hex(self, color): + """Convert a Rich color name to a CSS-style hex string. + + Args: + color: Rich color name or specification parsable by + :mod:`rich`. + + Returns: + Hex color string in the form ``#RRGGBB``. + """ + c = Color.parse(color) + rgb = c.get_truecolor() + hex_value = '#{:02x}{:02x}{:02x}'.format(*rgb) + return hex_value + + @property + def _rich_border_color(self) -> str: + return ( + self.RICH_BORDER_DARK_THEME if self._is_dark_theme() else self.RICH_BORDER_LIGHT_THEME + ) + + @property + def _pandas_border_color(self) -> str: + return self._rich_to_hex(self._rich_border_color) + + @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: + Backend-defined return value (commonly ``None``). + """ + pass diff --git a/src/easydiffraction/display/tablers/pandas.py b/src/easydiffraction/display/tablers/pandas.py new file mode 100644 index 00000000..a1883355 --- /dev/null +++ b/src/easydiffraction/display/tablers/pandas.py @@ -0,0 +1,169 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction 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 +except Exception: + HTML = None + display = None + +from easydiffraction.display.tablers.base import TableBackendBase +from easydiffraction.utils.environment import can_use_ipython_display +from easydiffraction.utils.logging import log + + +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. + + Args: + color: CSS color value (e.g., ``#RRGGBB``) to use for + borders and header accents. + + Returns: + A list of ``Styler.set_table_styles`` dictionaries. + """ + return [ + # Margins and outer border on the entire table + { + 'selector': ' ', + 'props': [ + ('border', f'1px solid {color}'), + ('border-collapse', 'collapse'), + ('margin-top', '0.5em'), + ('margin-left', '0.5em'), + ], + }, + # Horizontal border under header row + { + 'selector': 'thead', + 'props': [ + ('border-bottom', f'1px solid {color}'), + ], + }, + # Cell border, padding and line height + { + 'selector': 'th, td', + 'props': [ + ('border', 'none'), + ('padding-top', '0.25em'), + ('padding-bottom', '0.25em'), + ('line-height', '1.15em'), + ], + }, + # Style for index column + { + 'selector': 'th.row_heading', + 'props': [ + ('color', color), + ('font-weight', 'normal'), + ], + }, + # Remove zebra-row background + { + 'selector': 'tbody tr:nth-child(odd), tbody tr:nth-child(even)', + 'props': [ + ('background-color', 'transparent'), + ], + }, + ] + + 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: + A list of CSS rules for header cell alignment. + """ + return [ + { + 'selector': f'th.col{df.columns.get_loc(column)}', + 'props': [('text-align', align)], + } + 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: + A configured pandas Styler ready for display. + """ + table_styles = self._build_base_styles(color) + header_alignment_styles = self._build_header_alignment_styles(df, alignments) + + styler = df.style.format(precision=self.FLOAT_PRECISION) + styler = styler.set_table_attributes('class="dataframe"') # For mkdocs-jupyter + styler = styler.set_table_styles(table_styles + header_alignment_styles) + + for column, align in zip(df.columns, alignments, strict=False): + styler = styler.set_properties( + subset=[column], + **{'text-align': align}, + ) + return styler + + def _update_display(self, styler, display_handle) -> 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. + """ + # Handle with update() method + if display_handle is not None and hasattr(display_handle, 'update'): + # IPython DisplayHandle path + if can_use_ipython_display(display_handle) and HTML is not None: + try: + html = styler.to_html() + display_handle.update(HTML(html)) + return + except Exception as err: + log.debug(f'Pandas DisplayHandle update failed: {err!r}') + + # This should not happen in Pandas backend + else: + pass + + # Normal display + display(styler) + + 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. + """ + color = self._pandas_border_color + styler = self._apply_styling(df, alignments, color) + self._update_display(styler, display_handle) diff --git a/src/easydiffraction/display/tablers/rich.py b/src/easydiffraction/display/tablers/rich.py new file mode 100644 index 00000000..e5f18776 --- /dev/null +++ b/src/easydiffraction/display/tablers/rich.py @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction 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 +from rich.table import Table + +try: + from IPython.display import HTML + from IPython.display import display +except Exception: + HTML = None + display = None + +from easydiffraction.display.tablers.base import TableBackendBase +from easydiffraction.utils.environment import can_use_ipython_display +from easydiffraction.utils.logging import ConsoleManager +from easydiffraction.utils.logging import log + +"""Custom compact box style used for consistent borders.""" +CUSTOM_BOX = """\ +┌──┐ +│ │ +├──┤ +│ │ +├──┤ +├──┤ +│ │ +└──┘ +""" +RICH_TABLE_BOX: Box = Box(CUSTOM_BOX, ascii=False) + + +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. + + 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. + + Returns: + HTML string with inline styles for notebook display. + """ + tmp = Console(force_jupyter=False, record=True, file=io.StringIO()) + tmp.print(table) + html = tmp.export_html(inline_styles=True) + # Remove margins inside pre blocks and adjust font size + html = html.replace( + '
 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:
+            A :class:`~rich.table.Table` configured for display.
+        """
+        table = Table(
+            title=None,
+            box=RICH_TABLE_BOX,
+            show_header=True,
+            header_style='bold',
+            border_style=color,
+        )
+
+        # Index column
+        table.add_column(justify='right', style=color)
+
+        # Data columns
+        for col, align in zip(df, alignments, strict=False):
+            table.add_column(str(col), justify=align, no_wrap=False)
+
+        # Rows
+        for idx, row_values in df.iterrows():
+            formatted_row = [self._format_value(v) for v in row_values]
+            table.add_row(str(idx), *formatted_row)
+
+        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).
+        """
+        # Handle with update() method
+        if display_handle is not None and hasattr(display_handle, 'update'):
+            # IPython DisplayHandle path
+            if can_use_ipython_display(display_handle) and HTML is not None:
+                try:
+                    html = self._to_html(table)
+                    display_handle.update(HTML(html))
+                    return
+                except Exception as err:
+                    log.debug(f'Rich to HTML DisplayHandle update failed: {err!r}')
+
+            # Assume terminal/live-like handle
+            else:
+                try:
+                    display_handle.update(table)
+                    return
+                except Exception as err:
+                    log.debug(f'Rich live handle update failed: {err!r}')
+
+        # Normal print to console
+        console = ConsoleManager.get()
+        console.print(table)
+
+    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.
+        """
+        color = self._rich_border_color
+        table = self._build_table(df, alignments, color)
+        self._update_display(table, display_handle)
diff --git a/src/easydiffraction/display/tables.py b/src/easydiffraction/display/tables.py
new file mode 100644
index 00000000..a079e30f
--- /dev/null
+++ b/src/easydiffraction/display/tables.py
@@ -0,0 +1,118 @@
+# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction 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
+
+from easydiffraction.display.base import RendererBase
+from easydiffraction.display.base import RendererFactoryBase
+from easydiffraction.display.tablers.pandas import PandasTableBackend
+from easydiffraction.display.tablers.rich import RichTableBackend
+from easydiffraction.utils.environment import in_jupyter
+from easydiffraction.utils.logging import console
+from easydiffraction.utils.logging import log
+
+
+class TableEngineEnum(str, Enum):
+    RICH = 'rich'
+    PANDAS = 'pandas'
+
+    @classmethod
+    def default(cls) -> 'TableEngineEnum':
+        """Select default engine based on environment.
+
+        Returns Pandas when running in Jupyter, otherwise Rich.
+        """
+        if in_jupyter():
+            log.debug('Setting default table engine to Pandas for Jupyter')
+            return cls.PANDAS
+        log.debug('Setting default table engine to Rich for console')
+        return cls.RICH
+
+    def description(self) -> str:
+        if self is TableEngineEnum.RICH:
+            return 'Console rendering with Rich'
+        elif self is TableEngineEnum.PANDAS:
+            return 'Jupyter DataFrame rendering with Pandas'
+        return ''
+
+
+class TableRenderer(RendererBase):
+    """Renderer for tabular data with selectable engines (singleton)."""
+
+    @classmethod
+    def _factory(cls) -> RendererFactoryBase:
+        return TableRendererFactory
+
+    @classmethod
+    def _default_engine(cls) -> str:
+        """Default engine derived from TableEngineEnum."""
+        return TableEngineEnum.default().value
+
+    def show_config(self) -> None:
+        """Display minimal configuration for this renderer."""
+        headers = [
+            ('Parameter', 'left'),
+            ('Value', 'left'),
+        ]
+        rows = [['engine', self._engine]]
+        df = pd.DataFrame(rows, columns=pd.MultiIndex.from_tuples(headers))
+        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:
+            Backend-specific return value (usually ``None``).
+        """
+        # Work on a copy to avoid mutating the original DataFrame
+        df = df.copy()
+
+        # Force starting index from 1
+        df.index += 1
+
+        # Extract column alignments
+        alignments = df.columns.get_level_values(1)
+
+        # Remove alignments from df (Keep only the first index level)
+        df.columns = df.columns.get_level_values(0)
+
+        return self._backend.render(alignments, df, display_handle)
+
+
+class TableRendererFactory(RendererFactoryBase):
+    """Factory for creating tabler instances."""
+
+    @classmethod
+    def _registry(cls) -> dict:
+        """Build registry, adapting available engines to the
+        environment.
+
+        - In Jupyter: expose both 'rich' and 'pandas'.
+        - In terminal: expose only 'rich' (pandas is notebook-only).
+        """
+        base = {
+            TableEngineEnum.RICH.value: {
+                'description': TableEngineEnum.RICH.description(),
+                'class': RichTableBackend,
+            }
+        }
+        if in_jupyter():
+            base[TableEngineEnum.PANDAS.value] = {
+                'description': TableEngineEnum.PANDAS.description(),
+                'class': PandasTableBackend,
+            }
+        return base
diff --git a/src/easydiffraction/display/utils.py b/src/easydiffraction/display/utils.py
new file mode 100644
index 00000000..548fb68f
--- /dev/null
+++ b/src/easydiffraction/display/utils.py
@@ -0,0 +1,48 @@
+# SPDX-FileCopyrightText: 2021-2025 EasyDiffraction contributors 
+# SPDX-License-Identifier: BSD-3-Clause
+
+from __future__ import annotations
+
+from typing import ClassVar
+
+from easydiffraction.utils.environment import in_jupyter
+from easydiffraction.utils.logging import log
+
+# Optional import – safe even if IPython is not installed
+try:
+    from IPython.display import HTML
+    from IPython.display import display
+except Exception:
+    display = None
+    HTML = None
+
+
+class JupyterScrollManager:
+    """Ensures that Jupyter output cells are not scrollable (applied
+    once).
+    """
+
+    _applied: ClassVar[bool] = False
+
+    @classmethod
+    def disable_jupyter_scroll(cls) -> None:
+        """Inject CSS to prevent output cells from being scrollable."""
+        if cls._applied or not in_jupyter() or display is None or HTML is None:
+            return
+
+        css = """
+        \n",
+       "  \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "  \n",
+       "  \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "    \n",
+       "      \n",
+       "      \n",
+       "      \n",
+       "    \n",
+       "  \n",
+       "
EngineDescription
1
asciichartpy
Console ASCII line charts
2
plotly
Interactive browser-based graphing library
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ - "c.length_b.free = 'qwe'" + "project.plotter.show_supported_engines()\n", + "#project.plotter.show_config()" ] }, { "cell_type": "code", - "execution_count": 31, - "id": "4475ed9b-74d1-4080-8e57-0099b35e94f5", + "execution_count": 5, + "id": "cf5ca4a5-3dfe-4b47-a722-35825a2a9e68", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'fre'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Did you mean \u001b[32m'free'\u001b[0m? \n" + " \u001b[1;34mSupported engines\u001b[0m \n" ] - } - ], - "source": [ - "c.length_b.fre = 'fre'" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "e3d37a6d-9ec8-4d96-8f95-ce7b67e65f36", - "metadata": {}, - "outputs": [ + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'qwe'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Allowed writable: \u001b[32m'fit_max'\u001b[0m, \u001b[32m'fit_min'\u001b[0m, \u001b[32m'free'\u001b[0m, \u001b[32m'uncertainty'\u001b[0m, \n", - " \u001b[32m'value'\u001b[0m. \n" - ] + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
EngineDescription
0richConsole rendering with Rich
1pandasJupyter DataFrame rendering with Pandas
\n", + "
" + ], + "text/plain": [ + " Engine Description\n", + "0 rich Console rendering with Rich\n", + "1 pandas Jupyter DataFrame rendering with Pandas" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ - "c.length_b.qwe = 'qwe'" + "project.tabler.show_supported_engines()\n", + "#project.tabler.show_config()" ] }, { "cell_type": "code", - "execution_count": 33, - "id": "f108a56d-775c-4d4e-aedf-ecc4ead28178", + "execution_count": 6, + "id": "2c781d36-4881-4c1e-a193-94b0fb6b7d6f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mWARNING \u001b[0m Cannot modify read-only attribute \u001b[32m'description'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. \n" + "\u001b[33mWARNING \u001b[0m Engine is already set to \u001b[32m'pandas'\u001b[0m. No change made. \n" ] } ], "source": [ - "c.length_b.description = 'desc'" + "project.tabler.engine = 'pandas'" ] }, { "cell_type": "code", - "execution_count": 34, - "id": "382d6221-0074-4ec8-84a8-ec51e117420a", + "execution_count": 10, + "id": "4d08c275-2da6-4ad3-b0d1-cc0c8f668b8d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'qwe'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell\u001b[0m\u001b[1m>\u001b[0m. Allowed writable: \u001b[32m'angle_alpha'\u001b[0m, \u001b[32m'angle_beta'\u001b[0m, \u001b[32m'angle_gamma'\u001b[0m, \u001b[32m'length_a'\u001b[0m, \n", - " \u001b[32m'length_b'\u001b[0m, \u001b[32m'length_c'\u001b[0m. \n" + "\u001b[33mWARNING \u001b[0m Engine is already set to \u001b[32m'rich'\u001b[0m. No change made. \n" ] } ], "source": [ - "c.qwe = 'qwe'" + "project.tabler.engine = 'rich'" ] }, { "cell_type": "code", - "execution_count": 35, - "id": "7bf7bac1-79ce-45df-a2a8-c4b090359292", - "metadata": {}, - "outputs": [], - "source": [ - "sg = SpaceGroup()" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "5c30d269-5167-4598-ac57-66715673d9b0", + "execution_count": 11, + "id": "58f9bd70-2384-44a5-b3cb-10becdae52c7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mname_h_m\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'qwe'\u001b[0m is unknown. \u001b[1m(\u001b[0m\u001b[1;36m230\u001b[0m allowed values not listed here\u001b[1m)\u001b[0m. Using default \n", - " \u001b[32m'P 1'\u001b[0m. \n" + " \u001b[1;34mSupported plotter engines\u001b[0m \n" ] - } - ], - "source": [ - "sg = SpaceGroup(name_h_m='qwe')" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "c99fcf63-9536-4ea6-a30a-96367bb4aacb", - "metadata": {}, - "outputs": [ + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mname_h_m\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'P n m'\u001b[0m is unknown. \u001b[1m(\u001b[0m\u001b[1;36m230\u001b[0m allowed values not listed here\u001b[1m)\u001b[0m. Using default\n", - " \u001b[32m'P 1'\u001b[0m. \n", - "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mit_coordinate_system_code\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'cab'\u001b[0m is unknown. Allowed values: \u001b[32m''\u001b[0m. Using default \n", - " \u001b[32m''\u001b[0m. \n" - ] + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
EngineDescription
1
asciichartpy
Console ASCII line charts
2
plotly
Interactive browser-based graphing library
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ - "sg = SpaceGroup(name_h_m='P n m', it_coordinate_system_code='cab')" + "project.plotter.show_supported_engines()" ] }, { "cell_type": "code", - "execution_count": 38, - "id": "c2d7509e-a49b-4c2b-aa45-76a97de0761e", + "execution_count": 8, + "id": "f0374d85-e74a-4af7-bb90-1e58b4d68732", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mit_coordinate_system_code\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'cabd'\u001b[0m is unknown. Allowed values: \u001b[32m'-cba'\u001b[0m, \u001b[32m'a-cb'\u001b[0m, \n", - " \u001b[32m'abc'\u001b[0m, \u001b[32m'ba-c'\u001b[0m, \u001b[32m'bca'\u001b[0m, \u001b[32m'cab'\u001b[0m. Using default \u001b[32m'abc'\u001b[0m. \n" + "\u001b[33mWARNING \u001b[0m Engine \u001b[32m'rich2'\u001b[0m is not supported. Available engines: \u001b[32m'rich'\u001b[0m, \u001b[32m'pandas'\u001b[0m \n" ] } ], "source": [ - "sg = SpaceGroup(name_h_m='P n m a', it_coordinate_system_code='cabd')" + "project.tabler.engine = 'rich2'" ] }, { "cell_type": "code", - "execution_count": 39, - "id": "07db2ef2-58f3-4b46-8223-a2067254db51", + "execution_count": 9, + "id": "a7a805fe-735b-4df6-a1f7-9180e4aa845d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mspace_group.name_h_m\u001b[0m\u001b[1m>\u001b[0m. Expected `string`, got `float` \u001b[1m(\u001b[0m\u001b[1;36m34.9\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[32m'P n m a'\u001b[0m. \n" + " \u001b[1;34mSupported engines\u001b[0m \n" ] + }, + { + "data": { + "text/html": [ + "
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n",
+       " in <module>:1                                                                                    \n",
+       "                                                                                                  \n",
+       " 1 project.tabler.show_supported_engines()                                                      \n",
+       "   2                                                                                              \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/disp \n",
+       " lay/base.py:71 in show_supported_engines                                                         \n",
+       "                                                                                                  \n",
+       "   68 │   │   log.paragraph('Supported engines')                                                  \n",
+       "   69 │   │   # from easydiffraction.utils.utils import render_table                              \n",
+       "   70 │   │   # render_table(headers, rows)                                                       \n",
+       " 71 │   │   self.render(headers, rows)                                                          \n",
+       "   72                                                                                             \n",
+       "   73                                                                                             \n",
+       "   74 class RendererFactoryBase(ABC):                                                             \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/disp \n",
+       " lay/tables.py:45 in render                                                                       \n",
+       "                                                                                                  \n",
+       "   42 │   │   *,                                                                                  \n",
+       "   43 │   │   align: Sequence[str] | None = None,                                                 \n",
+       "   44 ) -> Any:                                                                               \n",
+       " 45 │   │   return self._backend.render(                                                        \n",
+       "   46 │   │   │   headers=headers,                                                                \n",
+       "   47 │   │   │   rows=rows,                                                                      \n",
+       "   48 │   │   │   align=align,                                                                    \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/disp \n",
+       " lay/tablers/rich.py:53 in render                                                                 \n",
+       "                                                                                                  \n",
+       "   50 │   │   align: Sequence[str] | None = None,                                                 \n",
+       "   51 ) -> Any:                                                                               \n",
+       "   52 │   │   table = Table(                                                                      \n",
+       " 53 │   │   │   title=None, box=self._box, show_header=True, header_style='bold', border_sty    \n",
+       "   54 │   │   )                                                                                   \n",
+       "   55 │   │   for i, header in enumerate(headers):                                                \n",
+       "   56 │   │   │   if align is not None and i < len(align):                                        \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/disp \n",
+       " lay/tablers/rich.py:31 in _box                                                                   \n",
+       "                                                                                                  \n",
+       "   28 │   │   │ ││ foot                                                                           \n",
+       "   29 │   │   └─┴┘ bottom                                                                         \n",
+       "   30 │   │   \"\"\"                                                                                 \n",
+       " 31 │   │   return Box(                                                                         \n",
+       "   32 │   │   │   \"\"\"\\                                                                            \n",
+       "   33 │   │   │   │   ┌──┐                                                                        \n",
+       "   34 │   │   │   │   │  │                                                                        \n",
+       "                                                                                                  \n",
+       " /Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/.pixi/envs/default/lib/p \n",
+       " ython3.13/site-packages/rich/box.py:30 in __init__                                               \n",
+       "                                                                                                  \n",
+       "    27 def __init__(self, box: str, *, ascii: bool = False) -> None:                          \n",
+       "    28 │   │   self._box = box                                                                    \n",
+       "    29 │   │   self.ascii = ascii                                                                 \n",
+       "  30 │   │   line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()          \n",
+       "    31 │   │   # top                                                                              \n",
+       "    32 │   │   self.top_left, self.top, self.top_divider, self.top_right = iter(line1)            \n",
+       "    33 │   │   # head                                                                             \n",
+       "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
+       "ValueError: too many values to unpack (expected 8)\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[31m╭─\u001b[0m\u001b[31m──────────────────────────────\u001b[0m\u001b[31m \u001b[0m\u001b[1;31mTraceback \u001b[0m\u001b[1;2;31m(most recent call last)\u001b[0m\u001b[31m \u001b[0m\u001b[31m───────────────────────────────\u001b[0m\u001b[31m─╮\u001b[0m\n", + "\u001b[31m│\u001b[0m in :1 \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m1 \u001b[1;4mproject.tabler.show_supported_engines()\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m2 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/disp\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2mlay/\u001b[0m\u001b[1mbase.py\u001b[0m:71 in show_supported_engines \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m68 \u001b[0m\u001b[2m│ │ \u001b[0mlog.paragraph(\u001b[33m'\u001b[0m\u001b[33mSupported engines\u001b[0m\u001b[33m'\u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m69 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# from easydiffraction.utils.utils import render_table\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m70 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# render_table(headers, rows)\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m71 \u001b[2m│ │ \u001b[0m\u001b[1;4;96mself\u001b[0m\u001b[1;4m.render(headers, rows)\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m72 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m73 \u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m74 \u001b[0m\u001b[94mclass\u001b[0m\u001b[90m \u001b[0m\u001b[4;92mRendererFactoryBase\u001b[0m(ABC): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/disp\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2mlay/\u001b[0m\u001b[1mtables.py\u001b[0m:45 in render \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m42 \u001b[0m\u001b[2m│ │ \u001b[0m*, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m43 \u001b[0m\u001b[2m│ │ \u001b[0malign: Sequence[\u001b[96mstr\u001b[0m] | \u001b[94mNone\u001b[0m = \u001b[94mNone\u001b[0m, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m44 \u001b[0m\u001b[2m│ \u001b[0m) -> Any: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m45 \u001b[2m│ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[1;4;96mself\u001b[0m\u001b[1;4m._backend.render(\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m46 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4mheaders=headers,\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m47 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4mrows=rows,\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m48 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[1;4malign=align,\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/disp\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2mlay/tablers/\u001b[0m\u001b[1mrich.py\u001b[0m:53 in render \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m50 \u001b[0m\u001b[2m│ │ \u001b[0malign: Sequence[\u001b[96mstr\u001b[0m] | \u001b[94mNone\u001b[0m = \u001b[94mNone\u001b[0m, \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m51 \u001b[0m\u001b[2m│ \u001b[0m) -> Any: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m52 \u001b[0m\u001b[2m│ │ \u001b[0mtable = Table( \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m53 \u001b[2m│ │ │ \u001b[0mtitle=\u001b[94mNone\u001b[0m, box=\u001b[1;4;96mself\u001b[0m\u001b[1;4m._box\u001b[0m, show_header=\u001b[94mTrue\u001b[0m, header_style=\u001b[33m'\u001b[0m\u001b[33mbold\u001b[0m\u001b[33m'\u001b[0m, border_sty \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m54 \u001b[0m\u001b[2m│ │ \u001b[0m) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m55 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[94mfor\u001b[0m i, header \u001b[95min\u001b[0m \u001b[96menumerate\u001b[0m(headers): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m56 \u001b[0m\u001b[2m│ │ │ \u001b[0m\u001b[94mif\u001b[0m align \u001b[95mis\u001b[0m \u001b[95mnot\u001b[0m \u001b[94mNone\u001b[0m \u001b[95mand\u001b[0m i < \u001b[96mlen\u001b[0m(align): \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/src/easydiffraction/disp\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2mlay/tablers/\u001b[0m\u001b[1mrich.py\u001b[0m:31 in _box \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m28 \u001b[0m\u001b[2;33m│ │ \u001b[0m\u001b[33m│ ││ foot\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m29 \u001b[0m\u001b[2;33m│ │ \u001b[0m\u001b[33m└─┴┘ bottom\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m30 \u001b[0m\u001b[2;33m│ │ \u001b[0m\u001b[33m\"\"\"\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m31 \u001b[2m│ │ \u001b[0m\u001b[94mreturn\u001b[0m \u001b[1;4mBox(\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m32 \u001b[0m\u001b[2;90m│ │ │ \u001b[0m\u001b[1;4;33m\"\"\"\\\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m33 \u001b[0m\u001b[2;33m│ │ │ │ \u001b[0m\u001b[1;4;33m┌──┐\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m34 \u001b[0m\u001b[2;33m│ │ │ │ \u001b[0m\u001b[1;4;33m│ │\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m/Users/andrewsazonov/Development/github.com/easyscience/diffraction-lib/.pixi/envs/default/lib/p\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2mython3.13/site-packages/rich/\u001b[0m\u001b[1mbox.py\u001b[0m:30 in __init__ \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 27 \u001b[0m\u001b[2m│ \u001b[0m\u001b[94mdef\u001b[0m\u001b[90m \u001b[0m\u001b[92m__init__\u001b[0m(\u001b[96mself\u001b[0m, box: \u001b[96mstr\u001b[0m, *, ascii: \u001b[96mbool\u001b[0m = \u001b[94mFalse\u001b[0m) -> \u001b[94mNone\u001b[0m: \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 28 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[96mself\u001b[0m._box = box \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 29 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[96mself\u001b[0m.ascii = ascii \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[31m❱ \u001b[0m 30 \u001b[2m│ │ \u001b[0m\u001b[1;4mline1, line2, line3, line4, line5, line6, line7, line8\u001b[0m = box.splitlines() \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 31 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# top\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 32 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[96mself\u001b[0m.top_left, \u001b[96mself\u001b[0m.top, \u001b[96mself\u001b[0m.top_divider, \u001b[96mself\u001b[0m.top_right = \u001b[96miter\u001b[0m(line1) \u001b[31m│\u001b[0m\n", + "\u001b[31m│\u001b[0m \u001b[2m 33 \u001b[0m\u001b[2m│ │ \u001b[0m\u001b[2m# head\u001b[0m \u001b[31m│\u001b[0m\n", + "\u001b[31m╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n", + "\u001b[1;91mValueError: \u001b[0mtoo many values to unpack \u001b[1m(\u001b[0mexpected \u001b[1;36m8\u001b[0m\u001b[1m)\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ - "sg.name_h_m = 34.9" + "project.tabler.show_supported_engines()" ] }, { "cell_type": "code", "execution_count": null, - "id": "6934d72d-01a7-41e3-8684-5a1c6106e933", + "id": "d5fe7811-34e3-464e-aa97-6f5ca755a0e1", "metadata": {}, "outputs": [], - "source": [ - "sg.name_h_m = 'P 1'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a8a152ec-5a11-4c38-bca4-7a4230218f34", - "metadata": {}, - "outputs": [], - "source": [ - "sg.name_h_m = 'P n m a'" - ] + "source": [] }, { "cell_type": "code", "execution_count": null, - "id": "6626a960-6390-4e76-8f27-dc2cebbdd123", + "id": "3c587e0b-4d38-4cb6-aee9-083d038b9d9f", "metadata": {}, "outputs": [], "source": [] @@ -462,7 +537,7 @@ { "cell_type": "code", "execution_count": null, - "id": "cb299acb-0920-43f3-be25-c432544e1198", + "id": "b7838cbe-03d6-482b-98cb-7c216f9bb2f6", "metadata": {}, "outputs": [], "source": [] @@ -484,7 +559,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.7" + "version": "3.13.8" } }, "nbformat": 4, diff --git a/tmp/Untitled0.ipynb b/tmp/Untitled0.ipynb new file mode 100644 index 00000000..b8f6e58b --- /dev/null +++ b/tmp/Untitled0.ipynb @@ -0,0 +1,492 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "2b4ff90d-5a58-4202-ac2a-874168a2c6a2", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction.sample_models.categories.atom_sites import AtomSite\n", + "from easydiffraction.sample_models.categories.cell import Cell\n", + "from easydiffraction.sample_models.categories.space_group import SpaceGroup" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "1100c5b2-e00c-4513-bd2e-e30742d47e67", + "metadata": {}, + "outputs": [], + "source": [ + "from easydiffraction.utils.logging import Logger\n", + "\n", + "Logger.configure(\n", + " level=Logger.Level.WARNING,\n", + " mode=Logger.Mode.VERBOSE,\n", + " reaction=Logger.Reaction.WARN,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "a1a30af4-91c9-4015-9c96-a571fd1a711a", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "s1 = AtomSite(label='La', type_symbol='La')" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "5f8217f7-e8cf-4202-8369-ced7438657f2", + "metadata": {}, + "outputs": [], + "source": [ + "s1.fract_x.value = 1.234" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "4c9cf2fe-7f72-4b9d-a574-5eb8c40223c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.La.fract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'xyz'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[1;36m1.234\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x.value = 'xyz'" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "cba23ca5-a865-428b-b1c8-90e38787e593", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.La.fract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'qwe'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[1;36m1.234\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "3ba30971-177b-40e4-b477-e79a00341f87", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mfract_x\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'uuuu'\u001b[0m\u001b[1m)\u001b[0m. Using default \u001b[1;36m0.0\u001b[0m. \n" + ] + } + ], + "source": [ + "s1 = AtomSite(label='Si', type_symbol='Si', fract_x='uuuu')" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "992966e7-6bb7-4bc7-bbff-80acfea6fd2c", + "metadata": {}, + "outputs": [], + "source": [ + "s1.fract_x.free = True" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "50ef6ebd-097d-4df2-93dc-39243bdba6fd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95matom_site.Si.fract_x.free\u001b[0m\u001b[1m>\u001b[0m. Expected `bool`, got `str` \u001b[1m(\u001b[0m\u001b[32m'abc'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[3;92mTrue\u001b[0m. \n" + ] + } + ], + "source": [ + "s1.fract_x.free = 'abc'" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2c46e9ca-f68d-4b71-b783-6660f357322c", + "metadata": {}, + "outputs": [], + "source": [ + "c = Cell()" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "8e3fee6f-dc71-49a0-bf67-6f85f4ba83cd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mlength_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-8.8\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Using default \u001b[1;36m10.0\u001b[0m. \n" + ] + } + ], + "source": [ + "c = Cell(length_b=-8.8)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "749e3f57-1097-4939-b853-4c67148fb831", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mlength_b\u001b[0m\u001b[1m>\u001b[0m. Expected `numeric`, got `str` \u001b[1m(\u001b[0m\u001b[32m'7.7'\u001b[0m\u001b[1m)\u001b[0m. Using default \u001b[1;36m10.0\u001b[0m. \n" + ] + } + ], + "source": [ + "c = Cell(length_b='7.7')" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "f06c5fa7-372c-4d76-b749-634d92fe6d11", + "metadata": {}, + "outputs": [], + "source": [ + "c = Cell(length_b=6.6)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "70fd1bf8-e7a3-4576-bed2-b60edf8a8097", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-5.5\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m6.6\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.value = -5.5" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b1277593-dea1-44c9-ac7e-d113f4ee3fda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m-4.4\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m6.6\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b = -4.4" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "e174a5cf-2653-431b-a665-f88a8e4423f0", + "metadata": {}, + "outputs": [], + "source": [ + "c.length_b = 3.3" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "9b429e1d-eabd-4e35-a3b7-1a9b752bb4f1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[1;36m2222.2\u001b[0m outside \u001b[1m[\u001b[0m\u001b[1;36m0\u001b[0m, \u001b[1;36m1000\u001b[0m\u001b[1m]\u001b[0m. Keeping current \u001b[1;36m3.3\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b = 2222.2" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "65d2c0fb-8e35-493c-a3e3-24421e9326bb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b.free\u001b[0m\u001b[1m>\u001b[0m. Expected `bool`, got `str` \u001b[1m(\u001b[0m\u001b[32m'qwe'\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[3;91mFalse\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.free = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "4475ed9b-74d1-4080-8e57-0099b35e94f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'fre'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Did you mean \u001b[32m'free'\u001b[0m? \n" + ] + } + ], + "source": [ + "c.length_b.fre = 'fre'" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "e3d37a6d-9ec8-4d96-8f95-ce7b67e65f36", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'qwe'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. Allowed writable: \u001b[32m'fit_max'\u001b[0m, \u001b[32m'fit_min'\u001b[0m, \u001b[32m'free'\u001b[0m, \u001b[32m'uncertainty'\u001b[0m, \n", + " \u001b[32m'value'\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.qwe = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "f108a56d-775c-4d4e-aedf-ecc4ead28178", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Cannot modify read-only attribute \u001b[32m'description'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell.length_b\u001b[0m\u001b[1m>\u001b[0m. \n" + ] + } + ], + "source": [ + "c.length_b.description = 'desc'" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "382d6221-0074-4ec8-84a8-ec51e117420a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Unknown attribute \u001b[32m'qwe'\u001b[0m of \u001b[1m<\u001b[0m\u001b[1;95mcell\u001b[0m\u001b[1m>\u001b[0m. Allowed writable: \u001b[32m'angle_alpha'\u001b[0m, \u001b[32m'angle_beta'\u001b[0m, \u001b[32m'angle_gamma'\u001b[0m, \u001b[32m'length_a'\u001b[0m, \n", + " \u001b[32m'length_b'\u001b[0m, \u001b[32m'length_c'\u001b[0m. \n" + ] + } + ], + "source": [ + "c.qwe = 'qwe'" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "7bf7bac1-79ce-45df-a2a8-c4b090359292", + "metadata": {}, + "outputs": [], + "source": [ + "sg = SpaceGroup()" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "5c30d269-5167-4598-ac57-66715673d9b0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mname_h_m\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'qwe'\u001b[0m is unknown. \u001b[1m(\u001b[0m\u001b[1;36m230\u001b[0m allowed values not listed here\u001b[1m)\u001b[0m. Using default \n", + " \u001b[32m'P 1'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='qwe')" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "c99fcf63-9536-4ea6-a30a-96367bb4aacb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mname_h_m\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'P n m'\u001b[0m is unknown. \u001b[1m(\u001b[0m\u001b[1;36m230\u001b[0m allowed values not listed here\u001b[1m)\u001b[0m. Using default\n", + " \u001b[32m'P 1'\u001b[0m. \n", + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mit_coordinate_system_code\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'cab'\u001b[0m is unknown. Allowed values: \u001b[32m''\u001b[0m. Using default \n", + " \u001b[32m''\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='P n m', it_coordinate_system_code='cab')" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "c2d7509e-a49b-4c2b-aa45-76a97de0761e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Value mismatch for \u001b[1m<\u001b[0m\u001b[1;95mit_coordinate_system_code\u001b[0m\u001b[1m>\u001b[0m. Provided \u001b[32m'cabd'\u001b[0m is unknown. Allowed values: \u001b[32m'-cba'\u001b[0m, \u001b[32m'a-cb'\u001b[0m, \n", + " \u001b[32m'abc'\u001b[0m, \u001b[32m'ba-c'\u001b[0m, \u001b[32m'bca'\u001b[0m, \u001b[32m'cab'\u001b[0m. Using default \u001b[32m'abc'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg = SpaceGroup(name_h_m='P n m a', it_coordinate_system_code='cabd')" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "07db2ef2-58f3-4b46-8223-a2067254db51", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING \u001b[0m Type mismatch for \u001b[1m<\u001b[0m\u001b[1;95mspace_group.name_h_m\u001b[0m\u001b[1m>\u001b[0m. Expected `string`, got `float` \u001b[1m(\u001b[0m\u001b[1;36m34.9\u001b[0m\u001b[1m)\u001b[0m. Keeping current \u001b[32m'P n m a'\u001b[0m. \n" + ] + } + ], + "source": [ + "sg.name_h_m = 34.9" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6934d72d-01a7-41e3-8684-5a1c6106e933", + "metadata": {}, + "outputs": [], + "source": [ + "sg.name_h_m = 'P 1'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8a152ec-5a11-4c38-bca4-7a4230218f34", + "metadata": {}, + "outputs": [], + "source": [ + "sg.name_h_m = 'P n m a'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6626a960-6390-4e76-8f27-dc2cebbdd123", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb299acb-0920-43f3-be25-c432544e1198", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (Pixi)", + "language": "python", + "name": "pixi-kernel-python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tmp/Untitled2.ipynb b/tmp/Untitled2.ipynb new file mode 100644 index 00000000..363fcab7 --- /dev/null +++ b/tmp/Untitled2.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py b/tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py new file mode 100644 index 00000000..cf21bcc8 --- /dev/null +++ b/tmp/basic_single-fit_pd-neut-cwl_LBCO-HRPT.py @@ -0,0 +1,750 @@ +# %% [markdown] +# # Structure Refinement: LBCO, HRPT +# +# This example demonstrates how to use the EasyDiffraction API in a +# simplified, user-friendly manner that closely follows the GUI workflow +# for a Rietveld refinement of La0.5Ba0.5CoO3 crystal structure using +# constant wavelength neutron powder diffraction data from HRPT at PSI. +# +# 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. +# +# 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.experiments`, and `project.analysis`. The `project` object is +# the main container for all information. + +# %% [markdown] +# ## Import Library + +# %% +# %% +import os + +import easydiffraction as ed +from easydiffraction.utils.logging import console +from easydiffraction.utils.logging import log + +# %% +print(os.getenv('TERM_PROGRAM')) + +# %% +# !echo $TERM_PROGRAM + +# %% + +# Logger.configure( +# level=Logger.Level.DEBUG, +# mode=Logger.Mode.VERBOSE, +# reaction=Logger.Reaction.WARN, +# ) + + +console.print('Initializing logger 1a', '111', 'Initializing logger 1b') +log.debug('Initializing logger 2a', '222', 'Initializing logger 2b') +log.info('Initializing logger INFO') +log.warning('Initializing logger WARNING') +log.debug('a') +# log.error("Initializing logger ERROR") +log.debug('b') +# log.critical("Initializing logger CRITICAL") +console.chapter('Chapter: Initializing logger 7') +console.section('Section: Initializing logger 8') +console.paragraph('Paragraph: Initializing logger 9') +console.print('aaa') +# exit() + + +# %% [markdown] +# ## Step 1: Create a Project +# +# This section explains how to create a project and define its metadata. + +# %% [markdown] +# #### Create Project + +# %% +project = ed.Project(name='lbco_hrpt') + +# %% [markdown] +# #### Set Project Metadata + +# %% +project.info.title = 'La0.5Ba0.5CoO3 at HRPT@PSI' +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).""" + +# %% [markdown] +# #### Show Project Metadata as CIF + +# %% +project.info.show_as_cif() + +# %% [markdown] +# #### Save Project +# +# When saving the project for the first time, you need to specify the +# directory path. In the example below, the project is saved to a +# temporary location defined by the system. + +# %% +project.save_as(dir_path='lbco_hrpt', temporary=True) + +# %% [markdown] +# #### Set Up Data Plotter + +# %% [markdown] +# Show supported plotting engines. + +# %% +project.plotter.show_supported_engines() + +# %% [markdown] +# Show current plotting configuration. + +# %% +project.plotter.show_config() + +# %% [markdown] +# Set plotting engine. + +# %% +# project.plotter.engine = 'plotly' + +# %% +project.tabler.show_config() +project.tabler.show_supported_engines() +#project.tabler.engine = 'rich' + +# %% [markdown] +# ## Step 2: Define Sample Model +# +# This section shows how to add sample models and modify their +# parameters. + +# %% [markdown] +# #### Add Sample Model + +# %% +project.sample_models.add_minimal(name='lbco') + +# %% [markdown] +# #### Show Defined Sample Models +# +# 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. + +# %% +project.sample_models.show_names() + +# %% [markdown] +# #### Set Space Group +# +# 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' + +# %% [markdown] +# #### Set Unit Cell +# +# Modify the default unit cell parameters. + +# %% +project.sample_models['lbco'].cell.length_a = 3.88 + +# %% [markdown] +# #### Set Atom Sites +# +# Add atom sites to the sample model. + +# %% +project.sample_models['lbco'].atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) +project.sample_models['lbco'].atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) +project.sample_models['lbco'].atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5, +) +project.sample_models['lbco'].atom_sites.add_from_args( + label='O', + type_symbol='O', + fract_x=0, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='c', + b_iso=0.5, +) + +# %% [markdown] +# #### Apply Symmetry Constraints + +# %% +project.sample_models['lbco'].apply_symmetry_constraints() + +# %% [markdown] +# #### Show Sample Model as CIF + +# %% +project.sample_models['lbco'].show_as_cif() + +# %% [markdown] +# #### Show Sample Model Structure + +# %% +project.sample_models['lbco'].show_structure() + +# %% [markdown] +# #### Save Project State +# +# Save the project state after adding the sample model. This ensures +# that all changes are stored and can be accessed later. The project +# state is saved in the directory specified during project creation. + +# %% +project.save() + +# %% [markdown] +# ## Step 3: Define Experiment +# +# This section shows how to add experiments, configure their parameters, +# and link the sample models defined in the previous step. + +# %% [markdown] +# #### Download Measured Data +# +# Download the data file from the EasyDiffraction repository on GitHub. + +# %% +ed.download_from_repository('hrpt_lbco.xye', destination='data') + +# %% [markdown] +# #### Add Diffraction Experiment + +# %% +project.experiments.add_from_data_path( + name='hrpt', + data_path='data/hrpt_lbco.xye', + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='neutron', +) + +# %% [markdown] +# #### Show Defined Experiments + +# %% +project.experiments.show_names() + +# %% [markdown] +# #### Show Measured Data + +# %% +project.plot_meas(expt_name='hrpt') + +# %% [markdown] +# #### Set Instrument +# +# Modify the default instrument parameters. + +# %% +project.experiments['hrpt'].instrument.setup_wavelength = 1.494 +project.experiments['hrpt'].instrument.calib_twotheta_offset = 0.6 + +# %% [markdown] +# #### Set Peak Profile +# +# Show supported peak profile types. + +# %% +project.experiments['hrpt'].show_supported_peak_profile_types() + +# %% [markdown] +# Show the current peak profile type. + +# %% +project.experiments['hrpt'].show_current_peak_profile_type() + +# %% [markdown] +# Select the desired peak profile type. + +# %% +project.experiments['hrpt'].peak_profile_type = 'pseudo-voigt' + +# %% [markdown] +# Modify default peak profile parameters. + +# %% +project.experiments['hrpt'].peak.broad_gauss_u = 0.1 +project.experiments['hrpt'].peak.broad_gauss_v = -0.1 +project.experiments['hrpt'].peak.broad_gauss_w = 0.1 +project.experiments['hrpt'].peak.broad_lorentz_x = 0 +project.experiments['hrpt'].peak.broad_lorentz_y = 0.1 + +# %% [markdown] +# #### Set Background + +# %% [markdown] +# Show supported background types. + +# %% +project.experiments['hrpt'].show_supported_background_types() + +# %% [markdown] +# Show current background type. + +# %% +project.experiments['hrpt'].show_current_background_type() + +# %% [markdown] +# Select the desired background type. + +# %% +project.experiments['hrpt'].background_type = 'line-segment' + +# %% [markdown] +# Add background points. + +# %% +project.experiments['hrpt'].background.add_from_args(x=10, y=170) +project.experiments['hrpt'].background.add_from_args(x=30, y=170) +project.experiments['hrpt'].background.add_from_args(x=50, y=170) +project.experiments['hrpt'].background.add_from_args(x=110, y=170) +project.experiments['hrpt'].background.add_from_args(x=165, y=170) + +# %% [markdown] +# Show current background points. + +# %% +project.experiments['hrpt'].background.show() + +# %% [markdown] +# #### Set Linked Phases +# +# Link the sample model defined in the previous step to the experiment. + +# %% +project.experiments['hrpt'].linked_phases.add_from_args(id='lbco', scale=10.0) + +# %% [markdown] +# #### Show Experiment as CIF + +# %% +project.experiments['hrpt'].show_as_cif() + +# %% [markdown] +# #### Save Project State + +# %% +project.save() + +# %% [markdown] +# ## Step 4: Perform Analysis +# +# This section explains the analysis process, including how to set up +# calculation and fitting engines. +# +# #### Set Calculator +# +# Show supported calculation engines. + +# %% +project.analysis.show_supported_calculators() + +# %% [markdown] +# Show current calculation engine. + +# %% +project.analysis.show_current_calculator() + +# %% [markdown] +# Select the desired calculation engine. + +# %% +project.analysis.current_calculator = 'cryspy' + +# %% [markdown] +# #### Show Calculated Data + +# %% +project.plot_calc(expt_name='hrpt') + +# %% [markdown] +# #### Plot Measured vs Calculated + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True) + +# %% [markdown] +# #### Show Parameters +# +# Show all parameters of the project. + +# %% +project.analysis.show_all_params() + +# %% [markdown] +# Show all fittable parameters. + +# %% +project.analysis.show_fittable_params() + +# %% [markdown] +# Show only free parameters. + +# %% +project.analysis.show_free_params() + +# %% [markdown] +# Show how to access parameters in the code. + +# %% +project.analysis.how_to_access_parameters() + +# %% [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' + +# %% [markdown] +# #### Set Minimizer +# +# Show supported fitting engines. + +# %% +project.analysis.show_available_minimizers() + +# %% [markdown] +# Show current fitting engine. + +# %% +project.analysis.show_current_minimizer() + +# %% [markdown] +# Select desired fitting engine. + +# %% +project.analysis.current_minimizer = 'lmfit (leastsq)' + +# %% [markdown] +# ### Perform Fit 1/5 +# +# Set sample model parameters to be refined. + +# %% +project.sample_models['lbco'].cell.length_a.free = True + +# %% [markdown] +# Set experiment parameters to be refined. + +# %% +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 +project.experiments['hrpt'].background['30'].y.free = True +project.experiments['hrpt'].background['50'].y.free = True +project.experiments['hrpt'].background['110'].y.free = True +project.experiments['hrpt'].background['165'].y.free = True + +# %% [markdown] +# Show free parameters after selection. + +# %% +project.analysis.show_free_params() + +# %% [markdown] +# #### Run Fitting + +# %% +project.tabler.engine = 'rich' +project.sample_models['lbco'].cell.length_a = 3.88 +project.analysis.fit() + +# %% [markdown] +# #### Plot Measured vs Calculated + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True) + +# %% [markdown] +# #### Save Project State + +# %% +project.save_as(dir_path='lbco_hrpt', temporary=True) + +# %% [markdown] +# ### Perform Fit 2/5 +# +# Set more parameters to be refined. + +# %% +project.experiments['hrpt'].peak.broad_gauss_u.free = True +project.experiments['hrpt'].peak.broad_gauss_v.free = True +project.experiments['hrpt'].peak.broad_gauss_w.free = True +project.experiments['hrpt'].peak.broad_lorentz_y.free = True + +# %% [markdown] +# Show free parameters after selection. + +# %% +project.analysis.show_free_params() + +# %% [markdown] +# #### Run Fitting + +# %% +project.analysis.fit() + +# %% [markdown] +# #### Plot Measured vs Calculated + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True) + +# %% [markdown] +# #### Save Project State + +# %% +project.save_as(dir_path='lbco_hrpt', temporary=True) + +# %% [markdown] +# ### Perform Fit 3/5 +# +# 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 + +# %% [markdown] +# Show free parameters after selection. + +# %% +project.analysis.show_free_params() + +# %% [markdown] +# #### Run Fitting + +# %% +project.analysis.fit() + +# %% [markdown] +# #### Plot Measured vs Calculated + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True) + +# %% [markdown] +# #### Save Project State + +# %% +project.save_as(dir_path='lbco_hrpt', temporary=True) + +# %% [markdown] +# ### Perform Fit 4/5 +# +# #### Set Constraints +# +# Set aliases for parameters. + +# %% +project.analysis.aliases.add_from_args( + label='biso_La', + param_uid=project.sample_models['lbco'].atom_sites['La'].b_iso.uid, +) +project.analysis.aliases.add_from_args( + label='biso_Ba', + param_uid=project.sample_models['lbco'].atom_sites['Ba'].b_iso.uid, +) + +# %% [markdown] +# Set constraints. + +# %% +project.analysis.constraints.add_from_args(lhs_alias='biso_Ba', rhs_expr='biso_La') + +# %% [markdown] +# Show defined constraints. + +# %% +project.analysis.show_constraints() + +# %% [markdown] +# Show free parameters before applying constraints. + +# %% +project.analysis.show_free_params() + +# %% [markdown] +# Apply constraints. + +# %% +project.analysis.apply_constraints() + +# %% [markdown] +# Show free parameters after applying constraints. + +# %% +project.analysis.show_free_params() + +# %% [markdown] +# #### Run Fitting + +# %% +project.analysis.fit() + +# %% [markdown] +# #### Plot Measured vs Calculated + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True) + +# %% [markdown] +# #### Save Project State + +# %% +project.save_as(dir_path='lbco_hrpt', temporary=True) + +# %% [markdown] +# ### Perform Fit 5/5 +# +# #### Set Constraints +# +# Set more aliases for parameters. + +# %% +project.analysis.aliases.add_from_args( + label='occ_La', + param_uid=project.sample_models['lbco'].atom_sites['La'].occupancy.uid, +) +project.analysis.aliases.add_from_args( + label='occ_Ba', + param_uid=project.sample_models['lbco'].atom_sites['Ba'].occupancy.uid, +) + +# %% [markdown] +# Set more constraints. + +# %% +project.analysis.constraints.add_from_args( + lhs_alias='occ_Ba', + rhs_expr='1 - occ_La', +) + +# %% [markdown] +# Show defined constraints. + +# %% +project.analysis.show_constraints() + +# %% [markdown] +# Apply constraints. + +# %% +project.analysis.apply_constraints() + +# %% [markdown] +# Set sample model parameters to be refined. + +# %% +project.sample_models['lbco'].atom_sites['La'].occupancy.free = True + +# %% [markdown] +# Show free parameters after selection. + +# %% +project.analysis.show_free_params() + +# %% [markdown] +# #### Run Fitting + +# %% +project.analysis.fit() + +# %% [markdown] +# #### Plot Measured vs Calculated + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# %% +project.plot_meas_vs_calc(expt_name='hrpt', x_min=38, x_max=41, show_residual=True) + +# %% [markdown] +# #### Save Project State + +# %% +project.save_as(dir_path='lbco_hrpt', temporary=True) + +# %% [markdown] +# ## Step 5: Summary +# +# This final section shows how to review the results of the analysis. + +# %% [markdown] +# #### Show Project Summary + + +# %% +project.summary.show_report() diff --git a/tmp/display.py b/tmp/display.py new file mode 100644 index 00000000..1fd08a68 --- /dev/null +++ b/tmp/display.py @@ -0,0 +1,413 @@ +import sys, os +sys.path.insert(0, "src") +os.chdir("/Users/andrewsazonov/Development/github.com/EasyScience/diffraction-lib") +import easydiffraction as ed + +project = ed.Project() + +print(project.tabler.engine) +project.tabler.engine = 'pandas' +project.tabler.engine = 'rich' +project.tabler.engine = 'rich2' +project.tabler.engine = 'pandas' + +project.tabler.show_supported_engines() + +project.tabler.engine = 'rich' +project.tabler.engine = 'pandas' +project.tabler.engine = 'rich2' +project.tabler.engine = 'rich' + +project.tabler.show_supported_engines() + + + +# + +import pyarrow as pa + +# Creating two tables to join +left_table = pa.table({'key': [1, 2, 3], 'value_left': ['A', 'B', 'C']}) +right_table = pa.table({'key': [1, 2, 3], 'value_right': ['X', 'Y', 'Z']}) + +# Performing an inner join on the 'key' column +joined_table = left_table.join(right_table, keys='key') +print(joined_table) +# - + + + +# + +import polars as pl +import pandas as pd + +df = pl.DataFrame({ + "Name": ["Alice", "Bob", "Charlie"], + "Age": [32, 28, 36], + "City": ["London", "Paris", "New York"], +}).to_pandas() + +alignments = ["left", "center", "right"] + +styles = [ + {"selector": "th", "props": [("border", "1px solid black"), ("text-align", "center")]}, + {"selector": "td", "props": [("border", "1px solid black")]}, +] + +styled = df.style.set_table_styles(styles).apply( + lambda row: ["background-color: #f9f9f9" if row.name % 2 else "" for _ in row], axis=1 +) + +for col, align in zip(df.columns, alignments): + styled = styled.set_properties(subset=[col], **{"text-align": align}) + +styled # ✅ works in Jupyter, plain Pandas Styler + +# + +from itables import options + +options.allow_html = True + +# + +import polars as pl +import pandas as pd + +df = pl.DataFrame({ + "Name": ["Alice", "Bob", "Charlie"], + "Age": [32, 28, 36], + "City": ["London", "Paris", "New York"], +}).to_pandas() + +alignments = ["left", "center", "right"] + +styles = [ + {"selector": "th", "props": [("border", "1px solid black"), ("text-align", "center")]}, + {"selector": "td", "props": [("border", "1px solid black")]}, +] + +styled = df.style.set_table_styles(styles).apply( + lambda row: ["background-color: #f9f9f9" if row.name % 2 else "" for _ in row], axis=1 +) + +for col, align in zip(df.columns, alignments): + styled = styled.set_properties(subset=[col], **{"text-align": align}) + +styled # ✅ works in Jupyter, plain Pandas Styler + +# + +import pyarrow as pa +import pandas as pd + +schema = pa.schema([ + pa.field("Name", pa.string(), metadata={"align": "left"}), + pa.field("Age", pa.int32(), metadata={"align": "center"}), + pa.field("City", pa.string(), metadata={"align": "right"}), +]) + +table = pa.Table.from_pydict( + {"Name": ["Alice", "Bob", "Charlie"], "Age": [32, 28, 36], "City": ["London", "Paris", "New York"]}, + schema=schema +) + +df = table.to_pandas() +alignments = [field.metadata.get(b"align", b"left").decode() for field in schema] + +styled = df.style.apply( + lambda row: ["background-color: #f2f2f2" if row.name % 2 else "" for _ in row], axis=1 +) + +for col, align in zip(df.columns, alignments): + styled = styled.set_properties(subset=[col], **{"text-align": align}) + +styled # ✅ works in Jupyter + +# + +import pandas as pd +import ipydatagrid as gd +from IPython.display import display + +df = pd.DataFrame({ + "Name": ["Alice", "Bob", "Charlie"], + "Age": [32, 28, 36], + "City": ["London", "Paris", "New York"], +}) + +grid = gd.DataGrid( + df, + style={ + "header": {"font_weight": "bold", "text_align": "center", "background_color": "#ddd"}, + "row_even": {"background_color": "#f9f9f9"}, + "row_odd": {"background_color": "#ffffff"}, + "cell": {"border": "1px solid black"}, + "column_Name": {"text_align": "left"}, + "column_Age": {"text_align": "center"}, + "column_City": {"text_align": "right"}, + }, + auto_fit_columns=True, +) + +display(grid) # ✅ force Jupyter to show the widget instead of repr + +# + +import pandas as pd +from itables import init_notebook_mode, show + +init_notebook_mode(all_interactive=True) # global setup + +df = pd.DataFrame({ + "Name": ["Alice", "Bob", "Charlie"], + "Age": [32, 28, 36], + "City": ["London", "Paris", "New York"], +}) + +alignments = ["left", "center", "right"] + +styled = df.style.apply( + lambda row: ["background-color: red" if row.name % 2 else "" for _ in row], axis=1 +) + +for col, align in zip(df.columns, alignments): + styled = styled.set_properties(subset=[col], **{"text-align": align}) + +# ✅ must pass allow_html=True here +show(styled, allow_html=True) + +# + +from ipydatagrid import DataGrid +from IPython.display import display +import pandas as pd + +df = pd.DataFrame({ + "Name": ["Alice", "Bob", "Charlie"], + "Age": [25, 30, 35], + "Score": [85.5, 90.2, 88.8], +}) + +grid = DataGrid(df) +display(grid) # <-- Should render interactive table if widgets are enabled + +# + +import pandas as pd + +# Example dataframe +df = pd.DataFrame({ + "Name": ["Alice", "Bob", "Charlie"], + "Age": [25, 30, 35], + "Score": [85.5, 90.2, 88.8], + "_align": ["left", "center", "right"] # alignment metadata +}) + +# Exclude _align when displaying +display(df.drop(columns="_align")) + +# Define a styler +styled = ( + df.style + .set_properties(**{ + "border": "1px solid grey", + "border-collapse": "collapse", + }) + .set_table_styles( + [ + {"selector": "th", "props": [("text-align", "center"), ("background-color", "#f2f2f2")]}, + {"selector": "td", "props": [("padding", "4px 8px")]}, + ], + overwrite=False, + ) + .apply(lambda s: ["background-color: #f9f9f9" if i % 2 == 0 else "" for i in range(len(s))], axis=0) +) + +# Apply column-specific alignment +for col, a in align.items(): + styled = styled.set_properties(subset=[col], **{"text-align": a}) + +display(styled) + + +# + +import pandas as pd + +# Build DataFrame with MultiIndex columns +df = pd.DataFrame({ + ("Name", "left"): ["Alice", "Bob", "Charlie"], + ("Age", "center"): [25, 3000, 35], + ("Score", "right"): [85.5, 90.2, 88.8], +}) + +filtered_df = df[['Name', 'Age']] +print(filtered_df) +# - + + + + + + + + + + + + + + + + + + + + + + + + + + +# + +import pandas as pd + +# Build DataFrame with MultiIndex columns +df = pd.DataFrame({ + ("Name", "left"): ["Alice", "Bob", "Charlie"], + ("Age", "center"): [25, 30, 35], + ("Score", "right"): [85.5, 90.2, 88.8], +}) + +filtered = + + +df.columns = pd.MultiIndex.from_tuples(df.columns, names=["#", "align"]) + +# Extract alignments +alignments = dict(zip(df.columns.get_level_values("#"), + df.columns.get_level_values("align"))) + +# Drop alignment level for display +df_display = df.copy() +#df_display.columns = df_display.columns.get_level_values("#") + +# Styler with alignment + number formatting +def apply_alignment(styler, aligns): + for col, align in aligns.items(): + styler = styler.set_properties( + subset=[col], + **{ "text-align": align } + ) + return styler + +styled = apply_alignment(df_display.style, alignments).format( + precision=2, # max 2 decimals + na_rep="", # empty for NaN +) + + +html = styled.to_html( + escape=False, + index=False, + #formatters=formatters, + #border=0, + #header=not skip_headers, + ) + +display(HTML(html)) + +# + +import pandas as pd + +# Create DataFrame with MultiIndex columns (name, alignment) +df = pd.DataFrame( + { + ("#", "left"): [1, 2, 3], + ("Name", "left"): ["Alice", "Bob", "Charlie"], + ("Age", "center"): [25, 30, 35], + ("Score", "right"): [85.5, 90.2, 88.8], + } +) + +# Extract alignments in a simple way +alignments = {col: align for col, align in df.columns} + +# Drop MultiIndex for display (keep only column names) +df.columns = [col for col, _ in df.columns] + +# Apply alignment via Styler +styler = df.style.set_properties(**{ + "text-align": "center" # default +}) +for col, align in alignments.items(): + styler = styler.set_properties(subset=[col], **{"text-align": align}) + +# Hide the pandas default index +styler = styler.hide(axis="index") + +# Optional: set precision for numeric formatting +styler = styler.format(precision=1) + +styler + +# + +import pandas as pd + +df = pd.DataFrame({ + "_align": ["center", "left", "left"], # alignment metadata + "Name": ["Alice", "Bob", "Charlie"], + "Age": [25, 30000, 35], + "Score": [85.5, 90.2, 88.8], +}) + +# Exclude _align when displaying +#display(df.drop(columns="_align")) + +# Use _align column when building Styler +align = dict(zip(df.columns[:-1], df["_align"])) +print(align.values()) +#styled = df.drop(columns="_align").style.set_properties( +# **{f"text-align": v for v in align.values()} +#) + +# Apply alignment via Styler +styled = df.style.set_properties(**{ + "text-align": "center" # default +}) +for col, align in alignments.items(): + styled = styler.set_properties(subset=[col], **{"text-align": align}) + +html = styled.to_html( + escape=False, + index=False, + #formatters=formatters, + #border=0, + #header=not skip_headers, + ) +display(HTML(html)) + + +print(df.index) +# - + + + +# + +df = pd.DataFrame({ + "Name": ["Alice", "Bob", "Charlie"], + "Age": [25, 30, 35], + "Score": [85.5, 90.2, 88.8], +}) + +df.attrs["align"] = {"Name": "left", "Age": "center", "Score": "right"} + +# Retrieve later +align = df.attrs.get("align", {}) +styled = df.style.set_properties( + **{col: f"text-align: {a}" for col, a in align.items()} +) +html = styled.to_html( + escape=False, + index=False, + #formatters=formatters, + #border=0, + #header=not skip_headers, + ) +display(HTML(html)) +# - + + diff --git a/tmp/display2.py b/tmp/display2.py new file mode 100644 index 00000000..47a325a3 --- /dev/null +++ b/tmp/display2.py @@ -0,0 +1,28 @@ +import pandas as pd + +df = pd.DataFrame({ + "Name": ["Alice", "Bob", "Charlie"], + "Age": [25, 30000, 35], + "Score": [85.5, 90.2, 88.8], +}) + +df + +# Filtering +df = df[['Name', 'Age']] + +alignments = ["left", "center", "right"] + +styles = [ + {"selector": "th", "props": [("border", "1px solid green"), ("text-align", "center")]}, + {"selector": "td", "props": [("border", "1px solid red")]}, +] + +styled = df.style.set_table_styles(styles) + +for col, align in zip(df.columns, alignments): + styled = styled.set_properties(subset=[col], **{"text-align": align}) + +styled + + diff --git a/tmp/display3-Copy1.py b/tmp/display3-Copy1.py new file mode 100644 index 00000000..0da4deac --- /dev/null +++ b/tmp/display3-Copy1.py @@ -0,0 +1,45 @@ +import pandas as pd + +df = pd.DataFrame({ + ("Name", "left"): ["Alice", "Bob", "Charlie"], + ("Age", "center"): [25, 3000, 35], + ("Score", "right"): [6585.5, 90.202, -558.8], +}) +df + +# Filtering +df = df[['Name', 'Score']] +df + +# + +# Table Model +# - + +#import sys, os +#sys.path.insert(0, "src") +#os.chdir("/Users/andrewsazonov/Development/github.com/EasyScience/diffraction-lib") +import easydiffraction as ed + +from easydiffraction.display.tables import TableRenderer + + +tabler = TableRenderer() + +tabler.render(df) + + + +tabler.show_config() +tabler.show_supported_engines() + +tabler.show_current_engine() + +tabler.engine = 'pandas' + +tabler.render(df) + +tabler.show_supported_engines() + + + + diff --git a/tmp/display3.py b/tmp/display3.py new file mode 100644 index 00000000..822ba960 --- /dev/null +++ b/tmp/display3.py @@ -0,0 +1,173 @@ +import pandas as pd + +FLOAT_PRECISION = 4 + +df = pd.DataFrame({ + ("Name", "left"): ["Alice", "Bob", "Charlie"], + ("Age", "center"): [25, 3000, 35], + ("Score", "right"): [6585.5, 90.202, -558.8], +}) +df + +# Filtering +df = df[['Name', 'Age', 'Score']] + +# + +# Table Model +# - + +# Force starting index from 1 +df.index += 1 + + +# + +def rich_to_hex(color): + from rich.color import Color + c = Color.parse(color) + rgb = c.get_truecolor() + hex_value = "#{:02x}{:02x}{:02x}".format(*rgb) + return hex_value + +# Styling +rich_dim_color_dark = "grey35" +rich_dim_color_light = "grey85" +pd_dim_color_dark = rich_to_hex(rich_dim_color_dark) +pd_dim_color_light = rich_to_hex(rich_dim_color_light) + +# + +from jupyter_dark_detect import is_dark +from IPython import get_ipython + +def is_dark_theme() -> bool: + """Return 'dark' or 'light'. + If not running inside Jupyter, return default.""" + default = True + + in_jupyter = get_ipython() is not None and \ + get_ipython().__class__.__name__ == "ZMQInteractiveShell" + + if not in_jupyter: + return default + + return True if is_dark() else False + + +rich_dim_color = rich_dim_color_dark if is_dark_theme() else rich_dim_color_light +pd_dim_color = pd_dim_color_dark if is_dark_theme() else pd_dim_color_light +print("is_dark", is_dark_theme()) +print("rich_dim_color", rich_dim_color) +print("pd_dim_color", pd_dim_color) + + +# + +# Model View: Rich +from rich.table import Table +from rich.console import Console +from rich.box import Box +# box.SQUARE +# ┌─┬┐ top +# │ ││ head +# ├─┼┤ head_row +# │ ││ mid +# ├─┼┤ foot_row +# ├─┼┤ foot_row +# │ ││ foot +# └─┴┘ bottom +custom_box = Box( + """\ +┌──┐ +│ │ +├──┤ +│ │ +├──┤ +├──┤ +│ │ +└──┘ +""", + ascii=False, +) + + +console = Console() +table = Table( + title=None, + box=custom_box, + show_header=True, + header_style='bold', + border_style=rich_dim_color, +) + +# Add index column header first +#table.add_column("#", justify="right") +table.add_column(style=rich_dim_color) + +# Add other column headers with alignment from 2nd level +for col, align in zip(df.columns.get_level_values(0), df.columns.get_level_values(1)): + table.add_column(str(col), justify=align) + +# Define precision +float_fmt = (f"{{:.{FLOAT_PRECISION}f}}").format + +# Add rows (prepend the index value as first column) +for idx, row in df.iterrows(): + formatted_row = [ + float_fmt(val) if isinstance(val, float) else str(val) + for val in row + ] + #table.add_row(str(idx), *map(str, row)) + table.add_row(str(idx), *formatted_row) + +console.print(table) +# - + +# Extract column alignments +alignments = df.columns.get_level_values(1) +alignments + +# Remove alignments from df (Keep only the first index level) +df.columns = df.columns.get_level_values(0) +df + +# + +styled = ( + df.style + .set_table_styles( + [ + # Outer border on the entire table + {"selector": " ", "props": [ + ("border", f"1px solid {pd_dim_color}"), + ("border-collapse", "collapse") + ]}, + + # Horizontal border under header row + {"selector": "thead", "props": [ + ("border-bottom", f"1px solid {pd_dim_color}") + ]}, + + # Remove all cell borders + {"selector": "th, td", "props": [ + ("border", "none") + ]}, + + # Style for index column + {"selector": "th.row0, th.row1, th.row2, th.row_heading", "props": [ + ("color", pd_dim_color), + ("font-weight", "normal") + ]}, + ] + ) + .format(precision=FLOAT_PRECISION) +) + +styled +# - +# column alignment +for col, align in zip(df.columns, alignments): + styled = styled.set_properties(subset=[col], **{"text-align": align}) +styled + + + + + + diff --git a/tmp/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py b/tmp/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py new file mode 100644 index 00000000..d8cf856b --- /dev/null +++ b/tmp/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py @@ -0,0 +1,120 @@ +# %% +# ## Import Library +import easydiffraction as ed + +# %% +# ## Step 1: Define Project +project = ed.Project() +#project.tabler.engine = 'rich' +#project.tabler.engine = 'pandas' + +# %% +# ## Step 2: Define Sample Model +project.sample_models.add_minimal(name='lbco') + +sample_model = project.sample_models['lbco'] +sample_model.space_group.name_h_m = 'P m -3 m' +sample_model.space_group.it_coordinate_system_code = '1' +sample_model.cell.length_a = 3.88 +sample_model.atom_sites.add_from_args( + label='La', + type_symbol='La', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) +sample_model.atom_sites.add_from_args( + label='Ba', + type_symbol='Ba', + fract_x=0, + fract_y=0, + fract_z=0, + wyckoff_letter='a', + b_iso=0.5, + occupancy=0.5, +) +sample_model.atom_sites.add_from_args( + label='Co', + type_symbol='Co', + fract_x=0.5, + fract_y=0.5, + fract_z=0.5, + wyckoff_letter='b', + b_iso=0.5, +) +sample_model.atom_sites.add_from_args( + label='O', type_symbol='O', fract_x=0, fract_y=0.5, fract_z=0.5, wyckoff_letter='c', b_iso=0.5 +) + +# %% +# ## Step 3: Define Experiment +ed.download_from_repository('hrpt_lbco.xye', destination='data') + +project.experiments.add_from_data_path( + name='hrpt', + data_path='data/hrpt_lbco.xye', + sample_form='powder', + beam_mode='constant wavelength', + radiation_probe='neutron', +) + +experiment = project.experiments['hrpt'] +experiment.instrument.setup_wavelength = 1.494 +experiment.instrument.calib_twotheta_offset = 0.6 +experiment.peak.broad_gauss_u = 0.1 +experiment.peak.broad_gauss_v = -0.1 +experiment.peak.broad_gauss_w = 0.1 +experiment.peak.broad_lorentz_y = 0.1 +experiment.background.add_from_args(x=10, y=170) +experiment.background.add_from_args(x=30, y=170) +experiment.background.add_from_args(x=50, y=170) +experiment.background.add_from_args(x=110, y=170) +experiment.background.add_from_args(x=165, y=170) +experiment.excluded_regions.add_from_args(start=0, end=5) +experiment.excluded_regions.add_from_args(start=165, end=180) +experiment.linked_phases.add_from_args(id='lbco', scale=10.0) + +# %% +# ## Step 4: Perform Analysis +sample_model.cell.length_a.free = True +sample_model.atom_sites['La'].b_iso.free = True +sample_model.atom_sites['Ba'].b_iso.free = True +sample_model.atom_sites['Co'].b_iso.free = True +sample_model.atom_sites['O'].b_iso.free = True + +experiment.instrument.calib_twotheta_offset.free = True +experiment.peak.broad_gauss_u.free = True +experiment.peak.broad_gauss_v.free = True +experiment.peak.broad_gauss_w.free = True +experiment.peak.broad_lorentz_y.free = True +experiment.background['10'].y.free = True +experiment.background['30'].y.free = True +experiment.background['50'].y.free = True +experiment.background['110'].y.free = True +experiment.background['165'].y.free = True +experiment.linked_phases['lbco'].scale.free = True + +# %% +sample_model.cell.length_a = 3.88 +project.analysis.fit() + +# %% +#project.plot_meas_vs_calc(expt_name='hrpt', show_residual=True) + +# %% +project.analysis.show_all_params() + +# %% +project.analysis.show_fittable_params() + +# %% +project.analysis.show_free_params() + +# %% +project.analysis.how_to_access_parameters() + +# %% +project.analysis.show_parameter_cif_uids() diff --git a/tmp/short.py b/tmp/short.py index 87f90d00..4c731568 100644 --- a/tmp/short.py +++ b/tmp/short.py @@ -1,6 +1,6 @@ from easydiffraction import Experiment from easydiffraction import Experiments -from easydiffraction import Logger +from easydiffraction.utils.logging import logger from easydiffraction import Project from easydiffraction import SampleModel from easydiffraction import SampleModels From b799bed7ab9b2ee3cde317b557b196dbc27f2e5d Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Mon, 15 Dec 2025 12:34:17 +0100 Subject: [PATCH 4/7] Unify CIF parsing, category updates, and data handling (#108) * Adds CIF parsing capabilities to Category and Parameter classes * Refines error logging using _log_name * Refactors for CIF parsing functionality * Enhances CIF parsing support in experiments: 1st step * Enhances CIF parsing in SampleModelFactory * Refactors CIF handling in SampleModelFactory and enhances DatablockItem with categories property * Enhances CIF handling in SampleModelFactory * Refactors ExperimentType initialization to use default values and updates experiment handling in factory methods * Refactors dependencies section in pixi.toml * More refactoring of old datastore towards new data * Refactors symmetry constraint methods in AtomSites and Cell classes for clarity and consistency * Refactors category update handling and updates CIF serialization methods * Refactors update methods and intruduces update_priority attribute * Refactors category update calls in CalculatorBase and Fitter classes for consistency * Refactors calculation methods and updates category handling in analysis and experiment modules * Refactors PdCwlData and PdDataBase classes for improved structure and clarity * Refactors category update handling in Project class and consolidates update logic for plotting methods * Refactors PdDataBase and PdCwlData classes to introduce d-spacing calculations and update methods for improved data handling * Refactors CIF serialization methods by removing unused datastore functions and simplifying experiment data handling * Adds Refln and ReflnData classes for single crystal diffraction data handling; removes obsolete ScDatastore class * Refactors data handling by replacing 'datastore' references with 'data' * Clean up code across multiple modules * Refactors Analysis class by removing unused calculator variable and simplifies fitting method; deletes obsolete datastore test files * Refactors add methods in various classes * Refactors LineSegment and ExcludedRegion classes to add 'id' parameter * Refactors one of the tutorials to fit new API * Refactors PolynomialTerm, LineSegment, and ExcludedRegion classes to add 'id' parameter * Add missing SPDX license headers * Refactor PolynomialTerm, LineSegment, and ExcludedRegion classes to set default values for parameters * Mark parameters as free if uncertainty is present in CIF deserialization * Update Biso values in lbco.cif to include uncertainty and remove unnecessary data * Add minimal example for Rietveld refinement using LBCO and HRPT data * Remove unnecessary blank line before showing project summary report * Refactor minimal example to streamline project and experiment definitions, removing unnecessary logging and aliases * Remove trailing whitespace from multiple files * Refactor joint_fit_experiments method call to use 'add' instead of 'add_from_args' * Refactor add method in Experiments and SampleModels to allow explicit sample/experiment input * Refactor Chebyshev and LineSegment models to include identifier descriptors and improve documentation * Refactor PdDataPoint classes to update point_id type and adjust mixin order * Refactor SampleModelFactory to introduce _create_minimal method for creating default models * Update tutorials * Add new data classes for powder diffraction and total scattering experiments * Temporary disable unit tests hook from pre-push checks * Add d_spacing property to PdDataBase for calculated d values * Refactor tutorials * Refactor format_value function to improve value formatting and precision handling * Refactor category_collection_to_cif function for improved readability and structure * Add analysis output for free parameters in project * Fix category_entry_name handling to ensure it defaults to an empty string * Update default values in ExperimentType to use enum values directly * Update functional tests following API changes * Update unit tests following new API * Refactor sample model and experiment addition methods * Enhance Chebyshev and LineSegment classes with improved background handling and id property * Refactor Chebyshev and LineSegment background tests to use mock parent data structure * Enhance Analysis and Fitter classes to update categories during fitting process * Refactor CategoryCollection to optimize child instance creation logic * Refactor test_fit_neutron_pd_cwl_hs to improve background data handling and enhance project setup * Refactor sample model addition in tutorials to use updated method * Fix fill_value in LineSegmentBackground interpolation to use correct y values * Update category handling in Fitter * Refactor time-of-flight to follow new API * More fixes of the functional tests * Refactor test_joint_fit * More refactoring in fitting tests * More refactoring of the tutorials to follow new API * Fix formatting * Update unit tests to follow new API * Refactor CIF string handling in Experiment and SampleModel factories * Refactor package structure by removing obsolete files and adding new data modules * Add '_pd_proc.intensity_norm' and '_pd_proc.intensity_norm_su' to CIF handler * Add integration tests for reading CIF files from Scipp * Rename functional tests to integration tests * Add SPDX license headers to test_scipp-cif.py * Extend tutorial section in docs * Refactor test for reading CIF files from Scipp * Bump deps * Pin pdffit2 to be <1.5.2 due to the issues or changed API in the latest versions * Add temporary workaround for pdffit2 installation in CI workflow * Update dependencies in pixi.toml and pyproject.toml for pdffit2 compatibility * Remove temporary workaround for pdffit2 installation in CI workflow * Add id-based data download API; update tutorials * Adopts download_data in tests; prunes local data * Excludes TOF regions in neutron PD fit test * Removes repo download util; updates index hash * Updates tutorial to new data download API * Adapts tests to index-based download_data * Uses returned data paths instead of hardcoded ones * Debugging (3089,) (3098,) (3089,) * Runs script tests serially (temporarily?) * More debugging * Runs tutorial scripts in isolated subprocesses * Streamlines tutorial tests in CI workflow * Justifies subprocess use in tests; adds noqa tags --- .github/workflows/coverage.yaml | 20 +- .github/workflows/pypi-test.yaml | 4 +- .github/workflows/test.yaml | 14 +- .pre-commit-config.yaml | 60 +- DEVELOPMENT.md | 4 +- docs/architecture/package-structure-full.md | 41 +- docs/architecture/package-structure-short.md | 21 +- docs/mkdocs.yml | 3 +- docs/tutorials/index.md | 18 +- out.prof | Bin 0 -> 2237016 bytes pixi.lock | 4221 +++---- pixi.toml | 19 +- pytest.ini | 2 +- src/easydiffraction/__init__.py | 4 +- src/easydiffraction/analysis/analysis.py | 58 +- src/easydiffraction/analysis/calculation.py | 67 - .../analysis/calculators/base.py | 102 +- .../analysis/calculators/crysfml.py | 6 +- .../analysis/calculators/cryspy.py | 9 +- .../analysis/calculators/pdffit.py | 4 +- .../analysis/categories/constraints.py | 9 + .../analysis/fit_helpers/metrics.py | 14 +- .../analysis/fit_helpers/reporting.py | 2 +- src/easydiffraction/analysis/fitting.py | 41 +- src/easydiffraction/core/category.py | 41 +- src/easydiffraction/core/datablock.py | 50 +- src/easydiffraction/core/guard.py | 9 +- src/easydiffraction/core/parameters.py | 60 +- .../experiments/categories/background/base.py | 13 - .../categories/background/chebyshev.py | 70 +- .../categories/background/line_segment.py | 83 +- .../experiments/categories/data/bragg_pd.py | 440 + .../experiments/categories/data/bragg_sc.py | 96 + .../experiments/categories/data/factory.py | 76 + .../experiments/categories/data/total.py | 267 + .../categories/excluded_regions.py | 78 +- .../experiments/categories/experiment_type.py | 16 +- .../experiments/categories/linked_phases.py | 4 +- .../experiments/categories/peak/cwl_mixins.py | 1 + .../experiments/datastore/__init__.py | 2 - .../experiments/datastore/base.py | 79 - .../experiments/datastore/factory.py | 70 - .../experiments/datastore/pd.py | 63 - .../experiments/datastore/sc.py | 46 - .../experiments/experiment/base.py | 96 +- .../experiments/experiment/bragg_pd.py | 15 +- .../experiments/experiment/enums.py | 1 + .../experiments/experiment/factory.py | 161 +- .../experiments/experiment/total_pd.py | 6 +- .../experiments/experiments.py | 171 +- src/easydiffraction/io/cif/parse.py | 34 + src/easydiffraction/io/cif/serialize.py | 260 +- src/easydiffraction/project/project.py | 90 +- .../sample_models/categories/atom_sites.py | 41 +- .../sample_models/categories/cell.py | 31 + .../sample_models/sample_model/base.py | 2 +- .../sample_models/sample_model/factory.py | 175 +- .../sample_models/sample_models.py | 73 +- src/easydiffraction/utils/utils.py | 140 +- .../test_pair-distribution-function.py | 42 +- ..._powder-diffraction_constant-wavelength.py | 193 +- .../test_powder-diffraction_joint-fit.py | 173 +- .../test_powder-diffraction_multiphase.py | 38 +- .../test_powder-diffraction_time-of-flight.py | 57 +- .../scipp-analysis/dream/test_scipp-cif.py | 74 + .../analysis/calculators/test_base.py | 53 - .../analysis/calculators/test_pdffit.py | 4 +- .../analysis/categories/test_aliases.py | 2 +- .../analysis/categories/test_constraints.py | 2 +- .../categories/test_joint_fit_experiments.py | 2 +- .../analysis/fit_helpers/test_metrics.py | 12 +- .../analysis/test_calculation.py | 39 - .../easydiffraction/analysis/test_fitting.py | 2 +- .../easydiffraction/core/test_category.py | 4 +- .../easydiffraction/core/test_datablock.py | 4 +- .../easydiffraction/core/test_parameters.py | 2 +- .../easydiffraction/core/test_singletons.py | 33 - .../categories/background/test_base.py | 5 +- .../categories/background/test_chebyshev.py | 21 +- .../background/test_line_segment.py | 26 +- .../categories/test_excluded_regions.py | 27 +- .../categories/test_linked_phases.py | 2 +- .../experiments/datastore/test_base.py | 9 - .../experiments/datastore/test_factory.py | 24 - .../experiments/datastore/test_pd.py | 9 - .../experiments/datastore/test_sc.py | 9 - .../experiments/experiment/test_bragg_pd.py | 9 +- .../experiments/experiment/test_total_pd.py | 6 +- .../experiments/test_experiments.py | 4 +- .../easydiffraction/io/cif/test_serialize.py | 6 +- .../io/cif/test_serialize_more.py | 55 +- .../project/test_project_d_spacing.py | 102 - .../categories/test_atom_sites.py | 3 +- .../sample_models/test_sample_models.py | 10 +- tests/unit/easydiffraction/test___init__.py | 31 +- tmp/_gemmi.py | 613 + tmp/_read_cif.py | 184 + tmp/_smart.py | 162 + tmp/data/hrpt.cif | 3140 ++++++ tmp/data/large_structure.cif | 5025 +++++++++ tmp/data/lbco.cif | 2 +- .../data => tmp/data_old}/mcstas_lbco-si.xys | 0 .../data_old}/mcstas_lbco-si_up-to-108k.xys | 0 .../powder_reduced_Si_2large_bank.xye | 0 .../data => tmp/data_old}/reduced_LBCO.xye | 0 .../data => tmp/data_old}/reduced_Si.xye | 0 .../data => tmp/data_old}/wish_ncaf.xye | 0 .../data => tmp/data_old}/wish_ncaf_2_9.xye | 0 .../data => tmp/data_old}/wish_ncaf_4_7.xye | 0 .../data => tmp/data_old}/wish_ncaf_4_7.xys | 0 .../data => tmp/data_old}/wish_ncaf_5_6.xye | 0 .../data => tmp/data_old}/wish_ybcfo_5_6.xye | 0 tmp/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py | 120 - tools/test_scripts.py | 45 +- ...vanced_joint-fit_pd-neut-xray-cwl_PbSO4.py | 66 +- .../basic_single-fit_pd-neut-cwl_LBCO-HRPT.py | 57 +- .../cryst-struct_pd-neut-cwl_CoSiO4-D20.py | 90 +- tutorials/cryst-struct_pd-neut-cwl_HS-HRPT.py | 64 +- tutorials/cryst-struct_pd-neut-tof_Si-SEPD.py | 26 +- ...-struct_pd-neut-tof_multidata_NCAF-WISH.py | 178 +- ...t_pd-neut-tof_multiphase-LBCO-Si_McStas.py | 64 +- ..._9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr | 5037 --------- tutorials/data/NaCl.gr | 5028 --------- tutorials/data/co2sio4_d20.xye | 1418 --- tutorials/data/d1a_pbso4.dat | 1801 --- tutorials/data/d1a_pbso4_first-half.dat | 1800 --- tutorials/data/d1a_pbso4_second-half.dat | 1110 -- tutorials/data/hrpt_hs.xye | 3220 ------ tutorials/data/hrpt_lbco.xye | 3099 ------ tutorials/data/lab_pbso4.dat | 3601 ------ tutorials/data/lbco.cif | 36 - tutorials/data/mcstas_lbco-si.xye | 1002 -- tutorials/data/ni-q27r100-neutron_from-2.gr | 9853 ----------------- tutorials/data/sepd_si.xye | 5600 ---------- tutorials/data/wish_ncaf_5_6.xys | 4121 ------- ...school-2025_analysis-powder-diffraction.py | 85 +- tutorials/pdf_pd-neut-cwl_Ni.py | 18 +- tutorials/pdf_pd-neut-tof_Si-NOMAD.py | 19 +- tutorials/pdf_pd-xray_NaCl.py | 29 +- .../quick_from-cif_pd-neut-cwl_LBCO-HRPT.py | 73 + ... quick_from-code_pd-neut-cwl_LBCO-HRPT.py} | 61 +- 141 files changed, 14596 insertions(+), 50948 deletions(-) create mode 100644 out.prof delete mode 100644 src/easydiffraction/analysis/calculation.py create mode 100644 src/easydiffraction/experiments/categories/data/bragg_pd.py create mode 100644 src/easydiffraction/experiments/categories/data/bragg_sc.py create mode 100644 src/easydiffraction/experiments/categories/data/factory.py create mode 100644 src/easydiffraction/experiments/categories/data/total.py delete mode 100644 src/easydiffraction/experiments/datastore/__init__.py delete mode 100644 src/easydiffraction/experiments/datastore/base.py delete mode 100644 src/easydiffraction/experiments/datastore/factory.py delete mode 100644 src/easydiffraction/experiments/datastore/pd.py delete mode 100644 src/easydiffraction/experiments/datastore/sc.py create mode 100644 src/easydiffraction/io/cif/parse.py rename tests/{functional => integration}/fitting/test_pair-distribution-function.py (81%) rename tests/{functional => integration}/fitting/test_powder-diffraction_constant-wavelength.py (70%) rename tests/{functional => integration}/fitting/test_powder-diffraction_joint-fit.py (66%) rename tests/{functional => integration}/fitting/test_powder-diffraction_multiphase.py (80%) rename tests/{functional => integration}/fitting/test_powder-diffraction_time-of-flight.py (81%) create mode 100644 tests/integration/scipp-analysis/dream/test_scipp-cif.py delete mode 100644 tests/unit/easydiffraction/analysis/test_calculation.py delete mode 100644 tests/unit/easydiffraction/experiments/datastore/test_base.py delete mode 100644 tests/unit/easydiffraction/experiments/datastore/test_factory.py delete mode 100644 tests/unit/easydiffraction/experiments/datastore/test_pd.py delete mode 100644 tests/unit/easydiffraction/experiments/datastore/test_sc.py delete mode 100644 tests/unit/easydiffraction/project/test_project_d_spacing.py create mode 100644 tmp/_gemmi.py create mode 100644 tmp/_read_cif.py create mode 100644 tmp/_smart.py create mode 100644 tmp/data/hrpt.cif create mode 100644 tmp/data/large_structure.cif rename {tutorials/data => tmp/data_old}/mcstas_lbco-si.xys (100%) rename {tutorials/data => tmp/data_old}/mcstas_lbco-si_up-to-108k.xys (100%) rename {tutorials/data => tmp/data_old}/powder_reduced_Si_2large_bank.xye (100%) rename {tutorials/data => tmp/data_old}/reduced_LBCO.xye (100%) rename {tutorials/data => tmp/data_old}/reduced_Si.xye (100%) rename {tutorials/data => tmp/data_old}/wish_ncaf.xye (100%) rename {tutorials/data => tmp/data_old}/wish_ncaf_2_9.xye (100%) rename {tutorials/data => tmp/data_old}/wish_ncaf_4_7.xye (100%) rename {tutorials/data => tmp/data_old}/wish_ncaf_4_7.xys (100%) rename {tutorials/data => tmp/data_old}/wish_ncaf_5_6.xye (100%) rename {tutorials/data => tmp/data_old}/wish_ybcfo_5_6.xye (100%) delete mode 100644 tmp/quick_single-fit_pd-neut-cwl_LBCO-HRPT.py delete mode 100644 tutorials/data/NOM_9999_Si_640g_PAC_50_ff_ftfrgr_up-to-50.gr delete mode 100644 tutorials/data/NaCl.gr delete mode 100644 tutorials/data/co2sio4_d20.xye delete mode 100644 tutorials/data/d1a_pbso4.dat delete mode 100644 tutorials/data/d1a_pbso4_first-half.dat delete mode 100644 tutorials/data/d1a_pbso4_second-half.dat delete mode 100644 tutorials/data/hrpt_hs.xye delete mode 100644 tutorials/data/hrpt_lbco.xye delete mode 100644 tutorials/data/lab_pbso4.dat delete mode 100644 tutorials/data/lbco.cif delete mode 100644 tutorials/data/mcstas_lbco-si.xye delete mode 100644 tutorials/data/ni-q27r100-neutron_from-2.gr delete mode 100644 tutorials/data/sepd_si.xye delete mode 100644 tutorials/data/wish_ncaf_5_6.xys create mode 100644 tutorials/quick_from-cif_pd-neut-cwl_LBCO-HRPT.py rename tutorials/{quick_single-fit_pd-neut-cwl_LBCO-HRPT.py => quick_from-code_pd-neut-cwl_LBCO-HRPT.py} (65%) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 35334eb5..64be8068 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -86,8 +86,8 @@ jobs: verbose: true token: ${{ secrets.CODECOV_TOKEN }} - # Job 2: Run functional tests with coverage and upload to Codecov - func-tests-coverage: + # Job 2: Run integration tests with coverage and upload to Codecov + integration-tests-coverage: runs-on: ubuntu-latest steps: @@ -108,23 +108,25 @@ jobs: shell: bash run: pixi run dev - - name: Run functional tests with coverage - run: pixi run func-tests-coverage --cov-report=xml:coverage-func.xml + - name: Run integration tests with coverage + run: + pixi run integration-tests-coverage + --cov-report=xml:coverage-integration.xml - - name: Upload functional tests coverage to Codecov + - name: Upload integration tests coverage to Codecov if: ${{ !cancelled() }} uses: codecov/codecov-action@v5 with: - name: func-tests-job - flags: functional - files: ./coverage-func.xml + 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: - needs: [docstring-coverage, unit-tests-coverage, func-tests-coverage] # depend on the previous jobs + needs: [docstring-coverage, unit-tests-coverage, integration-tests-coverage] # depend on the previous jobs runs-on: ubuntu-latest diff --git a/.github/workflows/pypi-test.yaml b/.github/workflows/pypi-test.yaml index 8cb5323c..38f439e5 100644 --- a/.github/workflows/pypi-test.yaml +++ b/.github/workflows/pypi-test.yaml @@ -61,8 +61,8 @@ jobs: - name: Run unit tests to verify the installation run: pixi run unit-tests - - name: Run functional tests to verify the installation - run: pixi run func-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 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ec185ee8..d84240c0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -56,9 +56,9 @@ jobs: pytest-marks: ${{ steps.set-mark.outputs.pytest_marks }} steps: - # Determine if functional tests should be run fully or only the fast ones + # 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 functional tests + - name: Set mark for integration tests id: set-mark run: | if [[ "${{ env.CI_BRANCH }}" == "master" || "${{ env.CI_BRANCH }}" == "develop" ]]; then @@ -115,12 +115,13 @@ jobs: pixi run --environment $env unit-tests done - - name: Run functional tests ${{ needs.env-prepare.outputs.pytest-marks }} + - 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 func-tests "${{ needs.env-prepare.outputs.pytest-marks }}" + 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 @@ -226,12 +227,13 @@ jobs: pixi run --environment $env unit-tests done - - name: Run functional tests ${{ needs.env-prepare.outputs.pytest-marks }} + - 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 func-tests "${{ needs.env-prepare.outputs.pytest-marks }}" + pixi run --environment $env integration-tests "${{ needs.env-prepare.outputs.pytest-marks }}" done # Job 4: Trigger dashboard build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3d471cd..6c3aca27 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,33 +11,33 @@ repos: pass_filenames: false stages: [pre-commit] - - id: pixi-py-lint-check-staged - name: pixi run py-lint-check-staged - entry: pixi run py-lint-check-pre - language: system - pass_filenames: false - stages: [pre-commit] + #- id: pixi-py-lint-check-staged + # name: pixi run py-lint-check-staged + # entry: pixi run py-lint-check-pre + # language: system + # pass_filenames: false + # stages: [pre-commit] - - id: pixi-py-format-check-staged - name: pixi run py-format-check-staged - entry: pixi run py-format-check-pre - language: system - pass_filenames: false - stages: [pre-commit] + #- id: pixi-py-format-check-staged + # name: pixi run py-format-check-staged + # entry: pixi run py-format-check-pre + # language: system + # pass_filenames: false + # stages: [pre-commit] - - id: pixi-nonpy-format-check-modified - name: pixi run nonpy-format-check-modified - entry: pixi run nonpy-format-check-modified - language: system - pass_filenames: false - stages: [pre-commit] + #- id: pixi-nonpy-format-check-modified + # name: pixi run nonpy-format-check-modified + # entry: pixi run nonpy-format-check-modified + # language: system + # pass_filenames: false + # stages: [pre-commit] - - id: pixi-docs-format-check - name: pixi run docs-format-check - entry: pixi run docs-format-check - language: system - pass_filenames: false - stages: [pre-commit] + #- id: pixi-docs-format-check + # name: pixi run docs-format-check + # entry: pixi run docs-format-check + # language: system + # pass_filenames: false + # stages: [pre-commit] # ---------------- # Pre-push checks @@ -49,9 +49,9 @@ repos: pass_filenames: false stages: [pre-push] - - id: pixi-unit-tests - name: pixi run unit-tests - entry: pixi run unit-tests - language: system - pass_filenames: false - stages: [pre-push] + #- id: pixi-unit-tests + # name: pixi run unit-tests + # entry: pixi run unit-tests + # language: system + # pass_filenames: false + # stages: [pre-push] diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 9d099afd..2acffeb3 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -75,9 +75,9 @@ This is an example of a workflow that describes the development process. ```bash pixi run unit-tests ``` -- Run functional tests +- Run integration tests ```bash - pixi run func-tests + pixi run integration-tests ``` - Test tutorials as python scripts ```bash diff --git a/docs/architecture/package-structure-full.md b/docs/architecture/package-structure-full.md index 1237c97b..e24eadab 100644 --- a/docs/architecture/package-structure-full.md +++ b/docs/architecture/package-structure-full.md @@ -47,8 +47,6 @@ │ ├── 📄 __init__.py │ ├── 📄 analysis.py │ │ └── 🏷️ class Analysis -│ ├── 📄 calculation.py -│ │ └── 🏷️ class Calculator │ └── 📄 fitting.py │ └── 🏷️ class Fitter ├── 📁 core @@ -141,6 +139,24 @@ │ │ │ └── 📄 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 @@ -185,16 +201,6 @@ │ │ └── 📄 linked_phases.py │ │ ├── 🏷️ class LinkedPhase │ │ └── 🏷️ class LinkedPhases -│ ├── 📁 datastore -│ │ ├── 📄 __init__.py -│ │ ├── 📄 base.py -│ │ │ └── 🏷️ class DatastoreBase -│ │ ├── 📄 factory.py -│ │ │ └── 🏷️ class DatastoreFactory -│ │ ├── 📄 pd.py -│ │ │ └── 🏷️ class PdDatastore -│ │ └── 📄 sc.py -│ │ └── 🏷️ class ScDatastore │ ├── 📁 experiment │ │ ├── 📄 __init__.py │ │ ├── 📄 base.py @@ -220,10 +226,13 @@ │ └── 📄 experiments.py │ └── 🏷️ class Experiments ├── 📁 io -│ └── 📁 cif -│ ├── 📄 handler.py -│ │ └── 🏷️ class CifHandler -│ └── 📄 serialize.py +│ ├── 📁 cif +│ │ ├── 📄 __init__.py +│ │ ├── 📄 handler.py +│ │ │ └── 🏷️ class CifHandler +│ │ ├── 📄 parse.py +│ │ └── 📄 serialize.py +│ └── 📄 __init__.py ├── 📁 project │ ├── 📄 __init__.py │ ├── 📄 project.py diff --git a/docs/architecture/package-structure-short.md b/docs/architecture/package-structure-short.md index 4158458e..7f9d5af0 100644 --- a/docs/architecture/package-structure-short.md +++ b/docs/architecture/package-structure-short.md @@ -28,7 +28,6 @@ │ │ └── 📄 lmfit.py │ ├── 📄 __init__.py │ ├── 📄 analysis.py -│ ├── 📄 calculation.py │ └── 📄 fitting.py ├── 📁 core │ ├── 📄 __init__.py @@ -71,6 +70,11 @@ │ │ │ ├── 📄 enums.py │ │ │ ├── 📄 factory.py │ │ │ └── 📄 line_segment.py +│ │ ├── 📁 data +│ │ │ ├── 📄 bragg_pd.py +│ │ │ ├── 📄 bragg_sc.py +│ │ │ ├── 📄 factory.py +│ │ │ └── 📄 total.py │ │ ├── 📁 instrument │ │ │ ├── 📄 __init__.py │ │ │ ├── 📄 base.py @@ -91,12 +95,6 @@ │ │ ├── 📄 excluded_regions.py │ │ ├── 📄 experiment_type.py │ │ └── 📄 linked_phases.py -│ ├── 📁 datastore -│ │ ├── 📄 __init__.py -│ │ ├── 📄 base.py -│ │ ├── 📄 factory.py -│ │ ├── 📄 pd.py -│ │ └── 📄 sc.py │ ├── 📁 experiment │ │ ├── 📄 __init__.py │ │ ├── 📄 base.py @@ -109,9 +107,12 @@ │ ├── 📄 __init__.py │ └── 📄 experiments.py ├── 📁 io -│ └── 📁 cif -│ ├── 📄 handler.py -│ └── 📄 serialize.py +│ ├── 📁 cif +│ │ ├── 📄 __init__.py +│ │ ├── 📄 handler.py +│ │ ├── 📄 parse.py +│ │ └── 📄 serialize.py +│ └── 📄 __init__.py ├── 📁 project │ ├── 📄 __init__.py │ ├── 📄 project.py diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fdea2145..94a2263a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -69,7 +69,8 @@ nav: - Tutorials: - Tutorials: tutorials/index.md - Getting Started: - - LBCO quick: tutorials/quick_single-fit_pd-neut-cwl_LBCO-HRPT.ipynb + - LBCO quick CIF: tutorials/quick_from-cif_pd-neut-cwl_LBCO-HRPT.ipynb + - LBCO quick code: tutorials/quick_from-code_pd-neut-cwl_LBCO-HRPT.ipynb - LBCO basic: tutorials/basic_single-fit_pd-neut-cwl_LBCO-HRPT.ipynb - PbSO4 advanced: tutorials/advanced_joint-fit_pd-neut-xray-cwl_PbSO4.ipynb - Standard Diffraction: diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index a10891b9..5ce91a38 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -17,11 +17,18 @@ The tutorials are organized into the following categories. ## Getting Started -- [LBCO `quick`](quick_single-fit_pd-neut-cwl_LBCO-HRPT.ipynb) – A minimal +- [LBCO `quick` CIF](quick_from-cif_pd-neut-cwl_LBCO-HRPT.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 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. + 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. Data collected from constant + wavelength neutron powder diffraction at HRPT at PSI. +- [LBCO `quick` `code`](quick_from-code_pd-neut-cwl_LBCO-HRPT.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 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`](basic_single-fit_pd-neut-cwl_LBCO-HRPT.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 @@ -39,6 +46,9 @@ The tutorials are organized into the following categories. ## Standard Diffraction +- [Co2SiO4 `pd-neut-cwl`](cryst-struct_pd-neut-cwl_HS-HRPT.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`](cryst-struct_pd-neut-cwl_HS-HRPT.ipynb) – Demonstrates a Rietveld refinement of the HS crystal structure using constant wavelength neutron powder diffraction data from HRPT at PSI. diff --git a/out.prof b/out.prof new file mode 100644 index 0000000000000000000000000000000000000000..dec44a3e24f662011fd4f2cc20846a636432810a GIT binary patch literal 2237016 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

SaK#8pn2NMT}jX-$_s92toId*BH$NG4 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�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�q27d_CN6vd40xJ$4eE{-JbkSI|3|(E->Kkm$|;v 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 +Ho?}d?cdT4@ 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Π`*$+U-3P=Z*npGUrTocIzB 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