From 8cd4f54edbeb9e42b4abf0e14d9d4b44131768e7 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 22 Apr 2026 15:21:45 +0200 Subject: [PATCH 01/19] FIrst draft of ParameterAnalysis --- docs/docs/tutorials/fit_parameters_test.ipynb | 631 ++++++++++++++++++ .../analysis/parameter_analysis.py | 406 +++++++++++ src/easydynamics/settings/__init__.py | 4 + .../parameter_analysis_fit_settings.py | 131 ++++ .../convolution_width_thresholds.ipynb | 18 +- 5 files changed, 1176 insertions(+), 14 deletions(-) create mode 100644 docs/docs/tutorials/fit_parameters_test.ipynb create mode 100644 src/easydynamics/analysis/parameter_analysis.py create mode 100644 src/easydynamics/settings/parameter_analysis_fit_settings.py diff --git a/docs/docs/tutorials/fit_parameters_test.ipynb b/docs/docs/tutorials/fit_parameters_test.ipynb new file mode 100644 index 00000000..413ce036 --- /dev/null +++ b/docs/docs/tutorials/fit_parameters_test.ipynb @@ -0,0 +1,631 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8643b10c", + "metadata": {}, + "source": [ + "# Brownian Diffusion\n", + "We here show how to set up an Analysis object and use it to first fit an artificial vanadium measurement to obtain the resolution. Next, we use the fitted resolution to fit an artificial measurement of a model with diffusion and some elastic scattering. \n", + "\n", + "We extract and plot the relevant parameters. Finally, we show how to fit directly to the diffusion model.\n", + "\n", + "In the near future, it will be possible to fit the width and area of the Lorentzian to the diffusion model as well." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bca91d3c", + "metadata": {}, + "outputs": [], + "source": [ + "# Imports\n", + "import pooch\n", + "\n", + "from easydynamics.analysis.analysis import Analysis\n", + "from easydynamics.experiment import Experiment\n", + "from easydynamics.sample_model import BrownianTranslationalDiffusion\n", + "from easydynamics.sample_model import ComponentCollection\n", + "from easydynamics.sample_model import DeltaFunction\n", + "from easydynamics.sample_model import Gaussian\n", + "from easydynamics.sample_model import Lorentzian\n", + "from easydynamics.sample_model import Polynomial\n", + "from easydynamics.sample_model.background_model import BackgroundModel\n", + "from easydynamics.sample_model.instrument_model import InstrumentModel\n", + "from easydynamics.sample_model.resolution_model import ResolutionModel\n", + "from easydynamics.sample_model.sample_model import SampleModel\n", + "\n", + "# Make the plots interactive\n", + "%matplotlib widget" + ] + }, + { + "cell_type": "markdown", + "id": "4c8e97b7", + "metadata": {}, + "source": [ + "We first create an `Experiment` object to contain the data. The data must either be a hdf5 file or a scipp.DataArray; in both cases it must have coordinates `Q` and `energy`. We here use Pooch to download an example vanadium data set.\n", + "\n", + "The data can be rebinned if needed, but we will show how to do that in a different tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8deca9b6", + "metadata": {}, + "outputs": [], + "source": [ + "# Load the vanadium data\n", + "vanadium_experiment = Experiment('Vanadium')\n", + "\n", + "file_path = pooch.retrieve(\n", + " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/vanadium_data_example.h5',\n", + " known_hash='16cc1b327c303feeb88fb9dda5390dc4880b62396b1793f98c6fef0b27c7b873',\n", + ")\n", + "\n", + "\n", + "vanadium_experiment.load_hdf5(filename=file_path)" + ] + }, + { + "cell_type": "markdown", + "id": "7daa3f64", + "metadata": {}, + "source": [ + "We can visualize the data in multiple ways, relying on plopp: https://scipp.github.io/plopp/\n", + "\n", + "We here show two ways to look at the data: as a 2d colormap with intensity as function of `Q` and `energy`, and as a slicer with intensity as function of `energy` for various `Q`.\n", + "\n", + "If you want $Q$ on the x axis, then set `transpose_axes=True`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fbd31297", + "metadata": {}, + "outputs": [], + "source": [ + "vanadium_experiment.plot_data(slicer=False, transpose_axes=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4153ba52", + "metadata": {}, + "outputs": [], + "source": [ + "vanadium_experiment.plot_data(slicer=True)" + ] + }, + { + "cell_type": "markdown", + "id": "6c87b01c", + "metadata": {}, + "source": [ + "We now want to fit the vanadium data to determine our resolution. The scattering from vanadium is almost exclusively incoherent elastic, so we model it as a delta function. We do this by creating a `SampleModel` and adding a `DeltaFunction` component to it. The component acts as a template and gets copied to every `Q` when we attach the `SampleModel` to our `Analysis` object. Let's create the `SampleModel`.\n", + "\n", + "We do not give the `DeltaFunction` a `center` value. In this case, the center will be fixed at 0 energy transfer. We set the start value of the area to 1." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6762faba", + "metadata": {}, + "outputs": [], + "source": [ + "delta_function = DeltaFunction(display_name='DeltaFunction', area=1)\n", + "sample_model = SampleModel(components=delta_function)" + ] + }, + { + "cell_type": "markdown", + "id": "dc82774e", + "metadata": {}, + "source": [ + "We now want to define our resolution function. We will here model it as a Gaussian. We create a `ComponentCollection` and append the `Gaussian` to it. We can add as many components to our resolution as we like; sometimes you need several Gaussians and other functions to accurately describe the resolution.\n", + "\n", + "We fix the area of the resolution to have value 1. If we did not do this, we would fit both the area of the delta function and of the resolution Gaussian, and the fit would never converge.\n", + "\n", + "We finally insert the components in a `ResolutionModel`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fdb7f19", + "metadata": {}, + "outputs": [], + "source": [ + "resolution_components = ComponentCollection()\n", + "res_gauss = Gaussian(width=0.1, area=1, display_name='Res. Gauss')\n", + "res_gauss.area.fixed = True\n", + "resolution_components.append_component(res_gauss)\n", + "resolution_model = ResolutionModel(components=resolution_components)" + ] + }, + { + "cell_type": "markdown", + "id": "088ac17d", + "metadata": {}, + "source": [ + "The background intensity was not 0, so we also create a background model. We use a `Polynomial` with a single coefficient, i.e. a flat background. We here show how to create the `BackgroundModel` and add the background in a single line. We could of course also add it like we did for the `SampleModel` or first create a `ComponentCollection` like we did for the `ResolutionModel`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ec6836f", + "metadata": {}, + "outputs": [], + "source": [ + "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" + ] + }, + { + "cell_type": "markdown", + "id": "eae3d14b", + "metadata": {}, + "source": [ + "We combine the resolution abd background model into an `InstrumentModel`. This model also contains a fittable energy offset to account for instrument misalignment. All components are centered at this energy offset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ad79a75", + "metadata": {}, + "outputs": [], + "source": [ + "instrument_model = InstrumentModel(\n", + " resolution_model=resolution_model,\n", + " background_model=background_model,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a99be7c1", + "metadata": {}, + "source": [ + "We are now ready to collect everything in an analysis object. We give it a display name, the experiment, the sample model and the instrument model. It will then automatically generate a model for each `Q` using the templates given in the `SampleModel`, `ResolutionModel` and `BackgroundModel`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b98d63dc", + "metadata": {}, + "outputs": [], + "source": [ + "vanadium_analysis = Analysis(\n", + " display_name='Vanadium Full Analysis',\n", + " experiment=vanadium_experiment,\n", + " sample_model=sample_model,\n", + " instrument_model=instrument_model,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a81248a4", + "metadata": {}, + "source": [ + "Let us first fit a single Q index and plot the data and model to see how it looks. For this, we use the `independent` fit method and choose an arbitrary Q index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aec75b7f", + "metadata": {}, + "outputs": [], + "source": [ + "fit_result_independent_single_Q = vanadium_analysis.fit(fit_method='independent', Q_index=5)\n", + "vanadium_analysis.plot_data_and_model(Q_index=5)" + ] + }, + { + "cell_type": "markdown", + "id": "c3fe553b", + "metadata": {}, + "source": [ + "The fit looks good, so let us fit all Q indices independently and plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e98e3d65", + "metadata": {}, + "outputs": [], + "source": [ + "fit_result_independent_all_Q = vanadium_analysis.fit(fit_method='independent')\n", + "vanadium_analysis.plot_data_and_model()" + ] + }, + { + "cell_type": "markdown", + "id": "12936c9d", + "metadata": {}, + "source": [ + "It can be nice to inspect the fit parameters, and sometimes continue working with them. To do this, we can convert them to a scipp dataset.\n", + "\n", + "We can also plot the parameters as a function of `Q` using the `plot_parameters` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "133e682e", + "metadata": {}, + "outputs": [], + "source": [ + "# Inspect the Parameters as a scipp Dataset\n", + "vanadium_analysis.parameters_to_dataset()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dfacdf24", + "metadata": {}, + "outputs": [], + "source": [ + "# Plot some of fitted parameters as a function of Q\n", + "vanadium_analysis.plot_parameters(names=['DeltaFunction area'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6f9f316", + "metadata": {}, + "outputs": [], + "source": [ + "vanadium_analysis.plot_parameters(names=['Res. Gauss width'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "572664a0", + "metadata": {}, + "outputs": [], + "source": [ + "vanadium_analysis.plot_parameters(names=['energy_offset'])" + ] + }, + { + "cell_type": "markdown", + "id": "ea2e5996", + "metadata": {}, + "source": [ + "We are now happy with our resolution function and can start looking at the data we want to fit. We first load and inspect the data in the same way as before. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3609e6c1", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_experiment = Experiment('Diffusion')\n", + "\n", + "file_path = pooch.retrieve(\n", + " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/diffusion_data_example.h5',\n", + " known_hash='5fe846b19aacbda8b8b936eb2e5310d025dc56c25b0b353521e7d6b921f229ab',\n", + ")\n", + "\n", + "diffusion_experiment.load_hdf5(filename=file_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba9b14cc", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_experiment.plot_data(slicer=True, ymax=4)" + ] + }, + { + "cell_type": "markdown", + "id": "6da4f9e2", + "metadata": {}, + "source": [ + "The data seems to have a sharp elastic peak, a quasielastic peak and a non-zero background. We set up the corresponding `SampleModel` and `BackgroundModel` just like before." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e685909a", + "metadata": {}, + "outputs": [], + "source": [ + "delta_function = DeltaFunction(display_name='DeltaFunction', area=0.2)\n", + "lorentzian = Lorentzian(display_name='Lorentzian', area=0.5, width=0.3)\n", + "component_collection = ComponentCollection(\n", + " components=[delta_function, lorentzian],\n", + ")\n", + "\n", + "sample_model = SampleModel(\n", + " components=component_collection,\n", + ")\n", + "\n", + "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" + ] + }, + { + "cell_type": "markdown", + "id": "927b8fb5", + "metadata": {}, + "source": [ + "We also create a new instrument_model and attach it to our analysis, giving it the resolution model determined in the vanadium analysis. We further fix all parameters in the resolution model and normalize it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53ecf27e", + "metadata": {}, + "outputs": [], + "source": [ + "instrument_model = InstrumentModel(\n", + " background_model=background_model,\n", + " resolution_model=vanadium_analysis.instrument_model.resolution_model,\n", + ")\n", + "instrument_model.resolution_model.fix_all_parameters()\n", + "instrument_model.normalize_resolution()\n", + "\n", + "diffusion_analysis = Analysis(\n", + " display_name='Diffusion Analysis',\n", + " experiment=diffusion_experiment,\n", + " sample_model=sample_model,\n", + " instrument_model=instrument_model,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b72a710b", + "metadata": {}, + "source": [ + "We don't want to fit our resolution anymore, so we fix all the parameters in it.\n", + "\n", + "`Analysis` handles the convolution of the `sample_model` with the `resolution_model`. The calculation is analytical when possible, and otherwise numerical. For numerical convolution, it will give warnings if the result might be inaccurate due to various numerical errors. In that case, two settings can be varied to improve the result.\n", + "`upsample_factor` improves accuracy for narrow signals. The default is 5.\n", + "`extension_factor` improves accuracy for broad signals. Here, the default is 0.2.\n", + "It is furthermore possible to toggle whether detailed balance correction is normalized or not (see next tutorial)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0fb0c648", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_analysis.convolution_settings.upsample_factor = 6\n", + "diffusion_analysis.convolution_settings.extension_factor = 0.2\n", + "diffusion_analysis.convolution_settings.normalize_detailed_balance = True" + ] + }, + { + "cell_type": "markdown", + "id": "e366a05d", + "metadata": {}, + "source": [ + "Before we start fitting it is a good idea to check how good our start guesses are. We do this by plotting the data and the model using `plot_data_and_model`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c66828eb", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_analysis.plot_data_and_model()" + ] + }, + { + "cell_type": "markdown", + "id": "2a4b7572", + "metadata": {}, + "source": [ + "The start guesses are not perfect, but they look good enough to start fitting. Let's give it a try!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9678eede", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_analysis.fit(fit_method='independent')\n", + "diffusion_analysis.plot_data_and_model()" + ] + }, + { + "cell_type": "markdown", + "id": "45daa848", + "metadata": {}, + "source": [ + "The fit looks good, so now we want to look at the most interesting fit parameters: the width and area of the Lorentzian. In later versions of EasyDynamics it will be possible to fit them to e.g. a DiffusionModel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df14b5c4", + "metadata": {}, + "outputs": [], + "source": [ + "# Let us look at the most interesting fit parameters\n", + "diffusion_analysis.plot_parameters(names=['Lorentzian width', 'Lorentzian area'])" + ] + }, + { + "cell_type": "markdown", + "id": "eb226c8f", + "metadata": {}, + "source": [ + "There is a clear trend: the area is more or less constant, while the width seems to increase with `Q^2`. We therefore try and fit a Brownian translational diffusion model to the data. In this model, the scattering is given by\n", + "$$\n", + "I(Q,E) = S \\frac{\\Gamma(Q)}{\\Gamma(Q)^2 + E^2},\n", + "$$\n", + "where $\\Gamma(Q) = D Q^2$ and $D$ is the diffusion coefficient. $S$ is an overall scale.\n", + "\n", + "In addition to this diffusion model, there is still the elastic incoherent scattering.\n", + "\n", + "We create a new `SampleModel` which as a `DeltaFunction` component for the elastic incoherent scattering and a `BrownianTranslationalDiffusion` diffusion model to describe the rest. We also create a new `BackgroundModel` and `InstrumentModel`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b6bba77", + "metadata": {}, + "outputs": [], + "source": [ + "delta_function = DeltaFunction(display_name='DeltaFunction', area=0.2)\n", + "component_collection = ComponentCollection(\n", + " components=[delta_function],\n", + ")\n", + "diffusion_model = BrownianTranslationalDiffusion(\n", + " display_name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", + ")\n", + "\n", + "sample_model = SampleModel(\n", + " components=component_collection,\n", + " diffusion_models=diffusion_model,\n", + ")\n", + "\n", + "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48370156", + "metadata": {}, + "outputs": [], + "source": [ + "instrument_model = InstrumentModel(\n", + " background_model=background_model,\n", + " resolution_model=vanadium_analysis.instrument_model.resolution_model,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8bb4abe9", + "metadata": {}, + "source": [ + "We attach all our models to a new `Analysis` object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3efb9b53", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_model_analysis = Analysis(\n", + " display_name='Diffusion Full Analysis',\n", + " experiment=diffusion_experiment,\n", + " sample_model=sample_model,\n", + " instrument_model=instrument_model,\n", + ")\n", + "\n", + "diffusion_model_analysis.instrument_model.resolution_model.fix_all_parameters()" + ] + }, + { + "cell_type": "markdown", + "id": "786157c0", + "metadata": {}, + "source": [ + "As always, we first check the start parameters before we fit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "683dd27c", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_model_analysis.plot_data_and_model()" + ] + }, + { + "cell_type": "markdown", + "id": "43e2aa66", + "metadata": {}, + "source": [ + "We can now fit all the data simultaneously to our model. It looks good!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd04d359", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_model_analysis.fit(fit_method='simultaneous')\n", + "diffusion_model_analysis.plot_data_and_model(ymax=2)" + ] + }, + { + "cell_type": "markdown", + "id": "bbeec088", + "metadata": {}, + "source": [ + "It does not make sense to plot the diffusion parameters, but we can display them (with uncertainties) like this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "842c1f01", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_model.get_all_parameters()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "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.14.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py new file mode 100644 index 00000000..8e2d8505 --- /dev/null +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -0,0 +1,406 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +from typing import Any + +import numpy as np +import scipp as sc +from easyscience.fitting.minimizers.utils import FitResults +from easyscience.fitting.multi_fitter import MultiFitter + +from easydynamics.analysis.analysis import Analysis +from easydynamics.base_classes.easydynamics_base import EasyDynamicsBase +from easydynamics.sample_model.component_collection import ComponentCollection +from easydynamics.sample_model.components.model_component import ModelComponent +from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase +from easydynamics.settings.parameter_analysis_fit_settings import ParameterAnalysisFitSettings + + +class ParameterAnalysis(EasyDynamicsBase): + """ + Analysing fitted parameters. + """ + + def __init__( + self, + parameters: sc.Dataset | Analysis | None = None, + parameter_names: str | list[str] | None = None, + fit_functions: ( + ModelComponent + | ComponentCollection + | DiffusionModelBase + | list[ModelComponent | ComponentCollection | DiffusionModelBase] + | None + ) = None, + fit_settings: ParameterAnalysisFitSettings | None = None, + display_name: str | None = 'MyAnalysis', + unique_name: str | None = None, + ) -> None: + """ + Initialize the AnalysisBase. + + Parameters + ---------- + display_name : str | None, default='MyAnalysis' + Display name of the analysis. + unique_name : str | None, default=None + Unique name of the analysis. If None, a unique name is automatically generated. By + default, None. + + Raises + ------ + TypeError + If experiment is not an Experiment or None or if sample_model is not a SampleModel or + None or if instrument_model is not an InstrumentModel or None or if + convolution_settings is not a ConvolutionSettings or None or if + detailed_balance_settings is not a DetailedBalanceSettings or None or if + extra_parameters is not a Parameter, a list of Parameters, or None. + """ + + super().__init__(display_name=display_name, unique_name=unique_name) + + # Check parameters + if parameters is not None and not isinstance(parameters, (sc.Dataset, Analysis)): + raise TypeError('parameters must be an sc.Dataset, an Analysis, or None.') + + if isinstance(parameters, Analysis): + self._parameters = parameters.parameters_to_dataset() + else: + self._parameters = parameters + + # Check fit settings + if fit_settings is not None and not isinstance(fit_settings, ParameterAnalysisFitSettings): + raise TypeError('fit_settings must be a ParameterAnalysisFitSettings or None.') + if fit_settings is None: + fit_settings = ParameterAnalysisFitSettings() + self._fit_settings = fit_settings + + # Check fit_functions + if fit_functions is not None and not isinstance( + fit_functions, + ( + ModelComponent, + ComponentCollection, + DiffusionModelBase, + list, + ), + ): + raise TypeError( + 'fit_functions must be a ModelComponent, a ComponentCollection, a list of ' + 'ModelComponent/ComponentCollection, a DiffusionModelBase, or None.' + ) + + # Make fit_functions a list if it's not already + if isinstance(fit_functions, (ModelComponent, ComponentCollection, DiffusionModelBase)): + fit_functions = [fit_functions] + + for func in fit_functions: + if not isinstance( + func, + (ModelComponent, ComponentCollection, DiffusionModelBase), + ): + raise TypeError( + 'All items in fit_functions list must be a ModelComponent, ' + 'a ComponentCollection, or a DiffusionModelBase.' + ) + + # Check parameter names + if parameter_names is not None and not isinstance(parameter_names, (str, list)): + raise TypeError('parameter_names must be a string, a list of strings, or None.') + # Make parameter_names a list if it's not already + if isinstance(parameter_names, str): + parameter_names = [parameter_names] + + for name in parameter_names: + if not isinstance(name, str): + raise TypeError('All items in parameter_names list must be strings.') + + # Check that parameter_names and fit_functions have the same length + if len(parameter_names) != len(fit_functions): + raise ValueError('parameter_names must have the same length as fit_functions.') + + # Convert fit_functions to a list of callables and expand parameter_names if necessary + fit_function_callables = [] + fit_objects = [] + expanded_parameter_names = [] + if fit_functions is not None: + for name, func in zip(parameter_names, fit_functions, strict=True): + if isinstance(func, DiffusionModelBase): + fit_funcs, fit_objs = self._diffusion_model_to_fit_functions(func) + fit_function_callables.extend(fit_funcs) + fit_objects.extend(fit_objs) + expanded_parameter_names.extend( + self._get_diffusion_model_parameter_names(name) + ) + elif isinstance(func, (ModelComponent, ComponentCollection)): + fit_function_callables.append(self._components_to_fit_function(func)) + fit_objects.append(func) + expanded_parameter_names.append(name) + self._fit_functions = fit_functions + self._fit_function_callables = fit_function_callables + self._fit_objects = fit_objects + self._parameter_names = parameter_names + self._expanded_parameter_names = expanded_parameter_names + + # Check that all names are in the DataSet + if self._parameters is not None: + for name in self._expanded_parameter_names: + if name not in self._parameters: + raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") + + ############# + # Properties + ############# + @property + def fit_settings(self) -> ParameterAnalysisFitSettings: + """ + Get the fit settings for the parameter analysis. + + Returns + ------- + ParameterAnalysisFitSettings + The fit settings for the parameter analysis. + """ + return self._fit_settings + + @fit_settings.setter + def fit_settings(self, value: ParameterAnalysisFitSettings) -> None: + """ + Set the fit settings for the parameter analysis. + + Parameters + ---------- + value : ParameterAnalysisFitSettings + The new fit settings for the parameter analysis. + + Raises + ------ + TypeError + If value is not a ParameterAnalysisFitSettings. + """ + if not isinstance(value, ParameterAnalysisFitSettings): + raise TypeError('fit_settings must be a ParameterAnalysisFitSettings.') + self._fit_settings = value + + ############# + # Other methods + ############# + + def fit(self) -> FitResults: + """ + Fit the parameters using the specified fit functions and settings. + + Returns + ------- + FitResults + The results of the fit + """ + + xs = [] + ys = [] + ws = [] + + for name in self._expanded_parameter_names: + ( + x, + y, + weight, + ) = self._get_xyweight_from_dataset(name) + xs.append(x) + ys.append(y) + ws.append(weight) + + mf = MultiFitter( + fit_objects=self._fit_objects, + fit_functions=self._fit_function_callables, + ) + + return mf.fit( + x=xs, + y=ys, + weights=ws, + ) + + ############# + # Private methods + ############# + + def _diffusion_model_to_fit_functions( + self, + diffusion_model: DiffusionModelBase, + ) -> tuple[list[callable], list[DiffusionModelBase]]: + """ + Convert a DiffusionModelBase to a list of fit functions. + + Parameters + ---------- + diffusion_model : DiffusionModelBase + The diffusion model to convert. + + Returns + ------- + tuple[list[callable], list[DiffusionModelBase]] + A list of fit functions corresponding to the diffusion model. + """ + + # Currently only looks at the area and width of a Lorentzian. + # Can and should be extended to also handle delta functions, more parameters etc. + + fit_functions = [] + fit_objects = [] + + if self.fit_settings.fit_area: + fit_functions.append(self._make_area_function(diffusion_model)) + fit_objects.append(diffusion_model) + + if self.fit_settings.fit_width: + fit_functions.append(self._make_width_function(diffusion_model)) + fit_objects.append(diffusion_model) + + return fit_functions, fit_objects + + @staticmethod + def _make_area_function(model: DiffusionModelBase) -> callable: + """ + Make a fit function for the area of a diffusion model. + + Parameters + ---------- + model : DiffusionModelBase + The diffusion model to make the fit function for. + """ + + def fit_function( + x: np.ndarray, + **kwargs: dict[str, Any], # noqa: ARG001 + ) -> np.ndarray: + return model.calculate_QISF(x) * model.scale.value + + return fit_function + + @staticmethod + def _make_width_function(model: DiffusionModelBase) -> callable: + """ + Make a fit function for the width of a diffusion model. + + Parameters + ---------- + model : DiffusionModelBase + The diffusion model to make the fit function for. + """ + + def fit_function( + x: np.ndarray, + **kwargs: dict[str, Any], # noqa: ARG001 + ) -> np.ndarray: + return model.calculate_width(x) + + return fit_function + + def _components_to_fit_functions( + self, + components: ModelComponent | ComponentCollection | None, + ) -> callable: + """ + Convert a ModelComponent or ComponentCollection to a fit function. + + Parameters + ---------- + components : ModelComponent | ComponentCollection | None + The component(s) to convert. + + Returns + ------- + callable + A fit function corresponding to the component(s). + """ + + if components is None: + return [] + + return self._make_components_function(components) + + @staticmethod + def _make_components_function( + components: ModelComponent | ComponentCollection, + ) -> callable: + def fit_function( + x: np.ndarray, + **kwargs: dict[str, Any], # noqa: ARG001 + ) -> np.ndarray: + return components.evaluate(x) + + return fit_function + + def _get_diffusion_model_parameter_names( + self, + parameter_name: str, + ) -> list[str]: + """ + Get the parameter names for a diffusion model. + + Parameters + ---------- + diffusion_model : DiffusionModelBase + The diffusion model to get parameter names from. + + Returns + ------- + list[str] + A list of parameter names. + """ + parameter_names = [] + if self.fit_settings.fit_area: + parameter_names.append(parameter_name + ' area') + if self.fit_settings.fit_width: + parameter_names.append(parameter_name + ' width') + return parameter_names + + def _get_xyweight_from_dataset( + self, parameter_name: str + ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Get the x, y, and weight values for a given parameter name from the parameters DataSet. + + Parameters + ---------- + parameter_name : str + The name of the parameter to get x, y, and weight values for. + + Returns + ------- + tuple[np.ndarray, np.ndarray, np.ndarray] + A tuple containing the x, y, and weight values for the given parameter name. + + Raises + ------ + ValueError + If the parameter name is not found in the parameters DataSet. + """ + if self._parameters is None: + raise ValueError('No parameters DataSet provided.') + if parameter_name not in self._parameters: + raise ValueError(f"Parameter name '{parameter_name}' not found in parameters DataSet.") + return ( + self._parameters[parameter_name].coords['Q'].values, + self._parameters[parameter_name].values, + 1 / self._parameters[parameter_name].variances ** 0.5, + ) + + ############# + # Dunder methods + ############# + + def __repr__(self) -> str: + """ + Return a string representation of the Analysis. + + Returns + ------- + str + A string representation of the Analysis. + """ + return ( + f' {self.__class__.__name__} (display_name={self.display_name}, ' + f'unique_name={self.unique_name})' + ) diff --git a/src/easydynamics/settings/__init__.py b/src/easydynamics/settings/__init__.py index c401fbce..1ddc2b93 100644 --- a/src/easydynamics/settings/__init__.py +++ b/src/easydynamics/settings/__init__.py @@ -4,6 +4,10 @@ from easydynamics.settings.convolution_settings import ConvolutionSettings from easydynamics.settings.detailed_balance_settings import DetailedBalanceSettings +# from easydynamics.settings.parameter_analysis_fit_settings import ( +# ParameterAnalysisFitSettings, +# ) + __all__ = [ 'ConvolutionSettings', 'DetailedBalanceSettings', diff --git a/src/easydynamics/settings/parameter_analysis_fit_settings.py b/src/easydynamics/settings/parameter_analysis_fit_settings.py new file mode 100644 index 00000000..dacbd906 --- /dev/null +++ b/src/easydynamics/settings/parameter_analysis_fit_settings.py @@ -0,0 +1,131 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +from easydynamics.base_classes.easydynamics_base import EasyDynamicsBase + + +class ParameterAnalysisFitSettings(EasyDynamicsBase): + """ + Class to manage fit settings for a ParameterAnalysis. + """ + + def __init__( + self, + fit_area: bool = True, + fit_width: bool = True, + display_name: str = 'ParameterAnalysisFitSettings', + unique_name: str | None = None, + ) -> None: + """ + Initialize the ParameterAnalysisFitSettings. + + Parameters + ---------- + fit_area : bool, default=True + Whether to fit the area of the DiffusionModel. If False, the area is not fitted. + fit_width : bool, default=True + Whether to fit the width of the DiffusionModel. If False, the width is not fitted. + display_name : str, default='ParameterAnalysisFitSettings' + Display name of the model. + unique_name : str | None, default=None + Unique name of the model. If None, a unique name will be generated. + + + Raises + ------ + TypeError + If fit_area or fit_width is not a bool. + """ + if not isinstance(fit_area, bool): + raise TypeError('fit_area must be True or False') + self._fit_area = fit_area + + if not isinstance(fit_width, bool): + raise TypeError('fit_width must be True or False') + self._fit_width = fit_width + + super().__init__( + display_name=display_name, + unique_name=unique_name, + ) + + # ------------------------------------------------------------------ + # Properties + # ------------------------------------------------------------------ + + @property + def fit_area(self) -> bool: + """ + Get whether to fit the area of the parameter. + + Returns + ------- + bool + True if the area is fitted, False otherwise. + """ + return self._fit_area + + @property + def fit_width(self) -> bool: + """ + Get whether to fit the width of the parameter. + + Returns + ------- + bool + True if the width is fitted, False otherwise. + """ + return self._fit_width + + @fit_area.setter + def fit_area(self, value: bool) -> None: + """ + Set whether to fit the area of the parameter. + + Parameters + ---------- + value : bool + True to fit the area, False otherwise. + + Raises + ------ + TypeError + If value is not a bool. + """ + if not isinstance(value, bool): + raise TypeError('fit_area must be True or False') + self._fit_area = value + + @fit_width.setter + def fit_width(self, value: bool) -> None: + """ + Set whether to fit the width of the parameter. + + Parameters + ---------- + value : bool + True to fit the width, False otherwise. + + Raises + ------ + + TypeError + If value is not a bool. + """ + if not isinstance(value, bool): + raise TypeError('fit_width must be True or False') + self._fit_width = value + + def __repr__(self) -> str: + """ + Return a string representation of the ParameterAnalysisFitSettings. + + Returns + ------- + str + A string representation of the ParameterAnalysisFitSettings. + """ + return ( + f'ParameterAnalysisFitSettings(fit_area={self.fit_area}, fit_width={self.fit_width})' + ) diff --git a/tests/performance_tests/convolution/convolution_width_thresholds.ipynb b/tests/performance_tests/convolution/convolution_width_thresholds.ipynb index 8b72d888..fbb5df29 100644 --- a/tests/performance_tests/convolution/convolution_width_thresholds.ipynb +++ b/tests/performance_tests/convolution/convolution_width_thresholds.ipynb @@ -45,9 +45,8 @@ " sample_components=sample_components,\n", " resolution_components=resolution_components,\n", " energy=x,\n", - " upsample_factor=None,\n", ")\n", - "\n", + "numerical_convolver.upsample_factor = None\n", "for gwidth in gaussian_widths:\n", " sample_components.components[0].width = gwidth\n", " y_analytical = analytical_convolver.convolution()\n", @@ -74,14 +73,6 @@ "plt.legend()" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "d6124d02", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, @@ -112,9 +103,8 @@ " sample_components=sample_components,\n", " resolution_components=resolution_components,\n", " energy=x,\n", - " upsample_factor=None,\n", ")\n", - "\n", + "numerical_convolver.upsample_factor = None\n", "for gwidth, gcenter in zip(gaussian_widths, gaussian_centers, strict=True):\n", " sample_components.components[0].width = gwidth\n", " sample_components.components[0].center = gcenter\n", @@ -145,7 +135,7 @@ ], "metadata": { "kernelspec": { - "display_name": "easydynamics_newbase", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -159,7 +149,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.14.4" } }, "nbformat": 4, From 7e472f4eb06969ccc0c435364977904e203e239a Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Fri, 24 Apr 2026 07:56:52 +0200 Subject: [PATCH 02/19] use dict instead of list --- docs/docs/tutorials/fit_parameters_test.ipynb | 71 +++++ .../analysis/parameter_analysis.py | 285 +++++++++++------- src/easydynamics/settings/__init__.py | 8 +- .../parameter_analysis_fit_settings.py | 131 -------- 4 files changed, 256 insertions(+), 239 deletions(-) delete mode 100644 src/easydynamics/settings/parameter_analysis_fit_settings.py diff --git a/docs/docs/tutorials/fit_parameters_test.ipynb b/docs/docs/tutorials/fit_parameters_test.ipynb index 413ce036..28be3f7f 100644 --- a/docs/docs/tutorials/fit_parameters_test.ipynb +++ b/docs/docs/tutorials/fit_parameters_test.ipynb @@ -474,6 +474,77 @@ "diffusion_analysis.plot_parameters(names=['Lorentzian width', 'Lorentzian area'])" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "bdc79445", + "metadata": {}, + "outputs": [], + "source": [ + "from easydynamics.analysis.parameter_analysis import ParameterAnalysis\n", + "\n", + "new_diffusion_model = BrownianTranslationalDiffusion(\n", + " display_name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", + ")\n", + "\n", + "\n", + "# parameter_analysis = ParameterAnalysis(\n", + "# display_name='Diffusion Parameter Analysis',\n", + "# fit_functions={'Lorentzian': new_diffusion_model},\n", + "# # fit_settings={'Lorentzian': [\"area\", \"width\"]},\n", + "# fit_settings={'Lorentzian': [\"width\"]},\n", + "# parameters=diffusion_analysis\n", + "# )\n", + "\n", + "\n", + "fit_func=Polynomial(coefficients=[0, 0, 1])\n", + "fit_func.coefficients[0].fixed = True\n", + "fit_func.coefficients[1].fixed = True\n", + "\n", + "parameter_analysis = ParameterAnalysis(\n", + " display_name='Diffusion Parameter Analysis',\n", + " fit_functions={'Lorentzian width': fit_func},\n", + " # fit_settings={'Lorentzian': [\"area\", \"width\"]},\n", + " parameters=diffusion_analysis\n", + ")\n", + "\n", + "\n", + "\n", + "result = parameter_analysis.fit()\n", + "new_diffusion_model.get_all_parameters()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11771143", + "metadata": {}, + "outputs": [], + "source": [ + "import scipp as sc\n", + "names = ['Lorentzian width', 'Lorentzian area']\n", + "\n", + "ds_subset = sc.Dataset(coords = parameter_analysis._parameters.coords)\n", + "\n", + "for name in names:\n", + " if name not in parameter_analysis._parameters:\n", + " raise KeyError(f\"{name} not found\")\n", + " ds_subset[name] = parameter_analysis._parameters[name]\n", + "\n", + "ds_subset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a06d2b3b", + "metadata": {}, + "outputs": [], + "source": [ + "plot = parameter_analysis.plot()\n", + "plot" + ] + }, { "cell_type": "markdown", "id": "eb226c8f", diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 8e2d8505..52dfd44f 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -4,6 +4,7 @@ from typing import Any import numpy as np +import plopp as pp import scipp as sc from easyscience.fitting.minimizers.utils import FitResults from easyscience.fitting.multi_fitter import MultiFitter @@ -12,8 +13,9 @@ from easydynamics.base_classes.easydynamics_base import EasyDynamicsBase from easydynamics.sample_model.component_collection import ComponentCollection from easydynamics.sample_model.components.model_component import ModelComponent -from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase -from easydynamics.settings.parameter_analysis_fit_settings import ParameterAnalysisFitSettings +from easydynamics.sample_model.diffusion_model.diffusion_model_base import ( + DiffusionModelBase, +) class ParameterAnalysis(EasyDynamicsBase): @@ -24,16 +26,20 @@ class ParameterAnalysis(EasyDynamicsBase): def __init__( self, parameters: sc.Dataset | Analysis | None = None, - parameter_names: str | list[str] | None = None, fit_functions: ( - ModelComponent - | ComponentCollection - | DiffusionModelBase - | list[ModelComponent | ComponentCollection | DiffusionModelBase] + dict[ + str, + ModelComponent + | ComponentCollection + | DiffusionModelBase + | list[ModelComponent | ComponentCollection | DiffusionModelBase] + | None, + ] | None ) = None, - fit_settings: ParameterAnalysisFitSettings | None = None, - display_name: str | None = 'MyAnalysis', + # fit_settings: ParameterAnalysisFitSettings | None = None, + fit_settings: dict[str, str | list[str]] | None = None, + display_name: str | None = "MyAnalysis", unique_name: str | None = None, ) -> None: """ @@ -49,19 +55,15 @@ def __init__( Raises ------ - TypeError - If experiment is not an Experiment or None or if sample_model is not a SampleModel or - None or if instrument_model is not an InstrumentModel or None or if - convolution_settings is not a ConvolutionSettings or None or if - detailed_balance_settings is not a DetailedBalanceSettings or None or if - extra_parameters is not a Parameter, a list of Parameters, or None. """ super().__init__(display_name=display_name, unique_name=unique_name) # Check parameters - if parameters is not None and not isinstance(parameters, (sc.Dataset, Analysis)): - raise TypeError('parameters must be an sc.Dataset, an Analysis, or None.') + if parameters is not None and not isinstance( + parameters, (sc.Dataset, Analysis) + ): + raise TypeError("parameters must be an sc.Dataset, an Analysis, or None.") if isinstance(parameters, Analysis): self._parameters = parameters.parameters_to_dataset() @@ -69,84 +71,54 @@ def __init__( self._parameters = parameters # Check fit settings - if fit_settings is not None and not isinstance(fit_settings, ParameterAnalysisFitSettings): - raise TypeError('fit_settings must be a ParameterAnalysisFitSettings or None.') if fit_settings is None: - fit_settings = ParameterAnalysisFitSettings() - self._fit_settings = fit_settings + fit_settings = {} - # Check fit_functions - if fit_functions is not None and not isinstance( - fit_functions, - ( - ModelComponent, - ComponentCollection, - DiffusionModelBase, - list, - ), - ): + if not isinstance(fit_settings, dict): raise TypeError( - 'fit_functions must be a ModelComponent, a ComponentCollection, a list of ' - 'ModelComponent/ComponentCollection, a DiffusionModelBase, or None.' + "fit_settings must be a dictionary of fit settings or None." ) - # Make fit_functions a list if it's not already - if isinstance(fit_functions, (ModelComponent, ComponentCollection, DiffusionModelBase)): - fit_functions = [fit_functions] - - for func in fit_functions: - if not isinstance( - func, - (ModelComponent, ComponentCollection, DiffusionModelBase), - ): + for key, value in fit_settings.items(): + if not isinstance(key, str): + raise TypeError("All keys in fit_settings must be strings.") + if not isinstance(value, (str, list)): raise TypeError( - 'All items in fit_functions list must be a ModelComponent, ' - 'a ComponentCollection, or a DiffusionModelBase.' + "All values in fit_settings must be strings or lists of strings." ) + if isinstance(value, list) and not all( + isinstance(item, str) for item in value + ): + raise TypeError("All items in lists in fit_settings must be strings.") - # Check parameter names - if parameter_names is not None and not isinstance(parameter_names, (str, list)): - raise TypeError('parameter_names must be a string, a list of strings, or None.') - # Make parameter_names a list if it's not already - if isinstance(parameter_names, str): - parameter_names = [parameter_names] + self._fit_settings = fit_settings - for name in parameter_names: - if not isinstance(name, str): - raise TypeError('All items in parameter_names list must be strings.') + if fit_functions is None: + fit_functions = {} - # Check that parameter_names and fit_functions have the same length - if len(parameter_names) != len(fit_functions): - raise ValueError('parameter_names must have the same length as fit_functions.') + if not isinstance(fit_functions, dict): + raise TypeError( + "fit_functions must be a dictionary mapping parameter names to fit functions." + ) - # Convert fit_functions to a list of callables and expand parameter_names if necessary - fit_function_callables = [] - fit_objects = [] - expanded_parameter_names = [] - if fit_functions is not None: - for name, func in zip(parameter_names, fit_functions, strict=True): - if isinstance(func, DiffusionModelBase): - fit_funcs, fit_objs = self._diffusion_model_to_fit_functions(func) - fit_function_callables.extend(fit_funcs) - fit_objects.extend(fit_objs) - expanded_parameter_names.extend( - self._get_diffusion_model_parameter_names(name) - ) - elif isinstance(func, (ModelComponent, ComponentCollection)): - fit_function_callables.append(self._components_to_fit_function(func)) - fit_objects.append(func) - expanded_parameter_names.append(name) - self._fit_functions = fit_functions - self._fit_function_callables = fit_function_callables - self._fit_objects = fit_objects - self._parameter_names = parameter_names - self._expanded_parameter_names = expanded_parameter_names + for name, func in fit_functions.items(): + if not isinstance(name, str): + raise TypeError("All keys in fit_functions must be strings.") + if not isinstance( + func, + ( + ModelComponent, + ComponentCollection, + DiffusionModelBase, + ), + ): + raise TypeError( + "All values in fit_functions must be a ModelComponent, a ComponentCollection, " + "or a DiffusionModelBase." + ) + self._fit_functions = fit_functions - # Check that all names are in the DataSet - if self._parameters is not None: - for name in self._expanded_parameter_names: - if name not in self._parameters: - raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") + self._prepare_fit_functions_and_parameter_names() ############# # Properties @@ -179,7 +151,7 @@ def fit_settings(self, value: ParameterAnalysisFitSettings) -> None: If value is not a ParameterAnalysisFitSettings. """ if not isinstance(value, ParameterAnalysisFitSettings): - raise TypeError('fit_settings must be a ParameterAnalysisFitSettings.') + raise TypeError("fit_settings must be a ParameterAnalysisFitSettings.") self._fit_settings = value ############# @@ -201,11 +173,7 @@ def fit(self) -> FitResults: ws = [] for name in self._expanded_parameter_names: - ( - x, - y, - weight, - ) = self._get_xyweight_from_dataset(name) + (x, y, weight) = self._get_xyweight_from_dataset(name) xs.append(x) ys.append(y) ws.append(weight) @@ -221,12 +189,62 @@ def fit(self) -> FitResults: weights=ws, ) + def plot( + self, names: str | list[str] | None = None, **kwargs: dict[str, Any] + ) -> None: + """ + Plot the parameters and fit results. + + Parameters + ---------- + names : str | list[str] | None, optional + The names of the parameters to plot. If None, all parameters are plotted. + **kwargs : dict[str, Any] + Additional keyword arguments to pass to the plotting function. + """ + if names is None: + names = self._expanded_parameter_names + elif isinstance(names, str): + names = [names] + + for name in names: + if name not in self._parameters: + raise ValueError( + f"Parameter name '{name}' not found in parameters DataSet." + ) + + data = sc.Dataset(coords=self._parameters.coords) + + for name in names: + data[name] = self._parameters[name] + + x = self._parameters.coords["Q"] + + fit_arrays = {} + + for name, func in zip(names, self._fit_function_callables, strict=True): + fit_values = func(x.values) + + fit_arrays[name + " fit"] = sc.DataArray( + data=sc.array( + dims=["Q"], values=fit_values, unit=self._parameters[name].unit + ), + coords={"Q": x}, + ) + + fit_dataset = sc.Dataset(fit_arrays) + + full_dataset = sc.merge(data, fit_dataset) + + return pp.plot(full_dataset, **kwargs) + ############# # Private methods ############# def _diffusion_model_to_fit_functions( self, + parameter_name: str, diffusion_model: DiffusionModelBase, ) -> tuple[list[callable], list[DiffusionModelBase]]: """ @@ -234,6 +252,8 @@ def _diffusion_model_to_fit_functions( Parameters ---------- + parameter_name : str + The name of the parameter. diffusion_model : DiffusionModelBase The diffusion model to convert. @@ -249,11 +269,23 @@ def _diffusion_model_to_fit_functions( fit_functions = [] fit_objects = [] - if self.fit_settings.fit_area: + if parameter_name in self.fit_settings: + fit_setting = self.fit_settings[parameter_name] + if isinstance(fit_setting, str): + fit_setting = [fit_setting] + + if "area" in fit_setting: + fit_functions.append(self._make_area_function(diffusion_model)) + fit_objects.append(diffusion_model) + + if "width" in fit_setting: + fit_functions.append(self._make_width_function(diffusion_model)) + fit_objects.append(diffusion_model) + else: + # If no fit settings are provided for this parameter, fit + # both area and width by default. fit_functions.append(self._make_area_function(diffusion_model)) fit_objects.append(diffusion_model) - - if self.fit_settings.fit_width: fit_functions.append(self._make_width_function(diffusion_model)) fit_objects.append(diffusion_model) @@ -297,7 +329,7 @@ def fit_function( return fit_function - def _components_to_fit_functions( + def _components_to_fit_function( self, components: ModelComponent | ComponentCollection | None, ) -> callable: @@ -350,12 +382,59 @@ def _get_diffusion_model_parameter_names( A list of parameter names. """ parameter_names = [] - if self.fit_settings.fit_area: - parameter_names.append(parameter_name + ' area') - if self.fit_settings.fit_width: - parameter_names.append(parameter_name + ' width') + + if parameter_name in self.fit_settings: + fit_setting = self.fit_settings[parameter_name] + if isinstance(fit_setting, str): + fit_setting = [fit_setting] + + if "area" in fit_setting: + parameter_names.append(parameter_name + " area") + + if "width" in fit_setting: + parameter_names.append(parameter_name + " width") + else: + parameter_names.append(parameter_name + " area") + parameter_names.append(parameter_name + " width") + return parameter_names + def _prepare_fit_functions_and_parameter_names(self) -> None: + """ + Make a list of fit functions callables, fit objects and + parameter names, expanding diffusion models into their + parameters if necessary. + """ + fit_function_callables = [] + fit_objects = [] + expanded_parameter_names = [] + for name, func in self._fit_functions.items(): + if isinstance(func, DiffusionModelBase): + fit_funcs, fit_objs = self._diffusion_model_to_fit_functions(name, func) + fit_function_callables.extend(fit_funcs) + fit_objects.extend(fit_objs) + expanded_parameter_names.extend( + self._get_diffusion_model_parameter_names(name) + ) + elif isinstance(func, (ModelComponent, ComponentCollection)): + fit_function_callables.append(self._components_to_fit_function(func)) + fit_objects.append(func) + expanded_parameter_names.append(name) + self._fit_function_callables = fit_function_callables + self._fit_objects = fit_objects + self._parameter_names = list(self._fit_functions.keys()) + self._expanded_parameter_names = expanded_parameter_names + + # Check that all names are in the DataSet + if self._parameters is not None: + for name in self._expanded_parameter_names: + if name not in self._parameters: + raise ValueError( + f"Parameter name '{name}' not found in parameters DataSet." + ) + + return + def _get_xyweight_from_dataset( self, parameter_name: str ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: @@ -378,11 +457,13 @@ def _get_xyweight_from_dataset( If the parameter name is not found in the parameters DataSet. """ if self._parameters is None: - raise ValueError('No parameters DataSet provided.') + raise ValueError("No parameters DataSet provided.") if parameter_name not in self._parameters: - raise ValueError(f"Parameter name '{parameter_name}' not found in parameters DataSet.") + raise ValueError( + f"Parameter name '{parameter_name}' not found in parameters DataSet." + ) return ( - self._parameters[parameter_name].coords['Q'].values, + self._parameters[parameter_name].coords["Q"].values, self._parameters[parameter_name].values, 1 / self._parameters[parameter_name].variances ** 0.5, ) @@ -401,6 +482,6 @@ def __repr__(self) -> str: A string representation of the Analysis. """ return ( - f' {self.__class__.__name__} (display_name={self.display_name}, ' - f'unique_name={self.unique_name})' + f" {self.__class__.__name__} (display_name={self.display_name}, " + f"unique_name={self.unique_name})" ) diff --git a/src/easydynamics/settings/__init__.py b/src/easydynamics/settings/__init__.py index 1ddc2b93..0867ddad 100644 --- a/src/easydynamics/settings/__init__.py +++ b/src/easydynamics/settings/__init__.py @@ -4,11 +4,7 @@ from easydynamics.settings.convolution_settings import ConvolutionSettings from easydynamics.settings.detailed_balance_settings import DetailedBalanceSettings -# from easydynamics.settings.parameter_analysis_fit_settings import ( -# ParameterAnalysisFitSettings, -# ) - __all__ = [ - 'ConvolutionSettings', - 'DetailedBalanceSettings', + "ConvolutionSettings", + "DetailedBalanceSettings", ] diff --git a/src/easydynamics/settings/parameter_analysis_fit_settings.py b/src/easydynamics/settings/parameter_analysis_fit_settings.py deleted file mode 100644 index dacbd906..00000000 --- a/src/easydynamics/settings/parameter_analysis_fit_settings.py +++ /dev/null @@ -1,131 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - - -from easydynamics.base_classes.easydynamics_base import EasyDynamicsBase - - -class ParameterAnalysisFitSettings(EasyDynamicsBase): - """ - Class to manage fit settings for a ParameterAnalysis. - """ - - def __init__( - self, - fit_area: bool = True, - fit_width: bool = True, - display_name: str = 'ParameterAnalysisFitSettings', - unique_name: str | None = None, - ) -> None: - """ - Initialize the ParameterAnalysisFitSettings. - - Parameters - ---------- - fit_area : bool, default=True - Whether to fit the area of the DiffusionModel. If False, the area is not fitted. - fit_width : bool, default=True - Whether to fit the width of the DiffusionModel. If False, the width is not fitted. - display_name : str, default='ParameterAnalysisFitSettings' - Display name of the model. - unique_name : str | None, default=None - Unique name of the model. If None, a unique name will be generated. - - - Raises - ------ - TypeError - If fit_area or fit_width is not a bool. - """ - if not isinstance(fit_area, bool): - raise TypeError('fit_area must be True or False') - self._fit_area = fit_area - - if not isinstance(fit_width, bool): - raise TypeError('fit_width must be True or False') - self._fit_width = fit_width - - super().__init__( - display_name=display_name, - unique_name=unique_name, - ) - - # ------------------------------------------------------------------ - # Properties - # ------------------------------------------------------------------ - - @property - def fit_area(self) -> bool: - """ - Get whether to fit the area of the parameter. - - Returns - ------- - bool - True if the area is fitted, False otherwise. - """ - return self._fit_area - - @property - def fit_width(self) -> bool: - """ - Get whether to fit the width of the parameter. - - Returns - ------- - bool - True if the width is fitted, False otherwise. - """ - return self._fit_width - - @fit_area.setter - def fit_area(self, value: bool) -> None: - """ - Set whether to fit the area of the parameter. - - Parameters - ---------- - value : bool - True to fit the area, False otherwise. - - Raises - ------ - TypeError - If value is not a bool. - """ - if not isinstance(value, bool): - raise TypeError('fit_area must be True or False') - self._fit_area = value - - @fit_width.setter - def fit_width(self, value: bool) -> None: - """ - Set whether to fit the width of the parameter. - - Parameters - ---------- - value : bool - True to fit the width, False otherwise. - - Raises - ------ - - TypeError - If value is not a bool. - """ - if not isinstance(value, bool): - raise TypeError('fit_width must be True or False') - self._fit_width = value - - def __repr__(self) -> str: - """ - Return a string representation of the ParameterAnalysisFitSettings. - - Returns - ------- - str - A string representation of the ParameterAnalysisFitSettings. - """ - return ( - f'ParameterAnalysisFitSettings(fit_area={self.fit_area}, fit_width={self.fit_width})' - ) From 46afd6081b5a913fb00e365e27126441f16d8bf3 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Mon, 27 Apr 2026 06:48:58 +0200 Subject: [PATCH 03/19] Update examples, linting --- docs/docs/tutorials/fit_parameters_test.ipynb | 702 ------------------ docs/docs/tutorials/tutorial0_basics.ipynb | 74 +- .../tutorials/tutorial0_more_advanced.ipynb | 57 +- docs/docs/tutorials/tutorial1_brownian.ipynb | 107 ++- src/easydynamics/__init__.py | 4 + src/easydynamics/analysis/__init__.py | 2 + .../analysis/parameter_analysis.py | 454 +++++++---- src/easydynamics/settings/__init__.py | 4 +- 8 files changed, 542 insertions(+), 862 deletions(-) delete mode 100644 docs/docs/tutorials/fit_parameters_test.ipynb diff --git a/docs/docs/tutorials/fit_parameters_test.ipynb b/docs/docs/tutorials/fit_parameters_test.ipynb deleted file mode 100644 index 28be3f7f..00000000 --- a/docs/docs/tutorials/fit_parameters_test.ipynb +++ /dev/null @@ -1,702 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "8643b10c", - "metadata": {}, - "source": [ - "# Brownian Diffusion\n", - "We here show how to set up an Analysis object and use it to first fit an artificial vanadium measurement to obtain the resolution. Next, we use the fitted resolution to fit an artificial measurement of a model with diffusion and some elastic scattering. \n", - "\n", - "We extract and plot the relevant parameters. Finally, we show how to fit directly to the diffusion model.\n", - "\n", - "In the near future, it will be possible to fit the width and area of the Lorentzian to the diffusion model as well." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bca91d3c", - "metadata": {}, - "outputs": [], - "source": [ - "# Imports\n", - "import pooch\n", - "\n", - "from easydynamics.analysis.analysis import Analysis\n", - "from easydynamics.experiment import Experiment\n", - "from easydynamics.sample_model import BrownianTranslationalDiffusion\n", - "from easydynamics.sample_model import ComponentCollection\n", - "from easydynamics.sample_model import DeltaFunction\n", - "from easydynamics.sample_model import Gaussian\n", - "from easydynamics.sample_model import Lorentzian\n", - "from easydynamics.sample_model import Polynomial\n", - "from easydynamics.sample_model.background_model import BackgroundModel\n", - "from easydynamics.sample_model.instrument_model import InstrumentModel\n", - "from easydynamics.sample_model.resolution_model import ResolutionModel\n", - "from easydynamics.sample_model.sample_model import SampleModel\n", - "\n", - "# Make the plots interactive\n", - "%matplotlib widget" - ] - }, - { - "cell_type": "markdown", - "id": "4c8e97b7", - "metadata": {}, - "source": [ - "We first create an `Experiment` object to contain the data. The data must either be a hdf5 file or a scipp.DataArray; in both cases it must have coordinates `Q` and `energy`. We here use Pooch to download an example vanadium data set.\n", - "\n", - "The data can be rebinned if needed, but we will show how to do that in a different tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8deca9b6", - "metadata": {}, - "outputs": [], - "source": [ - "# Load the vanadium data\n", - "vanadium_experiment = Experiment('Vanadium')\n", - "\n", - "file_path = pooch.retrieve(\n", - " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/vanadium_data_example.h5',\n", - " known_hash='16cc1b327c303feeb88fb9dda5390dc4880b62396b1793f98c6fef0b27c7b873',\n", - ")\n", - "\n", - "\n", - "vanadium_experiment.load_hdf5(filename=file_path)" - ] - }, - { - "cell_type": "markdown", - "id": "7daa3f64", - "metadata": {}, - "source": [ - "We can visualize the data in multiple ways, relying on plopp: https://scipp.github.io/plopp/\n", - "\n", - "We here show two ways to look at the data: as a 2d colormap with intensity as function of `Q` and `energy`, and as a slicer with intensity as function of `energy` for various `Q`.\n", - "\n", - "If you want $Q$ on the x axis, then set `transpose_axes=True`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fbd31297", - "metadata": {}, - "outputs": [], - "source": [ - "vanadium_experiment.plot_data(slicer=False, transpose_axes=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4153ba52", - "metadata": {}, - "outputs": [], - "source": [ - "vanadium_experiment.plot_data(slicer=True)" - ] - }, - { - "cell_type": "markdown", - "id": "6c87b01c", - "metadata": {}, - "source": [ - "We now want to fit the vanadium data to determine our resolution. The scattering from vanadium is almost exclusively incoherent elastic, so we model it as a delta function. We do this by creating a `SampleModel` and adding a `DeltaFunction` component to it. The component acts as a template and gets copied to every `Q` when we attach the `SampleModel` to our `Analysis` object. Let's create the `SampleModel`.\n", - "\n", - "We do not give the `DeltaFunction` a `center` value. In this case, the center will be fixed at 0 energy transfer. We set the start value of the area to 1." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6762faba", - "metadata": {}, - "outputs": [], - "source": [ - "delta_function = DeltaFunction(display_name='DeltaFunction', area=1)\n", - "sample_model = SampleModel(components=delta_function)" - ] - }, - { - "cell_type": "markdown", - "id": "dc82774e", - "metadata": {}, - "source": [ - "We now want to define our resolution function. We will here model it as a Gaussian. We create a `ComponentCollection` and append the `Gaussian` to it. We can add as many components to our resolution as we like; sometimes you need several Gaussians and other functions to accurately describe the resolution.\n", - "\n", - "We fix the area of the resolution to have value 1. If we did not do this, we would fit both the area of the delta function and of the resolution Gaussian, and the fit would never converge.\n", - "\n", - "We finally insert the components in a `ResolutionModel`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8fdb7f19", - "metadata": {}, - "outputs": [], - "source": [ - "resolution_components = ComponentCollection()\n", - "res_gauss = Gaussian(width=0.1, area=1, display_name='Res. Gauss')\n", - "res_gauss.area.fixed = True\n", - "resolution_components.append_component(res_gauss)\n", - "resolution_model = ResolutionModel(components=resolution_components)" - ] - }, - { - "cell_type": "markdown", - "id": "088ac17d", - "metadata": {}, - "source": [ - "The background intensity was not 0, so we also create a background model. We use a `Polynomial` with a single coefficient, i.e. a flat background. We here show how to create the `BackgroundModel` and add the background in a single line. We could of course also add it like we did for the `SampleModel` or first create a `ComponentCollection` like we did for the `ResolutionModel`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1ec6836f", - "metadata": {}, - "outputs": [], - "source": [ - "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" - ] - }, - { - "cell_type": "markdown", - "id": "eae3d14b", - "metadata": {}, - "source": [ - "We combine the resolution abd background model into an `InstrumentModel`. This model also contains a fittable energy offset to account for instrument misalignment. All components are centered at this energy offset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0ad79a75", - "metadata": {}, - "outputs": [], - "source": [ - "instrument_model = InstrumentModel(\n", - " resolution_model=resolution_model,\n", - " background_model=background_model,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "a99be7c1", - "metadata": {}, - "source": [ - "We are now ready to collect everything in an analysis object. We give it a display name, the experiment, the sample model and the instrument model. It will then automatically generate a model for each `Q` using the templates given in the `SampleModel`, `ResolutionModel` and `BackgroundModel`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b98d63dc", - "metadata": {}, - "outputs": [], - "source": [ - "vanadium_analysis = Analysis(\n", - " display_name='Vanadium Full Analysis',\n", - " experiment=vanadium_experiment,\n", - " sample_model=sample_model,\n", - " instrument_model=instrument_model,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "a81248a4", - "metadata": {}, - "source": [ - "Let us first fit a single Q index and plot the data and model to see how it looks. For this, we use the `independent` fit method and choose an arbitrary Q index" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "aec75b7f", - "metadata": {}, - "outputs": [], - "source": [ - "fit_result_independent_single_Q = vanadium_analysis.fit(fit_method='independent', Q_index=5)\n", - "vanadium_analysis.plot_data_and_model(Q_index=5)" - ] - }, - { - "cell_type": "markdown", - "id": "c3fe553b", - "metadata": {}, - "source": [ - "The fit looks good, so let us fit all Q indices independently and plot the results." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e98e3d65", - "metadata": {}, - "outputs": [], - "source": [ - "fit_result_independent_all_Q = vanadium_analysis.fit(fit_method='independent')\n", - "vanadium_analysis.plot_data_and_model()" - ] - }, - { - "cell_type": "markdown", - "id": "12936c9d", - "metadata": {}, - "source": [ - "It can be nice to inspect the fit parameters, and sometimes continue working with them. To do this, we can convert them to a scipp dataset.\n", - "\n", - "We can also plot the parameters as a function of `Q` using the `plot_parameters` method." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "133e682e", - "metadata": {}, - "outputs": [], - "source": [ - "# Inspect the Parameters as a scipp Dataset\n", - "vanadium_analysis.parameters_to_dataset()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dfacdf24", - "metadata": {}, - "outputs": [], - "source": [ - "# Plot some of fitted parameters as a function of Q\n", - "vanadium_analysis.plot_parameters(names=['DeltaFunction area'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b6f9f316", - "metadata": {}, - "outputs": [], - "source": [ - "vanadium_analysis.plot_parameters(names=['Res. Gauss width'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "572664a0", - "metadata": {}, - "outputs": [], - "source": [ - "vanadium_analysis.plot_parameters(names=['energy_offset'])" - ] - }, - { - "cell_type": "markdown", - "id": "ea2e5996", - "metadata": {}, - "source": [ - "We are now happy with our resolution function and can start looking at the data we want to fit. We first load and inspect the data in the same way as before. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3609e6c1", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_experiment = Experiment('Diffusion')\n", - "\n", - "file_path = pooch.retrieve(\n", - " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/diffusion_data_example.h5',\n", - " known_hash='5fe846b19aacbda8b8b936eb2e5310d025dc56c25b0b353521e7d6b921f229ab',\n", - ")\n", - "\n", - "diffusion_experiment.load_hdf5(filename=file_path)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba9b14cc", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_experiment.plot_data(slicer=True, ymax=4)" - ] - }, - { - "cell_type": "markdown", - "id": "6da4f9e2", - "metadata": {}, - "source": [ - "The data seems to have a sharp elastic peak, a quasielastic peak and a non-zero background. We set up the corresponding `SampleModel` and `BackgroundModel` just like before." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e685909a", - "metadata": {}, - "outputs": [], - "source": [ - "delta_function = DeltaFunction(display_name='DeltaFunction', area=0.2)\n", - "lorentzian = Lorentzian(display_name='Lorentzian', area=0.5, width=0.3)\n", - "component_collection = ComponentCollection(\n", - " components=[delta_function, lorentzian],\n", - ")\n", - "\n", - "sample_model = SampleModel(\n", - " components=component_collection,\n", - ")\n", - "\n", - "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" - ] - }, - { - "cell_type": "markdown", - "id": "927b8fb5", - "metadata": {}, - "source": [ - "We also create a new instrument_model and attach it to our analysis, giving it the resolution model determined in the vanadium analysis. We further fix all parameters in the resolution model and normalize it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "53ecf27e", - "metadata": {}, - "outputs": [], - "source": [ - "instrument_model = InstrumentModel(\n", - " background_model=background_model,\n", - " resolution_model=vanadium_analysis.instrument_model.resolution_model,\n", - ")\n", - "instrument_model.resolution_model.fix_all_parameters()\n", - "instrument_model.normalize_resolution()\n", - "\n", - "diffusion_analysis = Analysis(\n", - " display_name='Diffusion Analysis',\n", - " experiment=diffusion_experiment,\n", - " sample_model=sample_model,\n", - " instrument_model=instrument_model,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "b72a710b", - "metadata": {}, - "source": [ - "We don't want to fit our resolution anymore, so we fix all the parameters in it.\n", - "\n", - "`Analysis` handles the convolution of the `sample_model` with the `resolution_model`. The calculation is analytical when possible, and otherwise numerical. For numerical convolution, it will give warnings if the result might be inaccurate due to various numerical errors. In that case, two settings can be varied to improve the result.\n", - "`upsample_factor` improves accuracy for narrow signals. The default is 5.\n", - "`extension_factor` improves accuracy for broad signals. Here, the default is 0.2.\n", - "It is furthermore possible to toggle whether detailed balance correction is normalized or not (see next tutorial)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0fb0c648", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_analysis.convolution_settings.upsample_factor = 6\n", - "diffusion_analysis.convolution_settings.extension_factor = 0.2\n", - "diffusion_analysis.convolution_settings.normalize_detailed_balance = True" - ] - }, - { - "cell_type": "markdown", - "id": "e366a05d", - "metadata": {}, - "source": [ - "Before we start fitting it is a good idea to check how good our start guesses are. We do this by plotting the data and the model using `plot_data_and_model`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c66828eb", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_analysis.plot_data_and_model()" - ] - }, - { - "cell_type": "markdown", - "id": "2a4b7572", - "metadata": {}, - "source": [ - "The start guesses are not perfect, but they look good enough to start fitting. Let's give it a try!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9678eede", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_analysis.fit(fit_method='independent')\n", - "diffusion_analysis.plot_data_and_model()" - ] - }, - { - "cell_type": "markdown", - "id": "45daa848", - "metadata": {}, - "source": [ - "The fit looks good, so now we want to look at the most interesting fit parameters: the width and area of the Lorentzian. In later versions of EasyDynamics it will be possible to fit them to e.g. a DiffusionModel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "df14b5c4", - "metadata": {}, - "outputs": [], - "source": [ - "# Let us look at the most interesting fit parameters\n", - "diffusion_analysis.plot_parameters(names=['Lorentzian width', 'Lorentzian area'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bdc79445", - "metadata": {}, - "outputs": [], - "source": [ - "from easydynamics.analysis.parameter_analysis import ParameterAnalysis\n", - "\n", - "new_diffusion_model = BrownianTranslationalDiffusion(\n", - " display_name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", - ")\n", - "\n", - "\n", - "# parameter_analysis = ParameterAnalysis(\n", - "# display_name='Diffusion Parameter Analysis',\n", - "# fit_functions={'Lorentzian': new_diffusion_model},\n", - "# # fit_settings={'Lorentzian': [\"area\", \"width\"]},\n", - "# fit_settings={'Lorentzian': [\"width\"]},\n", - "# parameters=diffusion_analysis\n", - "# )\n", - "\n", - "\n", - "fit_func=Polynomial(coefficients=[0, 0, 1])\n", - "fit_func.coefficients[0].fixed = True\n", - "fit_func.coefficients[1].fixed = True\n", - "\n", - "parameter_analysis = ParameterAnalysis(\n", - " display_name='Diffusion Parameter Analysis',\n", - " fit_functions={'Lorentzian width': fit_func},\n", - " # fit_settings={'Lorentzian': [\"area\", \"width\"]},\n", - " parameters=diffusion_analysis\n", - ")\n", - "\n", - "\n", - "\n", - "result = parameter_analysis.fit()\n", - "new_diffusion_model.get_all_parameters()\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "11771143", - "metadata": {}, - "outputs": [], - "source": [ - "import scipp as sc\n", - "names = ['Lorentzian width', 'Lorentzian area']\n", - "\n", - "ds_subset = sc.Dataset(coords = parameter_analysis._parameters.coords)\n", - "\n", - "for name in names:\n", - " if name not in parameter_analysis._parameters:\n", - " raise KeyError(f\"{name} not found\")\n", - " ds_subset[name] = parameter_analysis._parameters[name]\n", - "\n", - "ds_subset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a06d2b3b", - "metadata": {}, - "outputs": [], - "source": [ - "plot = parameter_analysis.plot()\n", - "plot" - ] - }, - { - "cell_type": "markdown", - "id": "eb226c8f", - "metadata": {}, - "source": [ - "There is a clear trend: the area is more or less constant, while the width seems to increase with `Q^2`. We therefore try and fit a Brownian translational diffusion model to the data. In this model, the scattering is given by\n", - "$$\n", - "I(Q,E) = S \\frac{\\Gamma(Q)}{\\Gamma(Q)^2 + E^2},\n", - "$$\n", - "where $\\Gamma(Q) = D Q^2$ and $D$ is the diffusion coefficient. $S$ is an overall scale.\n", - "\n", - "In addition to this diffusion model, there is still the elastic incoherent scattering.\n", - "\n", - "We create a new `SampleModel` which as a `DeltaFunction` component for the elastic incoherent scattering and a `BrownianTranslationalDiffusion` diffusion model to describe the rest. We also create a new `BackgroundModel` and `InstrumentModel`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b6bba77", - "metadata": {}, - "outputs": [], - "source": [ - "delta_function = DeltaFunction(display_name='DeltaFunction', area=0.2)\n", - "component_collection = ComponentCollection(\n", - " components=[delta_function],\n", - ")\n", - "diffusion_model = BrownianTranslationalDiffusion(\n", - " display_name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", - ")\n", - "\n", - "sample_model = SampleModel(\n", - " components=component_collection,\n", - " diffusion_models=diffusion_model,\n", - ")\n", - "\n", - "background_model = BackgroundModel(components=Polynomial(coefficients=[0.001]))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "48370156", - "metadata": {}, - "outputs": [], - "source": [ - "instrument_model = InstrumentModel(\n", - " background_model=background_model,\n", - " resolution_model=vanadium_analysis.instrument_model.resolution_model,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "8bb4abe9", - "metadata": {}, - "source": [ - "We attach all our models to a new `Analysis` object." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3efb9b53", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_model_analysis = Analysis(\n", - " display_name='Diffusion Full Analysis',\n", - " experiment=diffusion_experiment,\n", - " sample_model=sample_model,\n", - " instrument_model=instrument_model,\n", - ")\n", - "\n", - "diffusion_model_analysis.instrument_model.resolution_model.fix_all_parameters()" - ] - }, - { - "cell_type": "markdown", - "id": "786157c0", - "metadata": {}, - "source": [ - "As always, we first check the start parameters before we fit" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "683dd27c", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_model_analysis.plot_data_and_model()" - ] - }, - { - "cell_type": "markdown", - "id": "43e2aa66", - "metadata": {}, - "source": [ - "We can now fit all the data simultaneously to our model. It looks good!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fd04d359", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_model_analysis.fit(fit_method='simultaneous')\n", - "diffusion_model_analysis.plot_data_and_model(ymax=2)" - ] - }, - { - "cell_type": "markdown", - "id": "bbeec088", - "metadata": {}, - "source": [ - "It does not make sense to plot the diffusion parameters, but we can display them (with uncertainties) like this." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "842c1f01", - "metadata": {}, - "outputs": [], - "source": [ - "diffusion_model.get_all_parameters()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "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.14.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/docs/tutorials/tutorial0_basics.ipynb b/docs/docs/tutorials/tutorial0_basics.ipynb index 44442036..1e0daa62 100644 --- a/docs/docs/tutorials/tutorial0_basics.ipynb +++ b/docs/docs/tutorials/tutorial0_basics.ipynb @@ -23,7 +23,8 @@ "\n", "import easydynamics as edyn\n", "import easydynamics.sample_model as sm\n", - "from easydynamics.analysis.analysis import Analysis\n", + "from easydynamics.analysis import Analysis\n", + "from easydynamics.analysis import ParameterAnalysis\n", "\n", "# Make the plots interactive\n", "%matplotlib widget" @@ -368,7 +369,76 @@ "id": "842c1f01", "metadata": {}, "source": [ - "It will soon be possible to use **EasyDynamics** to fit these parameters to e.g. a polynomial." + "The final step in this tutorial is to fit the are of the `Gaussian` to a straight line. For this, we use the `ParameterAnalysis` class. We create a `Polynomial` with two coefficients for the fit function. We pass a dictionary with the name of the parameter we want to fit as the key (in this case, `Gaussian area`), and the fit function as the item. We also pass our `Analysis` object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75db3d4c", + "metadata": {}, + "outputs": [], + "source": [ + "fit_func = sm.Polynomial(coefficients=[3.7, -0.5], unit='1/angstrom', display_name='Straight line')\n", + "\n", + "parameter_analysis = ParameterAnalysis(\n", + " fit_functions={'Gaussian area': fit_func}, parameters=analysis\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "01f45034", + "metadata": {}, + "source": [ + "Let us plot the start guess using the `plot()` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd3cf4a6", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "634bebdc", + "metadata": {}, + "source": [ + "It looks decent, so we can fit and plot again:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5ba8985", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.fit()\n", + "parameter_analysis.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "dc33728c", + "metadata": {}, + "source": [ + "To see the parameters we can use the `get_all_parameters()` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f18e2944", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.get_all_parameters()" ] } ], diff --git a/docs/docs/tutorials/tutorial0_more_advanced.ipynb b/docs/docs/tutorials/tutorial0_more_advanced.ipynb index 49cb8963..b0af166b 100644 --- a/docs/docs/tutorials/tutorial0_more_advanced.ipynb +++ b/docs/docs/tutorials/tutorial0_more_advanced.ipynb @@ -292,7 +292,62 @@ "metadata": {}, "outputs": [], "source": [ - "analysis.plot_parameters(names=['Gaussian area', 'Lorentzian area', 'DHO area'])" + "analysis.plot_parameters(names=['Gaussian area', 'DHO area', 'DHO center'])" + ] + }, + { + "cell_type": "markdown", + "id": "0eadbd91", + "metadata": {}, + "source": [ + "With apologies for the lack of creativity, these all appear like straight lines. We can fit them individually or all together using `ParameterAnalysis`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb2f0e06", + "metadata": {}, + "outputs": [], + "source": [ + "gauss_fit_func = sm.Polynomial(\n", + " coefficients=[3.7, -0.5], unit='1/angstrom', display_name='Gauss area fit'\n", + ")\n", + "dho_area_fit_func = sm.Polynomial(\n", + " coefficients=[2.5, 0.1], unit='1/angstrom', display_name='DHO area fit'\n", + ")\n", + "dho_center_fit_func = sm.Polynomial(\n", + " coefficients=[1.4, 0.1], unit='1/angstrom', display_name='DHO center fit'\n", + ")\n", + "\n", + "parameter_analysis = edyn.ParameterAnalysis(\n", + " fit_functions={\n", + " 'Gaussian area': gauss_fit_func,\n", + " 'DHO area': dho_area_fit_func,\n", + " 'DHO center': dho_center_fit_func,\n", + " },\n", + " parameters=analysis,\n", + ")\n", + "parameter_analysis.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "32bc1efc", + "metadata": {}, + "source": [ + "The start guesses look reasonable, so we fit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5548093", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.fit()\n", + "parameter_analysis.plot()" ] } ], diff --git a/docs/docs/tutorials/tutorial1_brownian.ipynb b/docs/docs/tutorials/tutorial1_brownian.ipynb index 413ce036..3b70f016 100644 --- a/docs/docs/tutorials/tutorial1_brownian.ipynb +++ b/docs/docs/tutorials/tutorial1_brownian.ipynb @@ -24,6 +24,7 @@ "import pooch\n", "\n", "from easydynamics.analysis.analysis import Analysis\n", + "from easydynamics.analysis.parameter_analysis import ParameterAnalysis\n", "from easydynamics.experiment import Experiment\n", "from easydynamics.sample_model import BrownianTranslationalDiffusion\n", "from easydynamics.sample_model import ComponentCollection\n", @@ -460,7 +461,7 @@ "id": "45daa848", "metadata": {}, "source": [ - "The fit looks good, so now we want to look at the most interesting fit parameters: the width and area of the Lorentzian. In later versions of EasyDynamics it will be possible to fit them to e.g. a DiffusionModel." + "The fit looks good, so now we want to look at the most interesting fit parameters: the width and area of the Lorentzian. " ] }, { @@ -470,7 +471,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Let us look at the most interesting fit parameters\n", "diffusion_analysis.plot_parameters(names=['Lorentzian width', 'Lorentzian area'])" ] }, @@ -485,6 +485,89 @@ "$$\n", "where $\\Gamma(Q) = D Q^2$ and $D$ is the diffusion coefficient. $S$ is an overall scale.\n", "\n", + "To fit the Brownian translational diffusion model to the data, we use the `ParameterAnalysis`. This time, we wish to fit the `Lorentzian`, and we wish to fit both its area (scale, $S$) and width to the diffusion model. We do this by setting `fit_settings` using a dictionary:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c1ab6b0", + "metadata": {}, + "outputs": [], + "source": [ + "brownian_diffusion_model = BrownianTranslationalDiffusion(\n", + " display_name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", + ")\n", + "\n", + "parameter_analysis = ParameterAnalysis(\n", + " fit_functions={'Lorentzian': brownian_diffusion_model},\n", + " fit_settings={'Lorentzian': ['area', 'width']},\n", + " parameters=diffusion_analysis,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "53dc12dc", + "metadata": {}, + "source": [ + "We first plot the start guess to see if it's reasonable." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7074c64", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "64babb01", + "metadata": {}, + "source": [ + "This looks pretty good. Let's fit and plot again:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26c418ef", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.fit()\n", + "parameter_analysis.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "81d30f55", + "metadata": {}, + "source": [ + "And we can get the parameters of the diffusion model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "313cab16", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.get_all_parameters()" + ] + }, + { + "cell_type": "markdown", + "id": "fc2f8434", + "metadata": {}, + "source": [ + "This is a good result, but we can do better. Now that we know that the quasielastic scattering is well described by a model of diffusion, we can fit this model directly to the data, fitting all $Q$ simultaneously.\n", + "\n", "In addition to this diffusion model, there is still the elastic incoherent scattering.\n", "\n", "We create a new `SampleModel` which as a `DeltaFunction` component for the elastic incoherent scattering and a `BrownianTranslationalDiffusion` diffusion model to describe the rest. We also create a new `BackgroundModel` and `InstrumentModel`." @@ -593,7 +676,7 @@ "id": "bbeec088", "metadata": {}, "source": [ - "It does not make sense to plot the diffusion parameters, but we can display them (with uncertainties) like this." + "It does not make sense to plot the diffusion parameters, as they are just two numbers with uncertainties, but we can display them (with uncertainties) like this." ] }, { @@ -605,6 +688,24 @@ "source": [ "diffusion_model.get_all_parameters()" ] + }, + { + "cell_type": "markdown", + "id": "fc9bf6b1", + "metadata": {}, + "source": [ + "For reference, here are the diffusion parameters found from fitting the width and scale of the fitted Lorentzians. Notice that the error bars when fitting everything simultaneously are a lot lower than the two-step fit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0af81fa8", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.get_all_parameters()" + ] } ], "metadata": { diff --git a/src/easydynamics/__init__.py b/src/easydynamics/__init__.py index 54557144..86979047 100644 --- a/src/easydynamics/__init__.py +++ b/src/easydynamics/__init__.py @@ -3,11 +3,15 @@ """EasyDynamics library.""" from easydynamics.analysis import Analysis +from easydynamics.analysis.parameter_analysis import ParameterAnalysis from easydynamics.experiment import Experiment from easydynamics.settings.convolution_settings import ConvolutionSettings +from easydynamics.settings.detailed_balance_settings import DetailedBalanceSettings __all__ = [ 'Analysis', 'ConvolutionSettings', + 'DetailedBalanceSettings', 'Experiment', + 'ParameterAnalysis', ] diff --git a/src/easydynamics/analysis/__init__.py b/src/easydynamics/analysis/__init__.py index a52854bc..289ec02f 100644 --- a/src/easydynamics/analysis/__init__.py +++ b/src/easydynamics/analysis/__init__.py @@ -2,7 +2,9 @@ # SPDX-License-Identifier: BSD-3-Clause from easydynamics.analysis.analysis import Analysis +from easydynamics.analysis.parameter_analysis import ParameterAnalysis __all__ = [ 'Analysis', + 'ParameterAnalysis', ] diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 52dfd44f..d50517f5 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -8,17 +8,16 @@ import scipp as sc from easyscience.fitting.minimizers.utils import FitResults from easyscience.fitting.multi_fitter import MultiFitter +from plopp.backends.matplotlib.figure import InteractiveFigure from easydynamics.analysis.analysis import Analysis -from easydynamics.base_classes.easydynamics_base import EasyDynamicsBase +from easydynamics.base_classes.easydynamics_modelbase import EasyDynamicsModelBase from easydynamics.sample_model.component_collection import ComponentCollection from easydynamics.sample_model.components.model_component import ModelComponent -from easydynamics.sample_model.diffusion_model.diffusion_model_base import ( - DiffusionModelBase, -) +from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase -class ParameterAnalysis(EasyDynamicsBase): +class ParameterAnalysis(EasyDynamicsModelBase): """ Analysing fitted parameters. """ @@ -27,132 +26,123 @@ def __init__( self, parameters: sc.Dataset | Analysis | None = None, fit_functions: ( - dict[ - str, - ModelComponent - | ComponentCollection - | DiffusionModelBase - | list[ModelComponent | ComponentCollection | DiffusionModelBase] - | None, - ] - | None + dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None ) = None, - # fit_settings: ParameterAnalysisFitSettings | None = None, fit_settings: dict[str, str | list[str]] | None = None, - display_name: str | None = "MyAnalysis", + display_name: str | None = 'ParameterAnalysis', unique_name: str | None = None, ) -> None: """ - Initialize the AnalysisBase. + Initialize the ParameterAnalysis. Parameters ---------- - display_name : str | None, default='MyAnalysis' + parameters : sc.Dataset | Analysis | None, default=None + The parameters to analyze. Can be provided as a sc.Dataset or as an Analysis (in which + case the parameters will be extracted from the Analysis). + fit_functions : dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None, default=None + Dictionary mapping parameter names to fit functions. The fit functions can be provided + as ModelComponents, ComponentCollections, or DiffusionModelBase objects. + fit_settings : dict[str, str | list[str]] | None, default=None + A dictionary mapping parameter names to fit settings. The fit settings can be provided + as strings or lists of strings. If None, default fit settings are used. + display_name : str | None, default="ParameterAnalysis" Display name of the analysis. unique_name : str | None, default=None Unique name of the analysis. If None, a unique name is automatically generated. By default, None. - - Raises - ------ """ super().__init__(display_name=display_name, unique_name=unique_name) - # Check parameters - if parameters is not None and not isinstance( - parameters, (sc.Dataset, Analysis) - ): - raise TypeError("parameters must be an sc.Dataset, an Analysis, or None.") + self._parameters = self._verify_parameters(parameters) + self._fit_settings = self._verify_fit_settings(fit_settings) + self._fit_functions = self._verify_fit_functions(fit_functions) - if isinstance(parameters, Analysis): - self._parameters = parameters.parameters_to_dataset() - else: - self._parameters = parameters + self._prepare_fit_functions_and_parameter_names() - # Check fit settings - if fit_settings is None: - fit_settings = {} + ############# + # Properties + ############# + @property + def parameters(self) -> sc.Dataset | None: + """ + Get the parameters for the parameter analysis. - if not isinstance(fit_settings, dict): - raise TypeError( - "fit_settings must be a dictionary of fit settings or None." - ) + Returns + ------- + sc.Dataset | None + The parameters for the parameter analysis. + """ + return self._parameters - for key, value in fit_settings.items(): - if not isinstance(key, str): - raise TypeError("All keys in fit_settings must be strings.") - if not isinstance(value, (str, list)): - raise TypeError( - "All values in fit_settings must be strings or lists of strings." - ) - if isinstance(value, list) and not all( - isinstance(item, str) for item in value - ): - raise TypeError("All items in lists in fit_settings must be strings.") + @parameters.setter + def parameters(self, value: sc.Dataset | Analysis | None) -> None: + """ + Set the parameters for the parameter analysis. - self._fit_settings = fit_settings + Parameters + ---------- + value : sc.Dataset | Analysis | None + The new parameters for the parameter analysis. + """ + self._parameters = self._verify_parameters(value) + self._prepare_fit_functions_and_parameter_names() - if fit_functions is None: - fit_functions = {} + @property + def fit_functions( + self, + ) -> dict[str, ModelComponent | ComponentCollection | DiffusionModelBase]: + """ + Get the fit functions for the parameter analysis. - if not isinstance(fit_functions, dict): - raise TypeError( - "fit_functions must be a dictionary mapping parameter names to fit functions." - ) + Returns + ------- + dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] + The fit functions for the parameter analysis. + """ + return self._fit_functions - for name, func in fit_functions.items(): - if not isinstance(name, str): - raise TypeError("All keys in fit_functions must be strings.") - if not isinstance( - func, - ( - ModelComponent, - ComponentCollection, - DiffusionModelBase, - ), - ): - raise TypeError( - "All values in fit_functions must be a ModelComponent, a ComponentCollection, " - "or a DiffusionModelBase." - ) - self._fit_functions = fit_functions + @fit_functions.setter + def fit_functions( + self, + value: (dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None), + ) -> None: + """ + Set the fit functions for the parameter analysis. + Parameters + ---------- + value : dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None + The new fit functions for the parameter analysis. + """ + self._fit_functions = self._verify_fit_functions(value) self._prepare_fit_functions_and_parameter_names() - ############# - # Properties - ############# @property - def fit_settings(self) -> ParameterAnalysisFitSettings: + def fit_settings(self) -> dict[str, str | list[str]]: """ Get the fit settings for the parameter analysis. Returns ------- - ParameterAnalysisFitSettings + dict[str, str | list[str]] The fit settings for the parameter analysis. """ return self._fit_settings @fit_settings.setter - def fit_settings(self, value: ParameterAnalysisFitSettings) -> None: + def fit_settings(self, value: dict[str, str | list[str]]) -> None: """ Set the fit settings for the parameter analysis. Parameters ---------- - value : ParameterAnalysisFitSettings + value : dict[str, str | list[str]] The new fit settings for the parameter analysis. - - Raises - ------ - TypeError - If value is not a ParameterAnalysisFitSettings. """ - if not isinstance(value, ParameterAnalysisFitSettings): - raise TypeError("fit_settings must be a ParameterAnalysisFitSettings.") - self._fit_settings = value + self._fit_settings = self._verify_fit_settings(value) + self._prepare_fit_functions_and_parameter_names() ############# # Other methods @@ -191,17 +181,28 @@ def fit(self) -> FitResults: def plot( self, names: str | list[str] | None = None, **kwargs: dict[str, Any] - ) -> None: + ) -> InteractiveFigure: """ Plot the parameters and fit results. Parameters ---------- - names : str | list[str] | None, optional + names : str | list[str] | None, default=None The names of the parameters to plot. If None, all parameters are plotted. **kwargs : dict[str, Any] Additional keyword arguments to pass to the plotting function. + + Returns + ------- + InteractiveFigure + An interactive figure containing the plots of the parameters and fit results. + + Raises + ------ + ValueError + If any of the specified parameter names are not found in the parameters DataSet. """ + if names is None: names = self._expanded_parameter_names elif isinstance(names, str): @@ -209,27 +210,25 @@ def plot( for name in names: if name not in self._parameters: - raise ValueError( - f"Parameter name '{name}' not found in parameters DataSet." - ) + raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") + + # Handle kwargs here. Need to update names with display names, etc. data = sc.Dataset(coords=self._parameters.coords) for name in names: data[name] = self._parameters[name] - x = self._parameters.coords["Q"] + x = self._parameters.coords['Q'] fit_arrays = {} for name, func in zip(names, self._fit_function_callables, strict=True): fit_values = func(x.values) - fit_arrays[name + " fit"] = sc.DataArray( - data=sc.array( - dims=["Q"], values=fit_values, unit=self._parameters[name].unit - ), - coords={"Q": x}, + fit_arrays[name + ' fit'] = sc.DataArray( + data=sc.array(dims=['Q'], values=fit_values, unit=self._parameters[name].unit), + coords={'Q': x}, ) fit_dataset = sc.Dataset(fit_arrays) @@ -238,6 +237,183 @@ def plot( return pp.plot(full_dataset, **kwargs) + def get_all_variables(self) -> list: + """ + Get all variables from the fit functions. + + Returns + ------- + list + A list of all variables from the fit functions. + """ + variables = [] + for fit_funcs in self.fit_functions.values(): + variables.extend(fit_funcs.get_all_variables()) + return variables + + ############# + # Private methods: verification and preparation + ############# + + def _verify_parameters(self, parameters: sc.Dataset | Analysis | None) -> sc.Dataset | None: + """ + Verify the parameters input and convert it to a sc.Dataset if it's an Analysis. + + Parameters + ---------- + parameters : sc.Dataset | Analysis | None + The parameters to verify. + + Returns + ------- + sc.Dataset | None + The verified parameters as a sc.Dataset, or None if no parameters were provided. + + Raises + ------ + TypeError + If parameters is not a sc.Dataset, an Analysis, or None. + """ + if parameters is not None and not isinstance(parameters, (sc.Dataset, Analysis)): + raise TypeError('parameters must be an sc.Dataset, an Analysis, or None.') + + if isinstance(parameters, Analysis): + verified_parameters = parameters.parameters_to_dataset() + else: + verified_parameters = parameters + return verified_parameters + + def _verify_fit_settings( + self, fit_settings: dict[str, str | list[str]] | None + ) -> dict[str, str | list[str]]: + """ + Verify the fit settings input. + + Parameters + ---------- + fit_settings : dict[str, str | list[str]] | None + The fit settings to verify. + + Returns + ------- + dict[str, str | list[str]] + The verified fit settings. + + Raises + ------ + TypeError + If fit_settings is not a dictionary or None. If any key in fit_settings is not a + string. If any value in fit_settings is not a string or a list of strings. If any item + in any list in fit_settings is not a string. + """ + if fit_settings is None: + fit_settings = {} + + if not isinstance(fit_settings, dict): + raise TypeError('fit_settings must be a dictionary of fit settings or None.') + + for key, value in fit_settings.items(): + if not isinstance(key, str): + raise TypeError('All keys in fit_settings must be strings.') + if not isinstance(value, (str, list)): + raise TypeError('All values in fit_settings must be strings or lists of strings.') + if isinstance(value, list) and not all(isinstance(item, str) for item in value): + raise TypeError('All items in lists in fit_settings must be strings.') + return fit_settings + + def _verify_fit_functions( + self, + fit_functions: ( + dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None + ), + ) -> dict[str, ModelComponent | ComponentCollection | DiffusionModelBase]: + """ + Verify the fit functions input. + + Parameters + ---------- + fit_functions : dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None + The fit functions to verify. + + Returns + ------- + dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] + The verified fit functions. + + Raises + ------ + TypeError + If fit_functions is not a dictionary or None. + """ + + if fit_functions is None: + fit_functions = {} + + if not isinstance(fit_functions, dict): + raise TypeError( + 'fit_functions must be a dictionary mapping parameter names to fit functions.' + ) + + for name, func in fit_functions.items(): + if not isinstance(name, str): + raise TypeError('All keys in fit_functions must be strings.') + if not isinstance( + func, + ( + ModelComponent, + ComponentCollection, + DiffusionModelBase, + ), + ): + raise TypeError( + 'All values in fit_functions must be a ModelComponent, a ComponentCollection, ' + 'or a DiffusionModelBase.' + ) + return fit_functions + + def _prepare_fit_functions_and_parameter_names(self) -> None: + """ + Make a list of fit functions callables, fit objects and parameter names, expanding + diffusion models into their parameters if necessary. Updates the following attributes: + - self._fit_function_callables: A list of callables corresponding to the fit functions. + - self._fit_objects: A list of the original fit objects corresponding to the fit functions. + - self._parameter_names: A list of the original parameter names corresponding to the fit + functions. + - self._expanded_parameter_names: A list of the expanded parameter names corresponding to + the fit functions, where diffusion models are expanded into their parameters + (e.g. "D area", "D width" for a diffusion model "D"). + + Raises + ------ + ValueError + If any parameter name in fit_functions is not found in the parameters DataSet. + """ + fit_function_callables = [] + fit_objects = [] + expanded_parameter_names = [] + for name, func in self._fit_functions.items(): + if isinstance(func, DiffusionModelBase): + fit_funcs, fit_objs = self._diffusion_model_to_fit_functions(name, func) + fit_function_callables.extend(fit_funcs) + fit_objects.extend(fit_objs) + expanded_parameter_names.extend(self._get_diffusion_model_parameter_names(name)) + elif isinstance(func, (ModelComponent, ComponentCollection)): + fit_function_callables.append(self._components_to_fit_function(func)) + fit_objects.append(func) + expanded_parameter_names.append(name) + self._fit_function_callables = fit_function_callables + self._fit_objects = fit_objects + self._parameter_names = list(self._fit_functions.keys()) + self._expanded_parameter_names = expanded_parameter_names + + # Check that all names are in the DataSet + if self._parameters is not None: + for name in self._expanded_parameter_names: + if name not in self._parameters: + raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") + + return + ############# # Private methods ############# @@ -274,11 +450,11 @@ def _diffusion_model_to_fit_functions( if isinstance(fit_setting, str): fit_setting = [fit_setting] - if "area" in fit_setting: + if 'area' in fit_setting: fit_functions.append(self._make_area_function(diffusion_model)) fit_objects.append(diffusion_model) - if "width" in fit_setting: + if 'width' in fit_setting: fit_functions.append(self._make_width_function(diffusion_model)) fit_objects.append(diffusion_model) else: @@ -300,6 +476,11 @@ def _make_area_function(model: DiffusionModelBase) -> callable: ---------- model : DiffusionModelBase The diffusion model to make the fit function for. + + Returns + ------- + callable + A fit function corresponding to the area of the diffusion model. """ def fit_function( @@ -319,6 +500,11 @@ def _make_width_function(model: DiffusionModelBase) -> callable: ---------- model : DiffusionModelBase The diffusion model to make the fit function for. + + Returns + ------- + callable + A fit function corresponding to the width of the diffusion model. """ def fit_function( @@ -373,8 +559,8 @@ def _get_diffusion_model_parameter_names( Parameters ---------- - diffusion_model : DiffusionModelBase - The diffusion model to get parameter names from. + parameter_name : str + The name of the parameter to get names for. Returns ------- @@ -388,53 +574,17 @@ def _get_diffusion_model_parameter_names( if isinstance(fit_setting, str): fit_setting = [fit_setting] - if "area" in fit_setting: - parameter_names.append(parameter_name + " area") + if 'area' in fit_setting: + parameter_names.append(parameter_name + ' area') - if "width" in fit_setting: - parameter_names.append(parameter_name + " width") + if 'width' in fit_setting: + parameter_names.append(parameter_name + ' width') else: - parameter_names.append(parameter_name + " area") - parameter_names.append(parameter_name + " width") + parameter_names.append(parameter_name + ' area') + parameter_names.append(parameter_name + ' width') return parameter_names - def _prepare_fit_functions_and_parameter_names(self) -> None: - """ - Make a list of fit functions callables, fit objects and - parameter names, expanding diffusion models into their - parameters if necessary. - """ - fit_function_callables = [] - fit_objects = [] - expanded_parameter_names = [] - for name, func in self._fit_functions.items(): - if isinstance(func, DiffusionModelBase): - fit_funcs, fit_objs = self._diffusion_model_to_fit_functions(name, func) - fit_function_callables.extend(fit_funcs) - fit_objects.extend(fit_objs) - expanded_parameter_names.extend( - self._get_diffusion_model_parameter_names(name) - ) - elif isinstance(func, (ModelComponent, ComponentCollection)): - fit_function_callables.append(self._components_to_fit_function(func)) - fit_objects.append(func) - expanded_parameter_names.append(name) - self._fit_function_callables = fit_function_callables - self._fit_objects = fit_objects - self._parameter_names = list(self._fit_functions.keys()) - self._expanded_parameter_names = expanded_parameter_names - - # Check that all names are in the DataSet - if self._parameters is not None: - for name in self._expanded_parameter_names: - if name not in self._parameters: - raise ValueError( - f"Parameter name '{name}' not found in parameters DataSet." - ) - - return - def _get_xyweight_from_dataset( self, parameter_name: str ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: @@ -457,13 +607,13 @@ def _get_xyweight_from_dataset( If the parameter name is not found in the parameters DataSet. """ if self._parameters is None: - raise ValueError("No parameters DataSet provided.") + raise ValueError('No parameters DataSet provided.') if parameter_name not in self._parameters: - raise ValueError( - f"Parameter name '{parameter_name}' not found in parameters DataSet." - ) + raise ValueError(f"Parameter name '{parameter_name}' not found in parameters DataSet.") + + # Need to check the variances. return ( - self._parameters[parameter_name].coords["Q"].values, + self._parameters[parameter_name].coords['Q'].values, self._parameters[parameter_name].values, 1 / self._parameters[parameter_name].variances ** 0.5, ) @@ -474,14 +624,14 @@ def _get_xyweight_from_dataset( def __repr__(self) -> str: """ - Return a string representation of the Analysis. + Return a string representation of the ParameterAnalysis. Returns ------- str - A string representation of the Analysis. + A string representation of the ParameterAnalysis. """ return ( - f" {self.__class__.__name__} (display_name={self.display_name}, " - f"unique_name={self.unique_name})" + f' {self.__class__.__name__} (display_name={self.display_name}, ' + f'unique_name={self.unique_name})' ) diff --git a/src/easydynamics/settings/__init__.py b/src/easydynamics/settings/__init__.py index 0867ddad..c401fbce 100644 --- a/src/easydynamics/settings/__init__.py +++ b/src/easydynamics/settings/__init__.py @@ -5,6 +5,6 @@ from easydynamics.settings.detailed_balance_settings import DetailedBalanceSettings __all__ = [ - "ConvolutionSettings", - "DetailedBalanceSettings", + 'ConvolutionSettings', + 'DetailedBalanceSettings', ] From d81e5ee5399af5594119b242384aa4a694264eaa Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Mon, 27 Apr 2026 06:55:45 +0200 Subject: [PATCH 04/19] FIx linting --- src/easydynamics/analysis/parameter_analysis.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index d50517f5..83b64445 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -16,6 +16,8 @@ from easydynamics.sample_model.components.model_component import ModelComponent from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase +FIT_FUNCTION_TYPE = ModelComponent | ComponentCollection | DiffusionModelBase + class ParameterAnalysis(EasyDynamicsModelBase): """ @@ -25,9 +27,7 @@ class ParameterAnalysis(EasyDynamicsModelBase): def __init__( self, parameters: sc.Dataset | Analysis | None = None, - fit_functions: ( - dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None - ) = None, + fit_functions: dict[str, FIT_FUNCTION_TYPE] | None = None, fit_settings: dict[str, str | list[str]] | None = None, display_name: str | None = 'ParameterAnalysis', unique_name: str | None = None, @@ -40,7 +40,7 @@ def __init__( parameters : sc.Dataset | Analysis | None, default=None The parameters to analyze. Can be provided as a sc.Dataset or as an Analysis (in which case the parameters will be extracted from the Analysis). - fit_functions : dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None, default=None + fit_functions : dict[str, FIT_FUNCTION_TYPE] | None, default=None Dictionary mapping parameter names to fit functions. The fit functions can be provided as ModelComponents, ComponentCollections, or DiffusionModelBase objects. fit_settings : dict[str, str | list[str]] | None, default=None From fa201d4e860c3feae177f586c2c90b19a41121f4 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Mon, 27 Apr 2026 15:17:29 +0200 Subject: [PATCH 05/19] Update plotting --- docs/docs/tutorials/tutorial0_basics.ipynb | 2 +- docs/docs/tutorials/tutorial1_brownian.ipynb | 2 +- .../analysis/parameter_analysis.py | 193 +++++++++++++++--- 3 files changed, 166 insertions(+), 31 deletions(-) diff --git a/docs/docs/tutorials/tutorial0_basics.ipynb b/docs/docs/tutorials/tutorial0_basics.ipynb index 1e0daa62..fa7e4ef8 100644 --- a/docs/docs/tutorials/tutorial0_basics.ipynb +++ b/docs/docs/tutorials/tutorial0_basics.ipynb @@ -379,7 +379,7 @@ "metadata": {}, "outputs": [], "source": [ - "fit_func = sm.Polynomial(coefficients=[3.7, -0.5], unit='1/angstrom', display_name='Straight line')\n", + "fit_func = sm.Polynomial(coefficients=[3.7, -0.5], display_name='Straight line')\n", "\n", "parameter_analysis = ParameterAnalysis(\n", " fit_functions={'Gaussian area': fit_func}, parameters=analysis\n", diff --git a/docs/docs/tutorials/tutorial1_brownian.ipynb b/docs/docs/tutorials/tutorial1_brownian.ipynb index 3b70f016..e34a07bb 100644 --- a/docs/docs/tutorials/tutorial1_brownian.ipynb +++ b/docs/docs/tutorials/tutorial1_brownian.ipynb @@ -568,7 +568,7 @@ "source": [ "This is a good result, but we can do better. Now that we know that the quasielastic scattering is well described by a model of diffusion, we can fit this model directly to the data, fitting all $Q$ simultaneously.\n", "\n", - "In addition to this diffusion model, there is still the elastic incoherent scattering.\n", + "In addition to this diffusion model, we will still fit the elastic incoherent scattering.\n", "\n", "We create a new `SampleModel` which as a `DeltaFunction` component for the elastic incoherent scattering and a `BrownianTranslationalDiffusion` diffusion model to describe the rest. We also create a new `BackgroundModel` and `InstrumentModel`." ] diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 83b64445..d95be89b 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +import itertools from typing import Any import numpy as np @@ -8,6 +9,7 @@ import scipp as sc from easyscience.fitting.minimizers.utils import FitResults from easyscience.fitting.multi_fitter import MultiFitter +from matplotlib import rcParams from plopp.backends.matplotlib.figure import InteractiveFigure from easydynamics.analysis.analysis import Analysis @@ -15,6 +17,7 @@ from easydynamics.sample_model.component_collection import ComponentCollection from easydynamics.sample_model.components.model_component import ModelComponent from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase +from easydynamics.utils.utils import _in_notebook FIT_FUNCTION_TYPE = ModelComponent | ComponentCollection | DiffusionModelBase @@ -46,7 +49,7 @@ def __init__( fit_settings : dict[str, str | list[str]] | None, default=None A dictionary mapping parameter names to fit settings. The fit settings can be provided as strings or lists of strings. If None, default fit settings are used. - display_name : str | None, default="ParameterAnalysis" + display_name : str | None, default='ParameterAnalysis' Display name of the analysis. unique_name : str | None, default=None Unique name of the analysis. If None, a unique name is automatically generated. By @@ -59,7 +62,13 @@ def __init__( self._fit_settings = self._verify_fit_settings(fit_settings) self._fit_functions = self._verify_fit_functions(fit_functions) - self._prepare_fit_functions_and_parameter_names() + ( + self._fit_function_callables, + self._fit_objects, + self._fit_function_display_names, + self._parameter_names, + self._expanded_parameter_names, + ) = self._prepare_fit_functions_and_parameter_names() ############# # Properties @@ -87,7 +96,13 @@ def parameters(self, value: sc.Dataset | Analysis | None) -> None: The new parameters for the parameter analysis. """ self._parameters = self._verify_parameters(value) - self._prepare_fit_functions_and_parameter_names() + ( + self._fit_function_callables, + self._fit_objects, + self._fit_function_display_names, + self._parameter_names, + self._expanded_parameter_names, + ) = self._prepare_fit_functions_and_parameter_names() @property def fit_functions( @@ -117,7 +132,13 @@ def fit_functions( The new fit functions for the parameter analysis. """ self._fit_functions = self._verify_fit_functions(value) - self._prepare_fit_functions_and_parameter_names() + ( + self._fit_function_callables, + self._fit_objects, + self._fit_function_display_names, + self._parameter_names, + self._expanded_parameter_names, + ) = self._prepare_fit_functions_and_parameter_names() @property def fit_settings(self) -> dict[str, str | list[str]]: @@ -142,7 +163,13 @@ def fit_settings(self, value: dict[str, str | list[str]]) -> None: The new fit settings for the parameter analysis. """ self._fit_settings = self._verify_fit_settings(value) - self._prepare_fit_functions_and_parameter_names() + ( + self._fit_function_callables, + self._fit_objects, + self._fit_function_display_names, + self._parameter_names, + self._expanded_parameter_names, + ) = self._prepare_fit_functions_and_parameter_names() ############# # Other methods @@ -156,8 +183,23 @@ def fit(self) -> FitResults: ------- FitResults The results of the fit + + Raises + ------ + ValueError + If no parameters DataSet is provided. If no fit functions are provided. If no parameter + names are found for the fit functions. """ + if self._parameters is None: + raise ValueError('No parameters DataSet provided.') + + if not self._fit_function_callables: + raise ValueError('No fit functions provided.') + + if not self._parameter_names: + raise ValueError('No parameter names found for fit functions.') + xs = [] ys = [] ws = [] @@ -201,8 +243,16 @@ def plot( ------ ValueError If any of the specified parameter names are not found in the parameters DataSet. + RuntimeError + If plot_data() is called outside of a Jupyter notebook environment. """ + if not _in_notebook(): + raise RuntimeError('plot_data() can only be used in a Jupyter notebook environment.') + + if self._parameters is None: + raise ValueError('No parameters available to plot.') + if names is None: names = self._expanded_parameter_names elif isinstance(names, str): @@ -212,8 +262,6 @@ def plot( if name not in self._parameters: raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") - # Handle kwargs here. Need to update names with display names, etc. - data = sc.Dataset(coords=self._parameters.coords) for name in names: @@ -221,21 +269,58 @@ def plot( x = self._parameters.coords['Q'] + color_cycle = itertools.cycle(rcParams['axes.prop_cycle'].by_key()['color']) + markers = ['o', 's', 'D', '^', 'v', '<', '>'] + marker_cycle = itertools.cycle(markers) + fit_arrays = {} - for name, func in zip(names, self._fit_function_callables, strict=True): + plot_kwargs_defaults = { + 'title': self.display_name, + 'linestyle': {}, + 'marker': {}, + 'color': {}, + 'markerfacecolor': {}, + } + + for name, func, display_name in zip( + names, + self._fit_function_callables, + self._fit_function_display_names, + strict=True, + ): fit_values = func(x.values) - fit_arrays[name + ' fit'] = sc.DataArray( + # Units need to be handled better here. See issue #65 + fit_arrays[display_name] = sc.DataArray( data=sc.array(dims=['Q'], values=fit_values, unit=self._parameters[name].unit), coords={'Q': x}, ) + # Default plot kwargs + color = next(color_cycle) + marker = next(marker_cycle) + + # Data + plot_kwargs_defaults['linestyle'][name] = 'none' + plot_kwargs_defaults['marker'][name] = marker + plot_kwargs_defaults['color'][name] = color + plot_kwargs_defaults['markerfacecolor'][name] = 'none' + + # Fit + plot_kwargs_defaults['linestyle'][display_name] = '--' + plot_kwargs_defaults['marker'][display_name] = None + plot_kwargs_defaults['color'][display_name] = color + plot_kwargs_defaults['markerfacecolor'][display_name] = 'none' + + # Update kwargs with user provided kwargs. + plot_kwargs_defaults.update(kwargs) + fit_dataset = sc.Dataset(fit_arrays) - full_dataset = sc.merge(data, fit_dataset) + full_dataset = sc.merge(fit_dataset, data) - return pp.plot(full_dataset, **kwargs) + return pp.plot(full_dataset, **plot_kwargs_defaults) def get_all_variables(self) -> list: """ @@ -371,11 +456,22 @@ def _verify_fit_functions( ) return fit_functions - def _prepare_fit_functions_and_parameter_names(self) -> None: + def _prepare_fit_functions_and_parameter_names( + self, + ) -> tuple[ + list[callable], + list[FIT_FUNCTION_TYPE], + list[str], + list[str], + list[str], + ]: """ Make a list of fit functions callables, fit objects and parameter names, expanding - diffusion models into their parameters if necessary. Updates the following attributes: + diffusion models into their parameters if necessary. The following attributes are prepared: - self._fit_function_callables: A list of callables corresponding to the fit functions. + - self._fit_function_display_names: A list of display names corresponding to the fit + functions, where diffusion models are expanded into their parameters + (e.g. "D area", "D width" for a diffusion model "D"). - self._fit_objects: A list of the original fit objects corresponding to the fit functions. - self._parameter_names: A list of the original parameter names corresponding to the fit functions. @@ -383,6 +479,13 @@ def _prepare_fit_functions_and_parameter_names(self) -> None: the fit functions, where diffusion models are expanded into their parameters (e.g. "D area", "D width" for a diffusion model "D"). + Returns + ------- + tuple[list[callable], list[FIT_FUNCTION_TYPE], list[str], list[str], list[str]] + A tuple containing the list of fit function callables, the list of fit objects, the + list of fit function display names, the list of original parameter names, and the list + of expanded parameter names. + Raises ------ ValueError @@ -391,28 +494,36 @@ def _prepare_fit_functions_and_parameter_names(self) -> None: fit_function_callables = [] fit_objects = [] expanded_parameter_names = [] + fit_function_display_names = [] for name, func in self._fit_functions.items(): if isinstance(func, DiffusionModelBase): - fit_funcs, fit_objs = self._diffusion_model_to_fit_functions(name, func) + fit_funcs, fit_objs, display_names = self._diffusion_model_to_fit_functions( + name, func + ) fit_function_callables.extend(fit_funcs) fit_objects.extend(fit_objs) - expanded_parameter_names.extend(self._get_diffusion_model_parameter_names(name)) + fit_function_display_names.extend(display_names) + expanded_parameter_names.extend(self._get_modelcomponent_parameter_names(name)) elif isinstance(func, (ModelComponent, ComponentCollection)): fit_function_callables.append(self._components_to_fit_function(func)) fit_objects.append(func) + fit_function_display_names.append(func.display_name) expanded_parameter_names.append(name) - self._fit_function_callables = fit_function_callables - self._fit_objects = fit_objects - self._parameter_names = list(self._fit_functions.keys()) - self._expanded_parameter_names = expanded_parameter_names + parameter_names = list(self._fit_functions.keys()) # Check that all names are in the DataSet if self._parameters is not None: - for name in self._expanded_parameter_names: + for name in expanded_parameter_names: if name not in self._parameters: raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") - return + return ( + fit_function_callables, + fit_objects, + fit_function_display_names, + parameter_names, + expanded_parameter_names, + ) ############# # Private methods @@ -422,7 +533,7 @@ def _diffusion_model_to_fit_functions( self, parameter_name: str, diffusion_model: DiffusionModelBase, - ) -> tuple[list[callable], list[DiffusionModelBase]]: + ) -> tuple[list[callable], list[DiffusionModelBase], list[str]]: """ Convert a DiffusionModelBase to a list of fit functions. @@ -435,8 +546,10 @@ def _diffusion_model_to_fit_functions( Returns ------- - tuple[list[callable], list[DiffusionModelBase]] - A list of fit functions corresponding to the diffusion model. + tuple[list[callable], list[DiffusionModelBase], list[str]] + A list of fit functions corresponding to the diffusion model, a list of the original + diffusion model repeated for each fit function, and a list of display names for the fit + functions. """ # Currently only looks at the area and width of a Lorentzian. @@ -444,6 +557,7 @@ def _diffusion_model_to_fit_functions( fit_functions = [] fit_objects = [] + display_names = [] if parameter_name in self.fit_settings: fit_setting = self.fit_settings[parameter_name] @@ -453,19 +567,24 @@ def _diffusion_model_to_fit_functions( if 'area' in fit_setting: fit_functions.append(self._make_area_function(diffusion_model)) fit_objects.append(diffusion_model) + display_names.append(diffusion_model.display_name + ' area') if 'width' in fit_setting: fit_functions.append(self._make_width_function(diffusion_model)) fit_objects.append(diffusion_model) + display_names.append(diffusion_model.display_name + ' width') else: # If no fit settings are provided for this parameter, fit # both area and width by default. fit_functions.append(self._make_area_function(diffusion_model)) fit_objects.append(diffusion_model) + display_names.append(diffusion_model.display_name + ' area') + fit_functions.append(self._make_width_function(diffusion_model)) fit_objects.append(diffusion_model) + display_names.append(diffusion_model.display_name + ' width') - return fit_functions, fit_objects + return fit_functions, fit_objects, display_names @staticmethod def _make_area_function(model: DiffusionModelBase) -> callable: @@ -550,12 +669,12 @@ def fit_function( return fit_function - def _get_diffusion_model_parameter_names( + def _get_modelcomponent_parameter_names( self, parameter_name: str, ) -> list[str]: """ - Get the parameter names for a diffusion model. + Get the parameter names for a model component. Parameters ---------- @@ -631,7 +750,23 @@ def __repr__(self) -> str: str A string representation of the ParameterAnalysis. """ + cls = self.__class__.__name__ + + n_params = len(self._parameters) if isinstance(self._parameters, sc.Dataset) else 0 + + param_names = ( + list(self._parameters.keys()) if isinstance(self._parameters, sc.Dataset) else None + ) + + fit_keys = list(self._fit_functions.keys()) + return ( - f' {self.__class__.__name__} (display_name={self.display_name}, ' - f'unique_name={self.unique_name})' + f'{cls}(\n' + f' display_name={self.display_name!r},\n' + f' unique_name={self.unique_name!r},\n' + f' n_parameters={n_params},\n' + f' parameter_names={param_names},\n' + f' fit_functions={fit_keys},\n' + f' expanded_parameters={self._expanded_parameter_names}\n' + f')' ) From 3fc6de4b1a65ea942f9022781d6ffdbe8b532ab0 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Mon, 27 Apr 2026 20:22:20 +0200 Subject: [PATCH 06/19] make a dataclass to hold some attributes --- .../analysis/parameter_analysis.py | 111 ++++++------------ .../analysis/parameter_dataclass.py | 37 ++++++ 2 files changed, 76 insertions(+), 72 deletions(-) create mode 100644 src/easydynamics/analysis/parameter_dataclass.py diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index d95be89b..85abc180 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -13,6 +13,7 @@ from plopp.backends.matplotlib.figure import InteractiveFigure from easydynamics.analysis.analysis import Analysis +from easydynamics.analysis.parameter_dataclass import _PreparedFitData from easydynamics.base_classes.easydynamics_modelbase import EasyDynamicsModelBase from easydynamics.sample_model.component_collection import ComponentCollection from easydynamics.sample_model.components.model_component import ModelComponent @@ -62,13 +63,7 @@ def __init__( self._fit_settings = self._verify_fit_settings(fit_settings) self._fit_functions = self._verify_fit_functions(fit_functions) - ( - self._fit_function_callables, - self._fit_objects, - self._fit_function_display_names, - self._parameter_names, - self._expanded_parameter_names, - ) = self._prepare_fit_functions_and_parameter_names() + self._prepared_fit_data = self._prepare_fit_functions_and_parameter_names() ############# # Properties @@ -96,13 +91,7 @@ def parameters(self, value: sc.Dataset | Analysis | None) -> None: The new parameters for the parameter analysis. """ self._parameters = self._verify_parameters(value) - ( - self._fit_function_callables, - self._fit_objects, - self._fit_function_display_names, - self._parameter_names, - self._expanded_parameter_names, - ) = self._prepare_fit_functions_and_parameter_names() + self._prepared_fit_data = self._prepare_fit_functions_and_parameter_names() @property def fit_functions( @@ -132,13 +121,7 @@ def fit_functions( The new fit functions for the parameter analysis. """ self._fit_functions = self._verify_fit_functions(value) - ( - self._fit_function_callables, - self._fit_objects, - self._fit_function_display_names, - self._parameter_names, - self._expanded_parameter_names, - ) = self._prepare_fit_functions_and_parameter_names() + self._prepared_fit_data = self._prepare_fit_functions_and_parameter_names() @property def fit_settings(self) -> dict[str, str | list[str]]: @@ -163,13 +146,7 @@ def fit_settings(self, value: dict[str, str | list[str]]) -> None: The new fit settings for the parameter analysis. """ self._fit_settings = self._verify_fit_settings(value) - ( - self._fit_function_callables, - self._fit_objects, - self._fit_function_display_names, - self._parameter_names, - self._expanded_parameter_names, - ) = self._prepare_fit_functions_and_parameter_names() + self._prepared_fit_data = self._prepare_fit_functions_and_parameter_names() ############# # Other methods @@ -194,25 +171,25 @@ def fit(self) -> FitResults: if self._parameters is None: raise ValueError('No parameters DataSet provided.') - if not self._fit_function_callables: + if not self._prepared_fit_data.fit_function_callables: raise ValueError('No fit functions provided.') - if not self._parameter_names: + if not self._prepared_fit_data.parameter_names: raise ValueError('No parameter names found for fit functions.') xs = [] ys = [] ws = [] - for name in self._expanded_parameter_names: + for name in self._prepared_fit_data.expanded_parameter_names: (x, y, weight) = self._get_xyweight_from_dataset(name) xs.append(x) ys.append(y) ws.append(weight) mf = MultiFitter( - fit_objects=self._fit_objects, - fit_functions=self._fit_function_callables, + fit_objects=self._prepared_fit_data.fit_objects, + fit_functions=self._prepared_fit_data.fit_function_callables, ) return mf.fit( @@ -242,7 +219,8 @@ def plot( Raises ------ ValueError - If any of the specified parameter names are not found in the parameters DataSet. + If any of the specified parameter names are not found in the parameters DataSet. If the + units of the specified parameters are not consistent. RuntimeError If plot_data() is called outside of a Jupyter notebook environment. """ @@ -254,7 +232,7 @@ def plot( raise ValueError('No parameters available to plot.') if names is None: - names = self._expanded_parameter_names + names = self._prepared_fit_data.expanded_parameter_names elif isinstance(names, str): names = [names] @@ -262,13 +240,17 @@ def plot( if name not in self._parameters: raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") + # Make a new Dataset with only the desired parameters. data = sc.Dataset(coords=self._parameters.coords) + units = [self._parameters[name].unit for name in names] + if len(set(units)) != 1: + raise ValueError(f'Units are not consistent, and cannot be plotted together: {units}') + for name in names: data[name] = self._parameters[name] - x = self._parameters.coords['Q'] - + # Create fit arrays and plot kwargs for each parameter and fit function color_cycle = itertools.cycle(rcParams['axes.prop_cycle'].by_key()['color']) markers = ['o', 's', 'D', '^', 'v', '<', '>'] marker_cycle = itertools.cycle(markers) @@ -282,11 +264,12 @@ def plot( 'color': {}, 'markerfacecolor': {}, } + x = self._parameters.coords['Q'] for name, func, display_name in zip( names, - self._fit_function_callables, - self._fit_function_display_names, + self._prepared_fit_data.fit_function_callables, + self._prepared_fit_data.fit_function_display_names, strict=True, ): fit_values = func(x.values) @@ -318,9 +301,9 @@ def plot( fit_dataset = sc.Dataset(fit_arrays) - full_dataset = sc.merge(fit_dataset, data) + data_and_model = sc.merge(fit_dataset, data) - return pp.plot(full_dataset, **plot_kwargs_defaults) + return pp.plot(data_and_model, **plot_kwargs_defaults) def get_all_variables(self) -> list: """ @@ -458,33 +441,17 @@ def _verify_fit_functions( def _prepare_fit_functions_and_parameter_names( self, - ) -> tuple[ - list[callable], - list[FIT_FUNCTION_TYPE], - list[str], - list[str], - list[str], - ]: - """ - Make a list of fit functions callables, fit objects and parameter names, expanding - diffusion models into their parameters if necessary. The following attributes are prepared: - - self._fit_function_callables: A list of callables corresponding to the fit functions. - - self._fit_function_display_names: A list of display names corresponding to the fit - functions, where diffusion models are expanded into their parameters - (e.g. "D area", "D width" for a diffusion model "D"). - - self._fit_objects: A list of the original fit objects corresponding to the fit functions. - - self._parameter_names: A list of the original parameter names corresponding to the fit - functions. - - self._expanded_parameter_names: A list of the expanded parameter names corresponding to - the fit functions, where diffusion models are expanded into their parameters - (e.g. "D area", "D width" for a diffusion model "D"). + ) -> _PreparedFitData: + """ + Prepare the fit functions and parameter names for fitting. + Returns ------- - tuple[list[callable], list[FIT_FUNCTION_TYPE], list[str], list[str], list[str]] - A tuple containing the list of fit function callables, the list of fit objects, the - list of fit function display names, the list of original parameter names, and the list - of expanded parameter names. + _PreparedFitData + A _PreparedFitData object containing the list of fit function callables, the list of + fit objects, the list of fit function display names, the list of original parameter + names, and the list of expanded parameter names. Raises ------ @@ -517,12 +484,12 @@ def _prepare_fit_functions_and_parameter_names( if name not in self._parameters: raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") - return ( - fit_function_callables, - fit_objects, - fit_function_display_names, - parameter_names, - expanded_parameter_names, + return _PreparedFitData( + fit_function_callables=fit_function_callables, + fit_objects=fit_objects, + fit_function_display_names=fit_function_display_names, + parameter_names=parameter_names, + expanded_parameter_names=expanded_parameter_names, ) ############# @@ -767,6 +734,6 @@ def __repr__(self) -> str: f' n_parameters={n_params},\n' f' parameter_names={param_names},\n' f' fit_functions={fit_keys},\n' - f' expanded_parameters={self._expanded_parameter_names}\n' + f' expanded_parameters={self._prepared_fit_data.expanded_parameter_names}\n' f')' ) diff --git a/src/easydynamics/analysis/parameter_dataclass.py b/src/easydynamics/analysis/parameter_dataclass.py new file mode 100644 index 00000000..b5d6016a --- /dev/null +++ b/src/easydynamics/analysis/parameter_dataclass.py @@ -0,0 +1,37 @@ +from dataclasses import dataclass + +from easydynamics.sample_model.component_collection import ComponentCollection +from easydynamics.sample_model.components.model_component import ModelComponent +from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase + +FIT_FUNCTION_TYPE = ModelComponent | ComponentCollection | DiffusionModelBase + + +@dataclass +class _PreparedFitData: + """ + Holds the prepared fit data for parameter analysis. This includes the fit function callables, + fit objects, display names, and parameter names, both original and expanded. + + Attributes + ---------- + fit_function_callables : list[callable] + A list of callables corresponding to the fit functions. + fit_objects : list[FIT_FUNCTION_TYPE] + A list of the original fit objects corresponding to the fit functions. + fit_function_display_names : list[str] + A list of display names corresponding to the fit functions, where diffusion models are + expanded into their parameters (e.g. "D area", "D width" for a diffusion model "D"). + parameter_names : list[str] + A list of the original parameter names corresponding to the fit functions. + expanded_parameter_names : list[str] + A list of the expanded parameter names corresponding to the fit functions, where diffusion + models are expanded into their parameters (e.g. "D area", "D width" for a diffusion model + "D"). + """ + + fit_function_callables: list[callable] + fit_objects: list[FIT_FUNCTION_TYPE] + fit_function_display_names: list[str] + parameter_names: list[str] + expanded_parameter_names: list[str] From 1f7cdf27a79025e8f7c138ea4d43ffdfc592aefb Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Mon, 27 Apr 2026 20:41:17 +0200 Subject: [PATCH 07/19] Some polishing --- .../analysis/parameter_analysis.py | 44 ++++++++++++++----- .../analysis/parameter_dataclass.py | 3 ++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 85abc180..bf98c0c0 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -25,7 +25,13 @@ class ParameterAnalysis(EasyDynamicsModelBase): """ - Analysing fitted parameters. + For analysing fitted parameters. + + Can be used to fit paramters to ModelComponents, ComponentCollections, or DiffusionModelBase + objects, and to plot the parameters and fit results. The parameters to be analyzed can be + provided as a sc.Dataset or directly as an Analysis object. Multiple parameters can be fitted + simultaneously, and the fit functions can be customized for each parameter. For diffusion + models, the area and width can be fitted separately (or not at all) by specifying fit settings. """ def __init__( @@ -244,7 +250,8 @@ def plot( data = sc.Dataset(coords=self._parameters.coords) units = [self._parameters[name].unit for name in names] - if len(set(units)) != 1: + first_unit = units[0] + if any(unit != first_unit for unit in units): raise ValueError(f'Units are not consistent, and cannot be plotted together: {units}') for name in names: @@ -266,12 +273,18 @@ def plot( } x = self._parameters.coords['Q'] - for name, func, display_name in zip( - names, - self._prepared_fit_data.fit_function_callables, - self._prepared_fit_data.fit_function_display_names, - strict=True, - ): + # for name, func, display_name in zip( + # names, + # self._prepared_fit_data.fit_function_callables, + # self._prepared_fit_data.fit_function_display_names, + # strict=True, + # ): + for name in names: + idx = self._prepared_fit_data.expanded_parameter_names.index(name) + + func = self._prepared_fit_data.fit_function_callables[idx] + display_name = self._prepared_fit_data.fit_function_display_names[idx] + fit_values = func(x.values) # Units need to be handled better here. See issue #65 @@ -341,6 +354,8 @@ def _verify_parameters(self, parameters: sc.Dataset | Analysis | None) -> sc.Dat ------ TypeError If parameters is not a sc.Dataset, an Analysis, or None. + ValueError + If parameters is a sc.Dataset but does not have a 'Q' coordinate. """ if parameters is not None and not isinstance(parameters, (sc.Dataset, Analysis)): raise TypeError('parameters must be an sc.Dataset, an Analysis, or None.') @@ -349,6 +364,9 @@ def _verify_parameters(self, parameters: sc.Dataset | Analysis | None) -> sc.Dat verified_parameters = parameters.parameters_to_dataset() else: verified_parameters = parameters + + if verified_parameters is not None and 'Q' not in verified_parameters.coords: + raise ValueError("parameters must have a 'Q' coordinate.") return verified_parameters def _verify_fit_settings( @@ -690,18 +708,22 @@ def _get_xyweight_from_dataset( Raises ------ ValueError - If the parameter name is not found in the parameters DataSet. + If the parameter name is not found in the parameters DataSet. If non-finite weights are + found for the parameter. """ if self._parameters is None: raise ValueError('No parameters DataSet provided.') if parameter_name not in self._parameters: raise ValueError(f"Parameter name '{parameter_name}' not found in parameters DataSet.") - # Need to check the variances. + weight = 1 / self._parameters[parameter_name].variances ** 0.5 + if not np.all(np.isfinite(weight)): + raise ValueError(f"Non-finite weights found for parameter '{parameter_name}'") + return ( self._parameters[parameter_name].coords['Q'].values, self._parameters[parameter_name].values, - 1 / self._parameters[parameter_name].variances ** 0.5, + weight, ) ############# diff --git a/src/easydynamics/analysis/parameter_dataclass.py b/src/easydynamics/analysis/parameter_dataclass.py index b5d6016a..6f31458b 100644 --- a/src/easydynamics/analysis/parameter_dataclass.py +++ b/src/easydynamics/analysis/parameter_dataclass.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from dataclasses import dataclass from easydynamics.sample_model.component_collection import ComponentCollection From bf7c9b09473f190487f386073f740e227103896d Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 28 Apr 2026 10:43:25 +0200 Subject: [PATCH 08/19] Add some tests --- .../analysis/parameter_analysis.py | 178 +++++++++++------- ...eter_dataclass.py => prepared_fit_data.py} | 0 .../analysis/test_parameter_analysis.py | 170 +++++++++++++++++ .../analysis/test_prepared_fit_data.py | 36 ++++ 4 files changed, 312 insertions(+), 72 deletions(-) rename src/easydynamics/analysis/{parameter_dataclass.py => prepared_fit_data.py} (100%) create mode 100644 tests/unit/easydynamics/analysis/test_parameter_analysis.py create mode 100644 tests/unit/easydynamics/analysis/test_prepared_fit_data.py diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index bf98c0c0..954cadab 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -13,11 +13,13 @@ from plopp.backends.matplotlib.figure import InteractiveFigure from easydynamics.analysis.analysis import Analysis -from easydynamics.analysis.parameter_dataclass import _PreparedFitData +from easydynamics.analysis.prepared_fit_data import _PreparedFitData from easydynamics.base_classes.easydynamics_modelbase import EasyDynamicsModelBase from easydynamics.sample_model.component_collection import ComponentCollection from easydynamics.sample_model.components.model_component import ModelComponent -from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase +from easydynamics.sample_model.diffusion_model.diffusion_model_base import ( + DiffusionModelBase, +) from easydynamics.utils.utils import _in_notebook FIT_FUNCTION_TYPE = ModelComponent | ComponentCollection | DiffusionModelBase @@ -39,7 +41,7 @@ def __init__( parameters: sc.Dataset | Analysis | None = None, fit_functions: dict[str, FIT_FUNCTION_TYPE] | None = None, fit_settings: dict[str, str | list[str]] | None = None, - display_name: str | None = 'ParameterAnalysis', + display_name: str | None = "ParameterAnalysis", unique_name: str | None = None, ) -> None: """ @@ -116,7 +118,9 @@ def fit_functions( @fit_functions.setter def fit_functions( self, - value: (dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None), + value: ( + dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None + ), ) -> None: """ Set the fit functions for the parameter analysis. @@ -175,13 +179,13 @@ def fit(self) -> FitResults: """ if self._parameters is None: - raise ValueError('No parameters DataSet provided.') + raise ValueError("No parameters DataSet provided.") if not self._prepared_fit_data.fit_function_callables: - raise ValueError('No fit functions provided.') + raise ValueError("No fit functions provided.") if not self._prepared_fit_data.parameter_names: - raise ValueError('No parameter names found for fit functions.') + raise ValueError("No parameter names found for fit functions.") xs = [] ys = [] @@ -232,10 +236,12 @@ def plot( """ if not _in_notebook(): - raise RuntimeError('plot_data() can only be used in a Jupyter notebook environment.') + raise RuntimeError( + "plot_data() can only be used in a Jupyter notebook environment." + ) if self._parameters is None: - raise ValueError('No parameters available to plot.') + raise ValueError("No parameters available to plot.") if names is None: names = self._prepared_fit_data.expanded_parameter_names @@ -244,7 +250,9 @@ def plot( for name in names: if name not in self._parameters: - raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") + raise ValueError( + f"Parameter name '{name}' not found in parameters DataSet." + ) # Make a new Dataset with only the desired parameters. data = sc.Dataset(coords=self._parameters.coords) @@ -252,26 +260,28 @@ def plot( units = [self._parameters[name].unit for name in names] first_unit = units[0] if any(unit != first_unit for unit in units): - raise ValueError(f'Units are not consistent, and cannot be plotted together: {units}') + raise ValueError( + f"Units are not consistent, and cannot be plotted together: {units}" + ) for name in names: data[name] = self._parameters[name] # Create fit arrays and plot kwargs for each parameter and fit function - color_cycle = itertools.cycle(rcParams['axes.prop_cycle'].by_key()['color']) - markers = ['o', 's', 'D', '^', 'v', '<', '>'] + color_cycle = itertools.cycle(rcParams["axes.prop_cycle"].by_key()["color"]) + markers = ["o", "s", "D", "^", "v", "<", ">"] marker_cycle = itertools.cycle(markers) fit_arrays = {} plot_kwargs_defaults = { - 'title': self.display_name, - 'linestyle': {}, - 'marker': {}, - 'color': {}, - 'markerfacecolor': {}, + "title": self.display_name, + "linestyle": {}, + "marker": {}, + "color": {}, + "markerfacecolor": {}, } - x = self._parameters.coords['Q'] + x = self._parameters.coords["Q"] # for name, func, display_name in zip( # names, @@ -289,8 +299,10 @@ def plot( # Units need to be handled better here. See issue #65 fit_arrays[display_name] = sc.DataArray( - data=sc.array(dims=['Q'], values=fit_values, unit=self._parameters[name].unit), - coords={'Q': x}, + data=sc.array( + dims=["Q"], values=fit_values, unit=self._parameters[name].unit + ), + coords={"Q": x}, ) # Default plot kwargs @@ -298,16 +310,16 @@ def plot( marker = next(marker_cycle) # Data - plot_kwargs_defaults['linestyle'][name] = 'none' - plot_kwargs_defaults['marker'][name] = marker - plot_kwargs_defaults['color'][name] = color - plot_kwargs_defaults['markerfacecolor'][name] = 'none' + plot_kwargs_defaults["linestyle"][name] = "none" + plot_kwargs_defaults["marker"][name] = marker + plot_kwargs_defaults["color"][name] = color + plot_kwargs_defaults["markerfacecolor"][name] = "none" # Fit - plot_kwargs_defaults['linestyle'][display_name] = '--' - plot_kwargs_defaults['marker'][display_name] = None - plot_kwargs_defaults['color'][display_name] = color - plot_kwargs_defaults['markerfacecolor'][display_name] = 'none' + plot_kwargs_defaults["linestyle"][display_name] = "--" + plot_kwargs_defaults["marker"][display_name] = None + plot_kwargs_defaults["color"][display_name] = color + plot_kwargs_defaults["markerfacecolor"][display_name] = "none" # Update kwargs with user provided kwargs. plot_kwargs_defaults.update(kwargs) @@ -336,7 +348,9 @@ def get_all_variables(self) -> list: # Private methods: verification and preparation ############# - def _verify_parameters(self, parameters: sc.Dataset | Analysis | None) -> sc.Dataset | None: + def _verify_parameters( + self, parameters: sc.Dataset | Analysis | None + ) -> sc.Dataset | None: """ Verify the parameters input and convert it to a sc.Dataset if it's an Analysis. @@ -357,15 +371,17 @@ def _verify_parameters(self, parameters: sc.Dataset | Analysis | None) -> sc.Dat ValueError If parameters is a sc.Dataset but does not have a 'Q' coordinate. """ - if parameters is not None and not isinstance(parameters, (sc.Dataset, Analysis)): - raise TypeError('parameters must be an sc.Dataset, an Analysis, or None.') + if parameters is not None and not isinstance( + parameters, (sc.Dataset, Analysis) + ): + raise TypeError("parameters must be an sc.Dataset, an Analysis, or None.") if isinstance(parameters, Analysis): verified_parameters = parameters.parameters_to_dataset() else: verified_parameters = parameters - if verified_parameters is not None and 'Q' not in verified_parameters.coords: + if verified_parameters is not None and "Q" not in verified_parameters.coords: raise ValueError("parameters must have a 'Q' coordinate.") return verified_parameters @@ -396,15 +412,21 @@ def _verify_fit_settings( fit_settings = {} if not isinstance(fit_settings, dict): - raise TypeError('fit_settings must be a dictionary of fit settings or None.') + raise TypeError( + "fit_settings must be a dictionary of fit settings or None." + ) for key, value in fit_settings.items(): if not isinstance(key, str): - raise TypeError('All keys in fit_settings must be strings.') + raise TypeError("All keys in fit_settings must be strings.") if not isinstance(value, (str, list)): - raise TypeError('All values in fit_settings must be strings or lists of strings.') - if isinstance(value, list) and not all(isinstance(item, str) for item in value): - raise TypeError('All items in lists in fit_settings must be strings.') + raise TypeError( + "All values in fit_settings must be strings or lists of strings." + ) + if isinstance(value, list) and not all( + isinstance(item, str) for item in value + ): + raise TypeError("All items in lists in fit_settings must be strings.") return fit_settings def _verify_fit_functions( @@ -437,12 +459,12 @@ def _verify_fit_functions( if not isinstance(fit_functions, dict): raise TypeError( - 'fit_functions must be a dictionary mapping parameter names to fit functions.' + "fit_functions must be a dictionary mapping parameter names to fit functions." ) for name, func in fit_functions.items(): if not isinstance(name, str): - raise TypeError('All keys in fit_functions must be strings.') + raise TypeError("All keys in fit_functions must be strings.") if not isinstance( func, ( @@ -452,8 +474,8 @@ def _verify_fit_functions( ), ): raise TypeError( - 'All values in fit_functions must be a ModelComponent, a ComponentCollection, ' - 'or a DiffusionModelBase.' + "All values in fit_functions must be a ModelComponent, a ComponentCollection, " + "or a DiffusionModelBase." ) return fit_functions @@ -482,13 +504,15 @@ def _prepare_fit_functions_and_parameter_names( fit_function_display_names = [] for name, func in self._fit_functions.items(): if isinstance(func, DiffusionModelBase): - fit_funcs, fit_objs, display_names = self._diffusion_model_to_fit_functions( - name, func + fit_funcs, fit_objs, display_names = ( + self._diffusion_model_to_fit_functions(name, func) ) fit_function_callables.extend(fit_funcs) fit_objects.extend(fit_objs) fit_function_display_names.extend(display_names) - expanded_parameter_names.extend(self._get_modelcomponent_parameter_names(name)) + expanded_parameter_names.extend( + self._get_modelcomponent_parameter_names(name) + ) elif isinstance(func, (ModelComponent, ComponentCollection)): fit_function_callables.append(self._components_to_fit_function(func)) fit_objects.append(func) @@ -500,7 +524,9 @@ def _prepare_fit_functions_and_parameter_names( if self._parameters is not None: for name in expanded_parameter_names: if name not in self._parameters: - raise ValueError(f"Parameter name '{name}' not found in parameters DataSet.") + raise ValueError( + f"Parameter name '{name}' not found in parameters DataSet." + ) return _PreparedFitData( fit_function_callables=fit_function_callables, @@ -549,25 +575,25 @@ def _diffusion_model_to_fit_functions( if isinstance(fit_setting, str): fit_setting = [fit_setting] - if 'area' in fit_setting: + if "area" in fit_setting: fit_functions.append(self._make_area_function(diffusion_model)) fit_objects.append(diffusion_model) - display_names.append(diffusion_model.display_name + ' area') + display_names.append(diffusion_model.display_name + " area") - if 'width' in fit_setting: + if "width" in fit_setting: fit_functions.append(self._make_width_function(diffusion_model)) fit_objects.append(diffusion_model) - display_names.append(diffusion_model.display_name + ' width') + display_names.append(diffusion_model.display_name + " width") else: # If no fit settings are provided for this parameter, fit # both area and width by default. fit_functions.append(self._make_area_function(diffusion_model)) fit_objects.append(diffusion_model) - display_names.append(diffusion_model.display_name + ' area') + display_names.append(diffusion_model.display_name + " area") fit_functions.append(self._make_width_function(diffusion_model)) fit_objects.append(diffusion_model) - display_names.append(diffusion_model.display_name + ' width') + display_names.append(diffusion_model.display_name + " width") return fit_functions, fit_objects, display_names @@ -678,14 +704,14 @@ def _get_modelcomponent_parameter_names( if isinstance(fit_setting, str): fit_setting = [fit_setting] - if 'area' in fit_setting: - parameter_names.append(parameter_name + ' area') + if "area" in fit_setting: + parameter_names.append(parameter_name + " area") - if 'width' in fit_setting: - parameter_names.append(parameter_name + ' width') + if "width" in fit_setting: + parameter_names.append(parameter_name + " width") else: - parameter_names.append(parameter_name + ' area') - parameter_names.append(parameter_name + ' width') + parameter_names.append(parameter_name + " area") + parameter_names.append(parameter_name + " width") return parameter_names @@ -712,16 +738,20 @@ def _get_xyweight_from_dataset( found for the parameter. """ if self._parameters is None: - raise ValueError('No parameters DataSet provided.') + raise ValueError("No parameters DataSet provided.") if parameter_name not in self._parameters: - raise ValueError(f"Parameter name '{parameter_name}' not found in parameters DataSet.") + raise ValueError( + f"Parameter name '{parameter_name}' not found in parameters DataSet." + ) weight = 1 / self._parameters[parameter_name].variances ** 0.5 if not np.all(np.isfinite(weight)): - raise ValueError(f"Non-finite weights found for parameter '{parameter_name}'") + raise ValueError( + f"Non-finite weights found for parameter '{parameter_name}'" + ) return ( - self._parameters[parameter_name].coords['Q'].values, + self._parameters[parameter_name].coords["Q"].values, self._parameters[parameter_name].values, weight, ) @@ -741,21 +771,25 @@ def __repr__(self) -> str: """ cls = self.__class__.__name__ - n_params = len(self._parameters) if isinstance(self._parameters, sc.Dataset) else 0 + n_params = ( + len(self._parameters) if isinstance(self._parameters, sc.Dataset) else 0 + ) param_names = ( - list(self._parameters.keys()) if isinstance(self._parameters, sc.Dataset) else None + list(self._parameters.keys()) + if isinstance(self._parameters, sc.Dataset) + else None ) fit_keys = list(self._fit_functions.keys()) return ( - f'{cls}(\n' - f' display_name={self.display_name!r},\n' - f' unique_name={self.unique_name!r},\n' - f' n_parameters={n_params},\n' - f' parameter_names={param_names},\n' - f' fit_functions={fit_keys},\n' - f' expanded_parameters={self._prepared_fit_data.expanded_parameter_names}\n' - f')' + f"{cls}(\n" + f" display_name={self.display_name!r},\n" + f" unique_name={self.unique_name!r},\n" + f" n_parameters={n_params},\n" + f" parameter_names={param_names},\n" + f" fit_functions={fit_keys},\n" + f" expanded_parameters={self._prepared_fit_data.expanded_parameter_names}\n" + f")" ) diff --git a/src/easydynamics/analysis/parameter_dataclass.py b/src/easydynamics/analysis/prepared_fit_data.py similarity index 100% rename from src/easydynamics/analysis/parameter_dataclass.py rename to src/easydynamics/analysis/prepared_fit_data.py diff --git a/tests/unit/easydynamics/analysis/test_parameter_analysis.py b/tests/unit/easydynamics/analysis/test_parameter_analysis.py new file mode 100644 index 00000000..c89549d7 --- /dev/null +++ b/tests/unit/easydynamics/analysis/test_parameter_analysis.py @@ -0,0 +1,170 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +import pytest +import scipp as sc + +from easydynamics.analysis.parameter_analysis import ParameterAnalysis +from easydynamics.sample_model.component_collection import ComponentCollection +from easydynamics.sample_model.components import Gaussian +from easydynamics.sample_model.components import Lorentzian +from easydynamics.sample_model.diffusion_model import BrownianTranslationalDiffusion + + +class TestParameterAnalysis: + @pytest.fixture + def dataset(self): + Q = sc.array(dims=["Q"], values=[0.1, 0.2]) + return sc.Dataset( + data={ + "parameter1": sc.DataArray( + data=sc.array( + dims=["Q"], values=[1.0, 2.0], variances=[0.1, 0.2], unit="meV" + ), + coords={"Q": Q}, + ), + "parameter2": sc.DataArray( + data=sc.array( + dims=["Q"], + values=[1.5, 2.5], + variances=[0.15, 0.25], + unit="1/meV", + ), + coords={"Q": Q}, + ), + "parameter3 area": sc.DataArray( + data=sc.array( + dims=["Q"], values=[4.0, 5.0], variances=[0.3, 0.5], unit="meV" + ), + coords={"Q": Q}, + ), + "parameter3 width": sc.DataArray( + data=sc.array( + dims=["Q"], values=[6.0, 7.0], variances=[0.6, 0.7], unit="meV" + ), + coords={"Q": Q}, + ), + } + ) + + @pytest.fixture + def parameter_analysis(self, dataset): + func1 = Gaussian() + func2 = Lorentzian() + return ParameterAnalysis( + parameters=dataset, + fit_functions={"parameter1": func1, "parameter3 area": func2}, + ) + + @pytest.fixture + def parameter_analysis_diffusion(self, dataset): + func = BrownianTranslationalDiffusion() + return ParameterAnalysis( + parameters=dataset, + fit_functions={"parameter3": func}, + fit_settings={"parameter3": ["area", "width"]}, + ) + + @pytest.fixture + def parameter_analysis_diffusion_and_componentcollection(self, dataset): + func1 = Gaussian() + func2 = Lorentzian() + funcs = ComponentCollection() + funcs.append_component(func1) + funcs.append_component(func2) + + func3 = BrownianTranslationalDiffusion() + return ParameterAnalysis( + parameters=dataset, + fit_functions={"parameter1": funcs, "parameter3": func3}, + fit_settings={"parameter3": ["area", "width"]}, + ) + + def test_parameter_analysis_initialization(self, parameter_analysis): + # WHEN THEN EXPECT + assert isinstance(parameter_analysis, ParameterAnalysis) + + # Parameters + assert isinstance(parameter_analysis.parameters, sc.Dataset) + assert set(parameter_analysis.parameters.keys()) == { + "parameter1", + "parameter2", + "parameter3 area", + "parameter3 width", + } + + # Fit functions + assert isinstance(parameter_analysis.fit_functions, dict) + assert set(parameter_analysis.fit_functions.keys()) == { + "parameter1", + "parameter3 area", + } + + # Fit settings default + assert isinstance(parameter_analysis.fit_settings, dict) + assert parameter_analysis.fit_settings == {} + + # Prepared fit data + prepared = parameter_analysis._prepared_fit_data + assert isinstance(prepared.fit_function_callables, list) + assert isinstance(prepared.fit_objects, list) + assert isinstance(prepared.fit_function_display_names, list) + assert isinstance(prepared.parameter_names, list) + assert isinstance(prepared.expanded_parameter_names, list) + + # Consistency checks + n_funcs = len(prepared.fit_function_callables) + assert len(prepared.fit_objects) == n_funcs + assert len(prepared.fit_function_display_names) == n_funcs + + # Parameter names should match input keys + assert prepared.parameter_names == ["parameter1", "parameter3 area"] + + # Expanded names should match what exists in dataset + for name in prepared.expanded_parameter_names: + assert name in parameter_analysis.parameters + + def test_parameter_property(self, parameter_analysis): + # WHEN + parameters = parameter_analysis.parameters + + # THEN EXPECT + assert isinstance(parameters, sc.Dataset) + assert set(parameters.keys()) == { + "parameter1", + "parameter2", + "parameter3 area", + "parameter3 width", + } + + # WHEN + Q = sc.array(dims=["Q"], values=[0.1, 0.2]) + new_data = sc.Dataset( + data={ + "parameter4": sc.DataArray( + data=sc.array( + dims=["Q"], + values=[71.0, 12.0], + variances=[1.1, 2.2], + unit="meV", + ), + coords={"Q": Q}, + ), + "parameter5": sc.DataArray( + data=sc.array( + dims=["Q"], + values=[8.5, 0.5], + variances=[2.15, 1.25], + unit="1/meV", + ), + coords={"Q": Q}, + ), + } + ) + + # THEN + parameter_analysis.parameters = new_data + + # EXPECT + assert parameter_analysis.parameters is new_data diff --git a/tests/unit/easydynamics/analysis/test_prepared_fit_data.py b/tests/unit/easydynamics/analysis/test_prepared_fit_data.py new file mode 100644 index 00000000..3d7a1e8b --- /dev/null +++ b/tests/unit/easydynamics/analysis/test_prepared_fit_data.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +import pytest + +from easydynamics.analysis.prepared_fit_data import _PreparedFitData + + +class TestPreparedFitData: + @pytest.fixture + def dummy_callable(self): + def f(x): + return x + + return f + + @pytest.fixture + def prepared_data(self, dummy_callable): + return _PreparedFitData( + fit_function_callables=[dummy_callable], + fit_objects=[object()], + fit_function_display_names=["f"], + parameter_names=["p"], + expanded_parameter_names=["p"], + ) + + def test_initialization(self, prepared_data, dummy_callable): + assert isinstance(prepared_data.fit_function_callables, list) + assert isinstance(prepared_data.fit_objects, list) + assert isinstance(prepared_data.fit_function_display_names, list) + assert isinstance(prepared_data.parameter_names, list) + assert isinstance(prepared_data.expanded_parameter_names, list) + + assert prepared_data.fit_function_callables[0] is dummy_callable + assert callable(prepared_data.fit_function_callables[0]) From f90845ce165dcc60c5c18a378a15072dfc6aa1fb Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 28 Apr 2026 14:33:23 +0200 Subject: [PATCH 09/19] simplify fitting --- docs/docs/tutorials/tutorial0_basics.ipynb | 8 +- .../tutorials/tutorial0_more_advanced.ipynb | 13 +- docs/docs/tutorials/tutorial1_brownian.ipynb | 14 +- src/easydynamics/__init__.py | 2 + src/easydynamics/analysis/fit_bindings.py | 82 ++ .../analysis/parameter_analysis.py | 699 +++++------------- .../analysis/test_parameter_analysis.py | 84 +-- .../analysis/test_prepared_fit_data.py | 6 +- 8 files changed, 333 insertions(+), 575 deletions(-) create mode 100644 src/easydynamics/analysis/fit_bindings.py diff --git a/docs/docs/tutorials/tutorial0_basics.ipynb b/docs/docs/tutorials/tutorial0_basics.ipynb index fa7e4ef8..15f2aea6 100644 --- a/docs/docs/tutorials/tutorial0_basics.ipynb +++ b/docs/docs/tutorials/tutorial0_basics.ipynb @@ -25,6 +25,7 @@ "import easydynamics.sample_model as sm\n", "from easydynamics.analysis import Analysis\n", "from easydynamics.analysis import ParameterAnalysis\n", + "from easydynamics.analysis.parameter_analysis import FitBinding\n", "\n", "# Make the plots interactive\n", "%matplotlib widget" @@ -369,7 +370,7 @@ "id": "842c1f01", "metadata": {}, "source": [ - "The final step in this tutorial is to fit the are of the `Gaussian` to a straight line. For this, we use the `ParameterAnalysis` class. We create a `Polynomial` with two coefficients for the fit function. We pass a dictionary with the name of the parameter we want to fit as the key (in this case, `Gaussian area`), and the fit function as the item. We also pass our `Analysis` object." + "The final step in this tutorial is to fit the are of the `Gaussian` to a straight line. For this, we use the `ParameterAnalysis` class. We create a `Polynomial` with two coefficients for the fit function. We create a `FitBinding`, telling the class we want to fit the parameter named `Gaussian area` with the fit function that we define." ] }, { @@ -381,8 +382,11 @@ "source": [ "fit_func = sm.Polynomial(coefficients=[3.7, -0.5], display_name='Straight line')\n", "\n", + "binding = FitBinding(parameter_name='Gaussian area', model=fit_func)\n", + "\n", "parameter_analysis = ParameterAnalysis(\n", - " fit_functions={'Gaussian area': fit_func}, parameters=analysis\n", + " parameters=analysis,\n", + " bindings=[binding],\n", ")" ] }, diff --git a/docs/docs/tutorials/tutorial0_more_advanced.ipynb b/docs/docs/tutorials/tutorial0_more_advanced.ipynb index b0af166b..375bf7cc 100644 --- a/docs/docs/tutorials/tutorial0_more_advanced.ipynb +++ b/docs/docs/tutorials/tutorial0_more_advanced.ipynb @@ -320,14 +320,17 @@ " coefficients=[1.4, 0.1], unit='1/angstrom', display_name='DHO center fit'\n", ")\n", "\n", + "binding1 = edyn.FitBinding(parameter_name='Gaussian area', model=gauss_fit_func)\n", + "\n", + "binding2 = edyn.FitBinding(parameter_name='DHO area', model=dho_area_fit_func)\n", + "\n", + "binding3 = edyn.FitBinding(parameter_name='DHO center', model=dho_center_fit_func)\n", + "\n", "parameter_analysis = edyn.ParameterAnalysis(\n", - " fit_functions={\n", - " 'Gaussian area': gauss_fit_func,\n", - " 'DHO area': dho_area_fit_func,\n", - " 'DHO center': dho_center_fit_func,\n", - " },\n", " parameters=analysis,\n", + " bindings=[binding1, binding2, binding3],\n", ")\n", + "\n", "parameter_analysis.plot()" ] }, diff --git a/docs/docs/tutorials/tutorial1_brownian.ipynb b/docs/docs/tutorials/tutorial1_brownian.ipynb index e34a07bb..58eede4d 100644 --- a/docs/docs/tutorials/tutorial1_brownian.ipynb +++ b/docs/docs/tutorials/tutorial1_brownian.ipynb @@ -24,6 +24,7 @@ "import pooch\n", "\n", "from easydynamics.analysis.analysis import Analysis\n", + "from easydynamics.analysis.parameter_analysis import FitBinding\n", "from easydynamics.analysis.parameter_analysis import ParameterAnalysis\n", "from easydynamics.experiment import Experiment\n", "from easydynamics.sample_model import BrownianTranslationalDiffusion\n", @@ -485,7 +486,7 @@ "$$\n", "where $\\Gamma(Q) = D Q^2$ and $D$ is the diffusion coefficient. $S$ is an overall scale.\n", "\n", - "To fit the Brownian translational diffusion model to the data, we use the `ParameterAnalysis`. This time, we wish to fit the `Lorentzian`, and we wish to fit both its area (scale, $S$) and width to the diffusion model. We do this by setting `fit_settings` using a dictionary:" + "To fit the Brownian translational diffusion model to the data, we use the `ParameterAnalysis`. This time, we wish to fit the `Lorentzian`, and we wish to fit both its area (scale, $S$) and width to the diffusion model. We do this by creating a `FitBinding`, saying we want to fit both the area an width of the component called `Lorentzian`" ] }, { @@ -499,10 +500,15 @@ " display_name='Brownian Translational Diffusion', diffusion_coefficient=2.4e-9, scale=0.5\n", ")\n", "\n", + "binding = FitBinding(\n", + " parameter_name='Lorentzian',\n", + " model=brownian_diffusion_model,\n", + " modes=['area', 'width'],\n", + ")\n", + "\n", "parameter_analysis = ParameterAnalysis(\n", - " fit_functions={'Lorentzian': brownian_diffusion_model},\n", - " fit_settings={'Lorentzian': ['area', 'width']},\n", " parameters=diffusion_analysis,\n", + " bindings=[binding],\n", ")" ] }, @@ -554,7 +560,7 @@ { "cell_type": "code", "execution_count": null, - "id": "313cab16", + "id": "7c47bcd4", "metadata": {}, "outputs": [], "source": [ diff --git a/src/easydynamics/__init__.py b/src/easydynamics/__init__.py index 86979047..93c7084c 100644 --- a/src/easydynamics/__init__.py +++ b/src/easydynamics/__init__.py @@ -3,6 +3,7 @@ """EasyDynamics library.""" from easydynamics.analysis import Analysis +from easydynamics.analysis.fit_bindings import FitBinding from easydynamics.analysis.parameter_analysis import ParameterAnalysis from easydynamics.experiment import Experiment from easydynamics.settings.convolution_settings import ConvolutionSettings @@ -13,5 +14,6 @@ 'ConvolutionSettings', 'DetailedBalanceSettings', 'Experiment', + 'FitBinding', 'ParameterAnalysis', ] diff --git a/src/easydynamics/analysis/fit_bindings.py b/src/easydynamics/analysis/fit_bindings.py new file mode 100644 index 00000000..9331c232 --- /dev/null +++ b/src/easydynamics/analysis/fit_bindings.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase + +if TYPE_CHECKING: + from collections.abc import Callable + + from easydynamics.sample_model.component_collection import ComponentCollection + from easydynamics.sample_model.components.model_component import ModelComponent + +# ----------------------------- +# Binding layer (KEY IDEA) +# ----------------------------- + + +@dataclass +class FitBinding: + """ + Contract between dataset, model, and fit function for ParameterAnalysis. This class + encapsulates the necessary information to bind a dataset key to a model and convert it into a + fit function callable. + """ + + parameter_name: str + model: ModelComponent | ComponentCollection | DiffusionModelBase + modes: str | list[str] | None = None + + def build_callables(self) -> list[Callable]: + if isinstance(self.modes, str): + modes = [self.modes] + elif self.modes is None: + modes = ['area', 'width'] # default + else: + modes = self.modes + + if isinstance(self.model, DiffusionModelBase): + return [self._build_diffusion_callable(mode) for mode in modes] + + return [lambda x, **_: self.model.evaluate(x)] + + def get_model_names(self) -> list[str]: + if isinstance(self.modes, str): + modes = [self.modes] + elif self.modes is None: + modes = ['area', 'width'] + else: + modes = self.modes + + if isinstance(self.model, DiffusionModelBase): + return [f'{self.model.display_name} {mode}' for mode in modes] + + return [self.model.display_name] + + def get_parameter_names(self) -> list[str]: + if isinstance(self.modes, str): + modes = [self.modes] + elif self.modes is None: + modes = ['area', 'width'] + else: + modes = self.modes + + if len(modes) == 1: + return [self.parameter_name] + + if isinstance(self.model, DiffusionModelBase): + return [f'{self.parameter_name} {mode}' for mode in modes] + + return [self.parameter_name] + + def _build_diffusion_callable(self, mode: str) -> Callable: + model = self.model + + if mode == 'area' or mode == 'auto': + return lambda x, **_: model.calculate_QISF(x) * model.scale.value + + if mode == 'width': + return lambda x, **_: model.calculate_width(x) + + raise ValueError(f'Unknown diffusion mode: {mode}') diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 954cadab..4f13ad3c 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -13,13 +13,11 @@ from plopp.backends.matplotlib.figure import InteractiveFigure from easydynamics.analysis.analysis import Analysis -from easydynamics.analysis.prepared_fit_data import _PreparedFitData +from easydynamics.analysis.fit_bindings import FitBinding from easydynamics.base_classes.easydynamics_modelbase import EasyDynamicsModelBase from easydynamics.sample_model.component_collection import ComponentCollection from easydynamics.sample_model.components.model_component import ModelComponent -from easydynamics.sample_model.diffusion_model.diffusion_model_base import ( - DiffusionModelBase, -) +from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase from easydynamics.utils.utils import _in_notebook FIT_FUNCTION_TYPE = ModelComponent | ComponentCollection | DiffusionModelBase @@ -39,9 +37,8 @@ class ParameterAnalysis(EasyDynamicsModelBase): def __init__( self, parameters: sc.Dataset | Analysis | None = None, - fit_functions: dict[str, FIT_FUNCTION_TYPE] | None = None, - fit_settings: dict[str, str | list[str]] | None = None, - display_name: str | None = "ParameterAnalysis", + bindings: FitBinding | list[FitBinding] | None = None, + display_name: str | None = 'ParameterAnalysis', unique_name: str | None = None, ) -> None: """ @@ -58,7 +55,7 @@ def __init__( fit_settings : dict[str, str | list[str]] | None, default=None A dictionary mapping parameter names to fit settings. The fit settings can be provided as strings or lists of strings. If None, default fit settings are used. - display_name : str | None, default='ParameterAnalysis' + display_name : str | None, default="ParameterAnalysis" Display name of the analysis. unique_name : str | None, default=None Unique name of the analysis. If None, a unique name is automatically generated. By @@ -68,10 +65,8 @@ def __init__( super().__init__(display_name=display_name, unique_name=unique_name) self._parameters = self._verify_parameters(parameters) - self._fit_settings = self._verify_fit_settings(fit_settings) - self._fit_functions = self._verify_fit_functions(fit_functions) - self._prepared_fit_data = self._prepare_fit_functions_and_parameter_names() + self._bindings = self._verify_bindings(bindings) ############# # Properties @@ -99,64 +94,30 @@ def parameters(self, value: sc.Dataset | Analysis | None) -> None: The new parameters for the parameter analysis. """ self._parameters = self._verify_parameters(value) - self._prepared_fit_data = self._prepare_fit_functions_and_parameter_names() @property - def fit_functions( - self, - ) -> dict[str, ModelComponent | ComponentCollection | DiffusionModelBase]: - """ - Get the fit functions for the parameter analysis. - - Returns - ------- - dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] - The fit functions for the parameter analysis. - """ - return self._fit_functions - - @fit_functions.setter - def fit_functions( - self, - value: ( - dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None - ), - ) -> None: - """ - Set the fit functions for the parameter analysis. - - Parameters - ---------- - value : dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None - The new fit functions for the parameter analysis. - """ - self._fit_functions = self._verify_fit_functions(value) - self._prepared_fit_data = self._prepare_fit_functions_and_parameter_names() - - @property - def fit_settings(self) -> dict[str, str | list[str]]: + def bindings(self) -> list[FitBinding] | None: """ - Get the fit settings for the parameter analysis. + Get the fit bindings for the parameter analysis. Returns ------- - dict[str, str | list[str]] - The fit settings for the parameter analysis. + list[FitBinding] | None + The fit bindings for the parameter analysis. """ - return self._fit_settings + return self._bindings - @fit_settings.setter - def fit_settings(self, value: dict[str, str | list[str]]) -> None: + @bindings.setter + def bindings(self, value: FitBinding | list[FitBinding] | None) -> None: """ - Set the fit settings for the parameter analysis. + Set the fit bindings for the parameter analysis. Parameters ---------- - value : dict[str, str | list[str]] - The new fit settings for the parameter analysis. + value : FitBinding | list[FitBinding] | None + The new fit bindings for the parameter analysis. """ - self._fit_settings = self._verify_fit_settings(value) - self._prepared_fit_data = self._prepare_fit_functions_and_parameter_names() + self._bindings = self._verify_bindings(value) ############# # Other methods @@ -179,27 +140,41 @@ def fit(self) -> FitResults: """ if self._parameters is None: - raise ValueError("No parameters DataSet provided.") - - if not self._prepared_fit_data.fit_function_callables: - raise ValueError("No fit functions provided.") + raise ValueError('No parameters DataSet provided.') - if not self._prepared_fit_data.parameter_names: - raise ValueError("No parameter names found for fit functions.") + if not self._bindings: + raise ValueError('No fit bindings provided.') xs = [] ys = [] ws = [] + funcs, models = [], [] + + for binding in self._bindings: + param_names = binding.get_parameter_names() + callables = binding.build_callables() + + if len(param_names) != len(callables): + raise RuntimeError('Mismatch between parameter names and callables.') + + for pname, func in zip(param_names, callables, strict=True): + if pname not in self._parameters: + raise ValueError( + f"Dataset key '{pname}' from binding '{binding.parameter_name}' not found." + ) - for name in self._prepared_fit_data.expanded_parameter_names: - (x, y, weight) = self._get_xyweight_from_dataset(name) - xs.append(x) - ys.append(y) - ws.append(weight) + x, y, weight = self._get_xyweight_from_dataset(pname) + + xs.append(x) + ys.append(y) + ws.append(weight) + + funcs.append(func) + models.append(binding.model) mf = MultiFitter( - fit_objects=self._prepared_fit_data.fit_objects, - fit_functions=self._prepared_fit_data.fit_function_callables, + fit_objects=models, + fit_functions=funcs, ) return mf.fit( @@ -236,99 +211,105 @@ def plot( """ if not _in_notebook(): - raise RuntimeError( - "plot_data() can only be used in a Jupyter notebook environment." - ) + raise RuntimeError('plot_data() can only be used in a Jupyter notebook environment.') if self._parameters is None: - raise ValueError("No parameters available to plot.") + raise ValueError('No parameters available to plot.') + + model_dataset = self.build_model_dataset(self._bindings) + + # Need a check of names + # for name in names: + # if name not in self._parameters: + # raise ValueError( + # f"Parameter name '{name}' not found in parameters DataSet." + # ) + + if names is not None: + if isinstance(names, str): + names = [names] + + model_dataset = sc.Dataset({k: model_dataset[k] for k in names}) + + # # Make a new Dataset with only the desired parameters. + # data = sc.Dataset(coords=self._parameters.coords) + + # units = [self._parameters[name].unit for name in names] + # first_unit = units[0] + # if any(unit != first_unit for unit in units): + # raise ValueError( + # f"Units are not consistent, and cannot be plotted together: {units}" + # ) + + color_cycle = itertools.cycle(rcParams['axes.prop_cycle'].by_key()['color']) + markers = itertools.cycle(['o', 's', 'D', '^', 'v', '<', '>']) + + plot_kwargs = { + 'title': self.display_name, + 'linestyle': {}, + 'marker': {}, + 'color': {}, + 'markerfacecolor': {}, + } - if names is None: - names = self._prepared_fit_data.expanded_parameter_names - elif isinstance(names, str): - names = [names] + data_arrays = {} - for name in names: - if name not in self._parameters: - raise ValueError( - f"Parameter name '{name}' not found in parameters DataSet." - ) + for b in self._bindings: + param_names = b.get_parameter_names() + model_names = b.get_model_names() - # Make a new Dataset with only the desired parameters. - data = sc.Dataset(coords=self._parameters.coords) + for pname, mname in zip(param_names, model_names, strict=True): + data_arrays[pname] = self._parameters[pname] + color = next(color_cycle) + marker = next(markers) - units = [self._parameters[name].unit for name in names] - first_unit = units[0] - if any(unit != first_unit for unit in units): - raise ValueError( - f"Units are not consistent, and cannot be plotted together: {units}" - ) + # Data styling + plot_kwargs['linestyle'][pname] = 'none' + plot_kwargs['marker'][pname] = marker + plot_kwargs['color'][pname] = color + plot_kwargs['markerfacecolor'][pname] = 'none' - for name in names: - data[name] = self._parameters[name] + # Model styling + plot_kwargs['linestyle'][mname] = '--' + plot_kwargs['marker'][mname] = None + plot_kwargs['color'][mname] = color - # Create fit arrays and plot kwargs for each parameter and fit function - color_cycle = itertools.cycle(rcParams["axes.prop_cycle"].by_key()["color"]) - markers = ["o", "s", "D", "^", "v", "<", ">"] - marker_cycle = itertools.cycle(markers) + # Update kwargs with user provided kwargs. + plot_kwargs.update(kwargs) - fit_arrays = {} + data_and_model = sc.Dataset(data_arrays) + data_and_model.update(model_dataset) - plot_kwargs_defaults = { - "title": self.display_name, - "linestyle": {}, - "marker": {}, - "color": {}, - "markerfacecolor": {}, - } - x = self._parameters.coords["Q"] - - # for name, func, display_name in zip( - # names, - # self._prepared_fit_data.fit_function_callables, - # self._prepared_fit_data.fit_function_display_names, - # strict=True, - # ): - for name in names: - idx = self._prepared_fit_data.expanded_parameter_names.index(name) - - func = self._prepared_fit_data.fit_function_callables[idx] - display_name = self._prepared_fit_data.fit_function_display_names[idx] - - fit_values = func(x.values) - - # Units need to be handled better here. See issue #65 - fit_arrays[display_name] = sc.DataArray( - data=sc.array( - dims=["Q"], values=fit_values, unit=self._parameters[name].unit - ), - coords={"Q": x}, - ) - - # Default plot kwargs - color = next(color_cycle) - marker = next(marker_cycle) - - # Data - plot_kwargs_defaults["linestyle"][name] = "none" - plot_kwargs_defaults["marker"][name] = marker - plot_kwargs_defaults["color"][name] = color - plot_kwargs_defaults["markerfacecolor"][name] = "none" - - # Fit - plot_kwargs_defaults["linestyle"][display_name] = "--" - plot_kwargs_defaults["marker"][display_name] = None - plot_kwargs_defaults["color"][display_name] = color - plot_kwargs_defaults["markerfacecolor"][display_name] = "none" + return pp.plot(data_and_model, **plot_kwargs) - # Update kwargs with user provided kwargs. - plot_kwargs_defaults.update(kwargs) + def build_model_dataset(self, bindings: list[FitBinding]) -> sc.Dataset: + """ + Evaluate all bindings into a sc.Dataset of model predictions. + """ + arrays = {} + + for b in bindings: + param_names = b.get_parameter_names() + model_names = b.get_model_names() + callables = b.build_callables() + + for pname, mname, func in zip(param_names, model_names, callables, strict=True): + da = self._parameters[pname] + x = da.coords['Q'] - fit_dataset = sc.Dataset(fit_arrays) + y_model = func(x.values) - data_and_model = sc.merge(fit_dataset, data) + arrays[mname] = sc.DataArray( + data=sc.array(dims=['Q'], values=y_model, unit=da.unit), + coords={'Q': x}, + ) + return sc.Dataset(arrays) + + def append_binding(self, binding: FitBinding) -> None: + self._bindings.append(binding) - return pp.plot(data_and_model, **plot_kwargs_defaults) + def clear_bindings(self) -> None: + self._bindings.clear() def get_all_variables(self) -> list: """ @@ -340,17 +321,42 @@ def get_all_variables(self) -> list: A list of all variables from the fit functions. """ variables = [] - for fit_funcs in self.fit_functions.values(): - variables.extend(fit_funcs.get_all_variables()) + for b in self._bindings: + variables.extend(b.model.get_all_variables()) return variables ############# # Private methods: verification and preparation ############# - def _verify_parameters( - self, parameters: sc.Dataset | Analysis | None - ) -> sc.Dataset | None: + def _verify_bindings(self, bindings: FitBinding | list[FitBinding] | None) -> list[FitBinding]: + """ + Verify the bindings input. + + Parameters + ---------- + bindings : FitBinding | list[FitBinding] | None + The bindings to verify. + + Returns + ------- + list[FitBinding] + A list of verified FitBindings. + + Raises + ------ + TypeError + If bindings is not a FitBinding, a list of FitBindings, or None. + """ + if bindings is None: + return [] + if isinstance(bindings, FitBinding): + return [bindings] + if isinstance(bindings, list) and all(isinstance(b, FitBinding) for b in bindings): + return bindings + raise TypeError('bindings must be a FitBinding, a list of FitBindings, or None.') + + def _verify_parameters(self, parameters: sc.Dataset | Analysis | None) -> sc.Dataset | None: """ Verify the parameters input and convert it to a sc.Dataset if it's an Analysis. @@ -371,350 +377,22 @@ def _verify_parameters( ValueError If parameters is a sc.Dataset but does not have a 'Q' coordinate. """ - if parameters is not None and not isinstance( - parameters, (sc.Dataset, Analysis) - ): - raise TypeError("parameters must be an sc.Dataset, an Analysis, or None.") + if parameters is not None and not isinstance(parameters, (sc.Dataset, Analysis)): + raise TypeError('parameters must be an sc.Dataset, an Analysis, or None.') if isinstance(parameters, Analysis): verified_parameters = parameters.parameters_to_dataset() else: verified_parameters = parameters - if verified_parameters is not None and "Q" not in verified_parameters.coords: + if verified_parameters is not None and 'Q' not in verified_parameters.coords: raise ValueError("parameters must have a 'Q' coordinate.") return verified_parameters - def _verify_fit_settings( - self, fit_settings: dict[str, str | list[str]] | None - ) -> dict[str, str | list[str]]: - """ - Verify the fit settings input. - - Parameters - ---------- - fit_settings : dict[str, str | list[str]] | None - The fit settings to verify. - - Returns - ------- - dict[str, str | list[str]] - The verified fit settings. - - Raises - ------ - TypeError - If fit_settings is not a dictionary or None. If any key in fit_settings is not a - string. If any value in fit_settings is not a string or a list of strings. If any item - in any list in fit_settings is not a string. - """ - if fit_settings is None: - fit_settings = {} - - if not isinstance(fit_settings, dict): - raise TypeError( - "fit_settings must be a dictionary of fit settings or None." - ) - - for key, value in fit_settings.items(): - if not isinstance(key, str): - raise TypeError("All keys in fit_settings must be strings.") - if not isinstance(value, (str, list)): - raise TypeError( - "All values in fit_settings must be strings or lists of strings." - ) - if isinstance(value, list) and not all( - isinstance(item, str) for item in value - ): - raise TypeError("All items in lists in fit_settings must be strings.") - return fit_settings - - def _verify_fit_functions( - self, - fit_functions: ( - dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None - ), - ) -> dict[str, ModelComponent | ComponentCollection | DiffusionModelBase]: - """ - Verify the fit functions input. - - Parameters - ---------- - fit_functions : dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] | None - The fit functions to verify. - - Returns - ------- - dict[str, ModelComponent | ComponentCollection | DiffusionModelBase] - The verified fit functions. - - Raises - ------ - TypeError - If fit_functions is not a dictionary or None. - """ - - if fit_functions is None: - fit_functions = {} - - if not isinstance(fit_functions, dict): - raise TypeError( - "fit_functions must be a dictionary mapping parameter names to fit functions." - ) - - for name, func in fit_functions.items(): - if not isinstance(name, str): - raise TypeError("All keys in fit_functions must be strings.") - if not isinstance( - func, - ( - ModelComponent, - ComponentCollection, - DiffusionModelBase, - ), - ): - raise TypeError( - "All values in fit_functions must be a ModelComponent, a ComponentCollection, " - "or a DiffusionModelBase." - ) - return fit_functions - - def _prepare_fit_functions_and_parameter_names( - self, - ) -> _PreparedFitData: - """ - Prepare the fit functions and parameter names for fitting. - - - Returns - ------- - _PreparedFitData - A _PreparedFitData object containing the list of fit function callables, the list of - fit objects, the list of fit function display names, the list of original parameter - names, and the list of expanded parameter names. - - Raises - ------ - ValueError - If any parameter name in fit_functions is not found in the parameters DataSet. - """ - fit_function_callables = [] - fit_objects = [] - expanded_parameter_names = [] - fit_function_display_names = [] - for name, func in self._fit_functions.items(): - if isinstance(func, DiffusionModelBase): - fit_funcs, fit_objs, display_names = ( - self._diffusion_model_to_fit_functions(name, func) - ) - fit_function_callables.extend(fit_funcs) - fit_objects.extend(fit_objs) - fit_function_display_names.extend(display_names) - expanded_parameter_names.extend( - self._get_modelcomponent_parameter_names(name) - ) - elif isinstance(func, (ModelComponent, ComponentCollection)): - fit_function_callables.append(self._components_to_fit_function(func)) - fit_objects.append(func) - fit_function_display_names.append(func.display_name) - expanded_parameter_names.append(name) - parameter_names = list(self._fit_functions.keys()) - - # Check that all names are in the DataSet - if self._parameters is not None: - for name in expanded_parameter_names: - if name not in self._parameters: - raise ValueError( - f"Parameter name '{name}' not found in parameters DataSet." - ) - - return _PreparedFitData( - fit_function_callables=fit_function_callables, - fit_objects=fit_objects, - fit_function_display_names=fit_function_display_names, - parameter_names=parameter_names, - expanded_parameter_names=expanded_parameter_names, - ) - ############# # Private methods ############# - def _diffusion_model_to_fit_functions( - self, - parameter_name: str, - diffusion_model: DiffusionModelBase, - ) -> tuple[list[callable], list[DiffusionModelBase], list[str]]: - """ - Convert a DiffusionModelBase to a list of fit functions. - - Parameters - ---------- - parameter_name : str - The name of the parameter. - diffusion_model : DiffusionModelBase - The diffusion model to convert. - - Returns - ------- - tuple[list[callable], list[DiffusionModelBase], list[str]] - A list of fit functions corresponding to the diffusion model, a list of the original - diffusion model repeated for each fit function, and a list of display names for the fit - functions. - """ - - # Currently only looks at the area and width of a Lorentzian. - # Can and should be extended to also handle delta functions, more parameters etc. - - fit_functions = [] - fit_objects = [] - display_names = [] - - if parameter_name in self.fit_settings: - fit_setting = self.fit_settings[parameter_name] - if isinstance(fit_setting, str): - fit_setting = [fit_setting] - - if "area" in fit_setting: - fit_functions.append(self._make_area_function(diffusion_model)) - fit_objects.append(diffusion_model) - display_names.append(diffusion_model.display_name + " area") - - if "width" in fit_setting: - fit_functions.append(self._make_width_function(diffusion_model)) - fit_objects.append(diffusion_model) - display_names.append(diffusion_model.display_name + " width") - else: - # If no fit settings are provided for this parameter, fit - # both area and width by default. - fit_functions.append(self._make_area_function(diffusion_model)) - fit_objects.append(diffusion_model) - display_names.append(diffusion_model.display_name + " area") - - fit_functions.append(self._make_width_function(diffusion_model)) - fit_objects.append(diffusion_model) - display_names.append(diffusion_model.display_name + " width") - - return fit_functions, fit_objects, display_names - - @staticmethod - def _make_area_function(model: DiffusionModelBase) -> callable: - """ - Make a fit function for the area of a diffusion model. - - Parameters - ---------- - model : DiffusionModelBase - The diffusion model to make the fit function for. - - Returns - ------- - callable - A fit function corresponding to the area of the diffusion model. - """ - - def fit_function( - x: np.ndarray, - **kwargs: dict[str, Any], # noqa: ARG001 - ) -> np.ndarray: - return model.calculate_QISF(x) * model.scale.value - - return fit_function - - @staticmethod - def _make_width_function(model: DiffusionModelBase) -> callable: - """ - Make a fit function for the width of a diffusion model. - - Parameters - ---------- - model : DiffusionModelBase - The diffusion model to make the fit function for. - - Returns - ------- - callable - A fit function corresponding to the width of the diffusion model. - """ - - def fit_function( - x: np.ndarray, - **kwargs: dict[str, Any], # noqa: ARG001 - ) -> np.ndarray: - return model.calculate_width(x) - - return fit_function - - def _components_to_fit_function( - self, - components: ModelComponent | ComponentCollection | None, - ) -> callable: - """ - Convert a ModelComponent or ComponentCollection to a fit function. - - Parameters - ---------- - components : ModelComponent | ComponentCollection | None - The component(s) to convert. - - Returns - ------- - callable - A fit function corresponding to the component(s). - """ - - if components is None: - return [] - - return self._make_components_function(components) - - @staticmethod - def _make_components_function( - components: ModelComponent | ComponentCollection, - ) -> callable: - def fit_function( - x: np.ndarray, - **kwargs: dict[str, Any], # noqa: ARG001 - ) -> np.ndarray: - return components.evaluate(x) - - return fit_function - - def _get_modelcomponent_parameter_names( - self, - parameter_name: str, - ) -> list[str]: - """ - Get the parameter names for a model component. - - Parameters - ---------- - parameter_name : str - The name of the parameter to get names for. - - Returns - ------- - list[str] - A list of parameter names. - """ - parameter_names = [] - - if parameter_name in self.fit_settings: - fit_setting = self.fit_settings[parameter_name] - if isinstance(fit_setting, str): - fit_setting = [fit_setting] - - if "area" in fit_setting: - parameter_names.append(parameter_name + " area") - - if "width" in fit_setting: - parameter_names.append(parameter_name + " width") - else: - parameter_names.append(parameter_name + " area") - parameter_names.append(parameter_name + " width") - - return parameter_names - def _get_xyweight_from_dataset( self, parameter_name: str ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: @@ -738,20 +416,16 @@ def _get_xyweight_from_dataset( found for the parameter. """ if self._parameters is None: - raise ValueError("No parameters DataSet provided.") + raise ValueError('No parameters DataSet provided.') if parameter_name not in self._parameters: - raise ValueError( - f"Parameter name '{parameter_name}' not found in parameters DataSet." - ) + raise ValueError(f"Parameter name '{parameter_name}' not found in parameters DataSet.") weight = 1 / self._parameters[parameter_name].variances ** 0.5 if not np.all(np.isfinite(weight)): - raise ValueError( - f"Non-finite weights found for parameter '{parameter_name}'" - ) + raise ValueError(f"Non-finite weights found for parameter '{parameter_name}'") return ( - self._parameters[parameter_name].coords["Q"].values, + self._parameters[parameter_name].coords['Q'].values, self._parameters[parameter_name].values, weight, ) @@ -759,37 +433,30 @@ def _get_xyweight_from_dataset( ############# # Dunder methods ############# - def __repr__(self) -> str: - """ - Return a string representation of the ParameterAnalysis. - - Returns - ------- - str - A string representation of the ParameterAnalysis. - """ cls = self.__class__.__name__ - n_params = ( - len(self._parameters) if isinstance(self._parameters, sc.Dataset) else 0 - ) + n_params = len(self._parameters) if isinstance(self._parameters, sc.Dataset) else 0 param_names = ( - list(self._parameters.keys()) - if isinstance(self._parameters, sc.Dataset) - else None + list(self._parameters.keys()) if isinstance(self._parameters, sc.Dataset) else None ) - fit_keys = list(self._fit_functions.keys()) + binding_info = [ + { + 'parameter': b.parameter_name, + 'model': b.model.display_name, + 'modes': b.modes, + } + for b in self._bindings + ] return ( - f"{cls}(\n" - f" display_name={self.display_name!r},\n" - f" unique_name={self.unique_name!r},\n" - f" n_parameters={n_params},\n" - f" parameter_names={param_names},\n" - f" fit_functions={fit_keys},\n" - f" expanded_parameters={self._prepared_fit_data.expanded_parameter_names}\n" - f")" + f'{cls}(\n' + f' display_name={self.display_name!r},\n' + f' unique_name={self.unique_name!r},\n' + f' n_parameters={n_params},\n' + f' parameter_names={param_names},\n' + f' bindings={binding_info}\n' + f')' ) diff --git a/tests/unit/easydynamics/analysis/test_parameter_analysis.py b/tests/unit/easydynamics/analysis/test_parameter_analysis.py index c89549d7..b7fb0036 100644 --- a/tests/unit/easydynamics/analysis/test_parameter_analysis.py +++ b/tests/unit/easydynamics/analysis/test_parameter_analysis.py @@ -15,35 +15,29 @@ class TestParameterAnalysis: @pytest.fixture def dataset(self): - Q = sc.array(dims=["Q"], values=[0.1, 0.2]) + Q = sc.array(dims=['Q'], values=[0.1, 0.2]) return sc.Dataset( data={ - "parameter1": sc.DataArray( - data=sc.array( - dims=["Q"], values=[1.0, 2.0], variances=[0.1, 0.2], unit="meV" - ), - coords={"Q": Q}, + 'parameter1': sc.DataArray( + data=sc.array(dims=['Q'], values=[1.0, 2.0], variances=[0.1, 0.2], unit='meV'), + coords={'Q': Q}, ), - "parameter2": sc.DataArray( + 'parameter2': sc.DataArray( data=sc.array( - dims=["Q"], + dims=['Q'], values=[1.5, 2.5], variances=[0.15, 0.25], - unit="1/meV", + unit='1/meV', ), - coords={"Q": Q}, + coords={'Q': Q}, ), - "parameter3 area": sc.DataArray( - data=sc.array( - dims=["Q"], values=[4.0, 5.0], variances=[0.3, 0.5], unit="meV" - ), - coords={"Q": Q}, + 'parameter3 area': sc.DataArray( + data=sc.array(dims=['Q'], values=[4.0, 5.0], variances=[0.3, 0.5], unit='meV'), + coords={'Q': Q}, ), - "parameter3 width": sc.DataArray( - data=sc.array( - dims=["Q"], values=[6.0, 7.0], variances=[0.6, 0.7], unit="meV" - ), - coords={"Q": Q}, + 'parameter3 width': sc.DataArray( + data=sc.array(dims=['Q'], values=[6.0, 7.0], variances=[0.6, 0.7], unit='meV'), + coords={'Q': Q}, ), } ) @@ -54,7 +48,7 @@ def parameter_analysis(self, dataset): func2 = Lorentzian() return ParameterAnalysis( parameters=dataset, - fit_functions={"parameter1": func1, "parameter3 area": func2}, + fit_functions={'parameter1': func1, 'parameter3 area': func2}, ) @pytest.fixture @@ -62,8 +56,8 @@ def parameter_analysis_diffusion(self, dataset): func = BrownianTranslationalDiffusion() return ParameterAnalysis( parameters=dataset, - fit_functions={"parameter3": func}, - fit_settings={"parameter3": ["area", "width"]}, + fit_functions={'parameter3': func}, + fit_settings={'parameter3': ['area', 'width']}, ) @pytest.fixture @@ -77,8 +71,8 @@ def parameter_analysis_diffusion_and_componentcollection(self, dataset): func3 = BrownianTranslationalDiffusion() return ParameterAnalysis( parameters=dataset, - fit_functions={"parameter1": funcs, "parameter3": func3}, - fit_settings={"parameter3": ["area", "width"]}, + fit_functions={'parameter1': funcs, 'parameter3': func3}, + fit_settings={'parameter3': ['area', 'width']}, ) def test_parameter_analysis_initialization(self, parameter_analysis): @@ -88,17 +82,17 @@ def test_parameter_analysis_initialization(self, parameter_analysis): # Parameters assert isinstance(parameter_analysis.parameters, sc.Dataset) assert set(parameter_analysis.parameters.keys()) == { - "parameter1", - "parameter2", - "parameter3 area", - "parameter3 width", + 'parameter1', + 'parameter2', + 'parameter3 area', + 'parameter3 width', } # Fit functions assert isinstance(parameter_analysis.fit_functions, dict) assert set(parameter_analysis.fit_functions.keys()) == { - "parameter1", - "parameter3 area", + 'parameter1', + 'parameter3 area', } # Fit settings default @@ -119,7 +113,7 @@ def test_parameter_analysis_initialization(self, parameter_analysis): assert len(prepared.fit_function_display_names) == n_funcs # Parameter names should match input keys - assert prepared.parameter_names == ["parameter1", "parameter3 area"] + assert prepared.parameter_names == ['parameter1', 'parameter3 area'] # Expanded names should match what exists in dataset for name in prepared.expanded_parameter_names: @@ -132,33 +126,33 @@ def test_parameter_property(self, parameter_analysis): # THEN EXPECT assert isinstance(parameters, sc.Dataset) assert set(parameters.keys()) == { - "parameter1", - "parameter2", - "parameter3 area", - "parameter3 width", + 'parameter1', + 'parameter2', + 'parameter3 area', + 'parameter3 width', } # WHEN - Q = sc.array(dims=["Q"], values=[0.1, 0.2]) + Q = sc.array(dims=['Q'], values=[0.1, 0.2]) new_data = sc.Dataset( data={ - "parameter4": sc.DataArray( + 'parameter4': sc.DataArray( data=sc.array( - dims=["Q"], + dims=['Q'], values=[71.0, 12.0], variances=[1.1, 2.2], - unit="meV", + unit='meV', ), - coords={"Q": Q}, + coords={'Q': Q}, ), - "parameter5": sc.DataArray( + 'parameter5': sc.DataArray( data=sc.array( - dims=["Q"], + dims=['Q'], values=[8.5, 0.5], variances=[2.15, 1.25], - unit="1/meV", + unit='1/meV', ), - coords={"Q": Q}, + coords={'Q': Q}, ), } ) diff --git a/tests/unit/easydynamics/analysis/test_prepared_fit_data.py b/tests/unit/easydynamics/analysis/test_prepared_fit_data.py index 3d7a1e8b..98cd3ae8 100644 --- a/tests/unit/easydynamics/analysis/test_prepared_fit_data.py +++ b/tests/unit/easydynamics/analysis/test_prepared_fit_data.py @@ -20,9 +20,9 @@ def prepared_data(self, dummy_callable): return _PreparedFitData( fit_function_callables=[dummy_callable], fit_objects=[object()], - fit_function_display_names=["f"], - parameter_names=["p"], - expanded_parameter_names=["p"], + fit_function_display_names=['f'], + parameter_names=['p'], + expanded_parameter_names=['p'], ) def test_initialization(self, prepared_data, dummy_callable): From 835be22277f51bf353f93f815b91f08af5f12681 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 29 Apr 2026 08:56:52 +0200 Subject: [PATCH 10/19] remove unused files --- .../tutorials/tutorial0_more_advanced.ipynb | 16 +++++--- .../analysis/prepared_fit_data.py | 40 ------------------- .../analysis/test_prepared_fit_data.py | 36 ----------------- 3 files changed, 11 insertions(+), 81 deletions(-) delete mode 100644 src/easydynamics/analysis/prepared_fit_data.py delete mode 100644 tests/unit/easydynamics/analysis/test_prepared_fit_data.py diff --git a/docs/docs/tutorials/tutorial0_more_advanced.ipynb b/docs/docs/tutorials/tutorial0_more_advanced.ipynb index 375bf7cc..a773d723 100644 --- a/docs/docs/tutorials/tutorial0_more_advanced.ipynb +++ b/docs/docs/tutorials/tutorial0_more_advanced.ipynb @@ -314,17 +314,23 @@ " coefficients=[3.7, -0.5], unit='1/angstrom', display_name='Gauss area fit'\n", ")\n", "dho_area_fit_func = sm.Polynomial(\n", - " coefficients=[2.5, 0.1], unit='1/angstrom', display_name='DHO area fit'\n", + " coefficients=[2.0, 0.12], unit='1/angstrom', display_name='DHO area fit'\n", ")\n", "dho_center_fit_func = sm.Polynomial(\n", - " coefficients=[1.4, 0.1], unit='1/angstrom', display_name='DHO center fit'\n", + " coefficients=[1.1, 0.2], unit='1/angstrom', display_name='DHO center fit'\n", ")\n", "\n", - "binding1 = edyn.FitBinding(parameter_name='Gaussian area', model=gauss_fit_func)\n", + "binding1 = edyn.FitBinding(\n", + " parameter_name='Gaussian area',\n", + " model=gauss_fit_func)\n", "\n", - "binding2 = edyn.FitBinding(parameter_name='DHO area', model=dho_area_fit_func)\n", + "binding2 = edyn.FitBinding(\n", + " parameter_name='DHO area',\n", + " model=dho_area_fit_func)\n", "\n", - "binding3 = edyn.FitBinding(parameter_name='DHO center', model=dho_center_fit_func)\n", + "binding3 = edyn.FitBinding(\n", + " parameter_name='DHO center',\n", + " model=dho_center_fit_func)\n", "\n", "parameter_analysis = edyn.ParameterAnalysis(\n", " parameters=analysis,\n", diff --git a/src/easydynamics/analysis/prepared_fit_data.py b/src/easydynamics/analysis/prepared_fit_data.py deleted file mode 100644 index 6f31458b..00000000 --- a/src/easydynamics/analysis/prepared_fit_data.py +++ /dev/null @@ -1,40 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - -from dataclasses import dataclass - -from easydynamics.sample_model.component_collection import ComponentCollection -from easydynamics.sample_model.components.model_component import ModelComponent -from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase - -FIT_FUNCTION_TYPE = ModelComponent | ComponentCollection | DiffusionModelBase - - -@dataclass -class _PreparedFitData: - """ - Holds the prepared fit data for parameter analysis. This includes the fit function callables, - fit objects, display names, and parameter names, both original and expanded. - - Attributes - ---------- - fit_function_callables : list[callable] - A list of callables corresponding to the fit functions. - fit_objects : list[FIT_FUNCTION_TYPE] - A list of the original fit objects corresponding to the fit functions. - fit_function_display_names : list[str] - A list of display names corresponding to the fit functions, where diffusion models are - expanded into their parameters (e.g. "D area", "D width" for a diffusion model "D"). - parameter_names : list[str] - A list of the original parameter names corresponding to the fit functions. - expanded_parameter_names : list[str] - A list of the expanded parameter names corresponding to the fit functions, where diffusion - models are expanded into their parameters (e.g. "D area", "D width" for a diffusion model - "D"). - """ - - fit_function_callables: list[callable] - fit_objects: list[FIT_FUNCTION_TYPE] - fit_function_display_names: list[str] - parameter_names: list[str] - expanded_parameter_names: list[str] diff --git a/tests/unit/easydynamics/analysis/test_prepared_fit_data.py b/tests/unit/easydynamics/analysis/test_prepared_fit_data.py deleted file mode 100644 index 98cd3ae8..00000000 --- a/tests/unit/easydynamics/analysis/test_prepared_fit_data.py +++ /dev/null @@ -1,36 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - - -import pytest - -from easydynamics.analysis.prepared_fit_data import _PreparedFitData - - -class TestPreparedFitData: - @pytest.fixture - def dummy_callable(self): - def f(x): - return x - - return f - - @pytest.fixture - def prepared_data(self, dummy_callable): - return _PreparedFitData( - fit_function_callables=[dummy_callable], - fit_objects=[object()], - fit_function_display_names=['f'], - parameter_names=['p'], - expanded_parameter_names=['p'], - ) - - def test_initialization(self, prepared_data, dummy_callable): - assert isinstance(prepared_data.fit_function_callables, list) - assert isinstance(prepared_data.fit_objects, list) - assert isinstance(prepared_data.fit_function_display_names, list) - assert isinstance(prepared_data.parameter_names, list) - assert isinstance(prepared_data.expanded_parameter_names, list) - - assert prepared_data.fit_function_callables[0] is dummy_callable - assert callable(prepared_data.fit_function_callables[0]) From b4ed05d77b3b1c6d044ade66b6209d9552fec74a Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 29 Apr 2026 20:30:32 +0200 Subject: [PATCH 11/19] Add lots of docstring for fitbinding --- .../tutorials/tutorial0_more_advanced.ipynb | 12 +- src/easydynamics/analysis/fit_bindings.py | 265 +++++++++++++++++- .../analysis/parameter_analysis.py | 2 +- 3 files changed, 256 insertions(+), 23 deletions(-) diff --git a/docs/docs/tutorials/tutorial0_more_advanced.ipynb b/docs/docs/tutorials/tutorial0_more_advanced.ipynb index a773d723..ee1e4e57 100644 --- a/docs/docs/tutorials/tutorial0_more_advanced.ipynb +++ b/docs/docs/tutorials/tutorial0_more_advanced.ipynb @@ -320,17 +320,11 @@ " coefficients=[1.1, 0.2], unit='1/angstrom', display_name='DHO center fit'\n", ")\n", "\n", - "binding1 = edyn.FitBinding(\n", - " parameter_name='Gaussian area',\n", - " model=gauss_fit_func)\n", + "binding1 = edyn.FitBinding(parameter_name='Gaussian area', model=gauss_fit_func)\n", "\n", - "binding2 = edyn.FitBinding(\n", - " parameter_name='DHO area',\n", - " model=dho_area_fit_func)\n", + "binding2 = edyn.FitBinding(parameter_name='DHO area', model=dho_area_fit_func)\n", "\n", - "binding3 = edyn.FitBinding(\n", - " parameter_name='DHO center',\n", - " model=dho_center_fit_func)\n", + "binding3 = edyn.FitBinding(parameter_name='DHO center', model=dho_center_fit_func)\n", "\n", "parameter_analysis = edyn.ParameterAnalysis(\n", " parameters=analysis,\n", diff --git a/src/easydynamics/analysis/fit_bindings.py b/src/easydynamics/analysis/fit_bindings.py index 9331c232..a3e8bab6 100644 --- a/src/easydynamics/analysis/fit_bindings.py +++ b/src/easydynamics/analysis/fit_bindings.py @@ -1,34 +1,214 @@ from __future__ import annotations -from dataclasses import dataclass from typing import TYPE_CHECKING +from easydynamics.base_classes.easydynamics_base import EasyDynamicsBase +from easydynamics.sample_model.component_collection import ComponentCollection +from easydynamics.sample_model.components.model_component import ModelComponent from easydynamics.sample_model.diffusion_model.diffusion_model_base import DiffusionModelBase if TYPE_CHECKING: from collections.abc import Callable - from easydynamics.sample_model.component_collection import ComponentCollection - from easydynamics.sample_model.components.model_component import ModelComponent -# ----------------------------- -# Binding layer (KEY IDEA) -# ----------------------------- - - -@dataclass -class FitBinding: +class FitBinding(EasyDynamicsBase): """ Contract between dataset, model, and fit function for ParameterAnalysis. This class encapsulates the necessary information to bind a dataset key to a model and convert it into a fit function callable. """ - parameter_name: str - model: ModelComponent | ComponentCollection | DiffusionModelBase - modes: str | list[str] | None = None + def __init__( + self, + parameter_name: str, + model: ModelComponent | ComponentCollection | DiffusionModelBase, + modes: str | list[str] | None = None, + display_name: str | None = None, + unique_name: str | None = None, + ) -> None: + """ + Initialize a FitBinding. + + Parameters + ---------- + parameter_name : str + The name of the parameter to fit. This should correspond to a key in the dataset. + model : ModelComponent | ComponentCollection | DiffusionModelBase + The model to fit. This can be a single ModelComponent, a ComponentCollection, or a + DiffusionModelBase. + modes : str | list[str] | None, default=None + The modes to fit for diffusion models. This can be a single string, a list of strings, + or None (which defaults to ["area", "width"]). Only applicable if the model is a + DiffusionModelBase. Default is None. + display_name : str | None, default=None + An optional display name for the FitBinding. If None, the unique_name will be used. + Default is None. + unique_name : str | None, default=None + An optional unique name for the FitBinding. If None, a unique name will be generated. + Default is None. + + Raises + ------ + ValueError + If parameter_name is not a string, if model is not a ModelComponent, + ComponentCollection or DiffusionModelBase, or if modes is not a string, list of + strings, or None. + + Examples + -------- + 1. Basic usage with a ModelComponent: + >>> import easydynamics.sample_model as sm + >>> import easydynamics as edyn + >>> fit_func = sm.Polynomial(coefficients=[3.7, -0.5], display_name='Straight line') + >>> binding = edyn.FitBinding(parameter_name='Gaussian area', model=fit_func) + >>> print(binding) + FitBinding(parameter_name='Gaussian area', model=Polynomial(unique_name = Polynomial_1, + unit = meV, coefficients = [Straight line_c0=3.7, Straight line_c1=-0.5]), modes=None) + + 2. Usage with a DiffusionModelBase and specific modes: + >>> brownian_diffusion_model = sm.BrownianTranslationalDiffusion( + ... display_name='Brownian Diffusion', diffusion_coefficient=2.4e-9, scale=0.5 + ... ) + >>> binding = edyn.FitBinding( + ... parameter_name='Lorentzian', + ... model=brownian_diffusion_model, + ... modes=['area', 'width'], + ... ) + FitBinding(parameter_name=Lorentzian, model=Brownian Diffusion, modes=['area', 'width'], + display_name=FitBinding_1, unique_name=FitBinding_1) + """ + + super().__init__(display_name=display_name, unique_name=unique_name) + + if not isinstance(parameter_name, str): + raise ValueError('parameter_name must be a string') + + if not isinstance(model, (ModelComponent, ComponentCollection, DiffusionModelBase)): + raise ValueError( + 'model must be a ModelComponent, ComponentCollection, or DiffusionModelBase' + ) + + if modes is not None and not isinstance(modes, (str, list)): + raise ValueError('modes must be a string, list of strings, or None') + + self._parameter_name = parameter_name + self._model = model + self._modes = modes + + # ------------------------------------------------------------------ + # Properties + # ------------------------------------------------------------------ + + @property + def parameter_name(self) -> str: + """ + The name of the parameter to fit. This should correspond to a key in the dataset. + + Returns + ------- + str + The name of the parameter to fit. + """ + return self._parameter_name + + @parameter_name.setter + def parameter_name(self, value: str) -> None: + """ + Set the name of the parameter to fit. + + Parameters + ---------- + value : str + The new name of the parameter to fit. + + Raises + ------ + ValueError + If the value is not a string. + """ + if not isinstance(value, str): + raise ValueError('parameter_name must be a string') + self._parameter_name = value + + @property + def model(self) -> ModelComponent | ComponentCollection | DiffusionModelBase: + """ + The model to fit. This can be a single ModelComponent, a ComponentCollection, or a + DiffusionModelBase. + + Returns + ------- + ModelComponent | ComponentCollection | DiffusionModelBase + The model to fit. + """ + return self._model + + @model.setter + def model(self, value: ModelComponent | ComponentCollection | DiffusionModelBase) -> None: + """ + Set the model to fit. + + Parameters + ---------- + value : ModelComponent | ComponentCollection | DiffusionModelBase + The new model to fit. + + Raises + ------ + ValueError + If the value is not a ModelComponent, ComponentCollection, or DiffusionModelBase. + """ + if not isinstance(value, (ModelComponent, ComponentCollection, DiffusionModelBase)): + raise ValueError( + 'model must be a ModelComponent, ComponentCollection, or DiffusionModelBase' + ) + self._model = value + + @property + def modes(self) -> str | list[str] | None: + """ + The modes to fit for diffusion models. This can be a single string, a list of strings, or + None (which defaults to ["area", "width"]). + + Returns + ------- + str | list[str] | None + The modes to fit for diffusion models. + """ + return self._modes + + @modes.setter + def modes(self, value: str | list[str] | None) -> None: + """ + Set the modes to fit for diffusion models. + + Parameters + ---------- + value : str | list[str] | None + The new modes to fit for diffusion models. + + Raises + ------ + ValueError + If the value is not a string, list of strings, or None. + """ + if value is not None and not isinstance(value, (str, list)): + raise ValueError('modes must be a string, list of strings, or None') + self._modes = value + + # ------------------------------------------------------------------ + # Other methods + # ------------------------------------------------------------------ def build_callables(self) -> list[Callable]: + """ + Build the callables for fitting based on the model and modes. + + Returns + ------- + list[Callable] + A list of callables for fitting. + """ if isinstance(self.modes, str): modes = [self.modes] elif self.modes is None: @@ -42,6 +222,14 @@ def build_callables(self) -> list[Callable]: return [lambda x, **_: self.model.evaluate(x)] def get_model_names(self) -> list[str]: + """ + Get the names of the models based on the current modes. + + Returns + ------- + list[str] + A list of model names. + """ if isinstance(self.modes, str): modes = [self.modes] elif self.modes is None: @@ -55,6 +243,14 @@ def get_model_names(self) -> list[str]: return [self.model.display_name] def get_parameter_names(self) -> list[str]: + """ + Get the names of the parameters based on the current modes. + + Returns + ------- + list[str] + A list of parameter names. + """ if isinstance(self.modes, str): modes = [self.modes] elif self.modes is None: @@ -70,7 +266,29 @@ def get_parameter_names(self) -> list[str]: return [self.parameter_name] + # ------------------------------------------------------------------ + # Private methods + # ------------------------------------------------------------------ + def _build_diffusion_callable(self, mode: str) -> Callable: + """ + Build a callable for a specific diffusion mode. + + Parameters + ---------- + mode : str + The diffusion mode ("area", "width", or "auto"). + + Returns + ------- + Callable + A callable for the specified diffusion mode. + + Raises + ------ + ValueError + If the mode is unknown. + """ model = self.model if mode == 'area' or mode == 'auto': @@ -80,3 +298,24 @@ def _build_diffusion_callable(self, mode: str) -> Callable: return lambda x, **_: model.calculate_width(x) raise ValueError(f'Unknown diffusion mode: {mode}') + + # ------------------------------------------------------------------ + # dunder methods + # ------------------------------------------------------------------ + + def __repr__(self) -> str: + """ + Return a string representation of the FitBinding. + + Returns + ------- + str + A string representation of the FitBinding. + """ + return ( + f'FitBinding(parameter_name={self.parameter_name},\n ' + f'model={self.model.display_name},\n ' + f'modes={self.modes},\n ' + f'display_name={self.display_name},\n ' + f'unique_name={self.unique_name})' + ) diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 4f13ad3c..c209f87b 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -55,7 +55,7 @@ def __init__( fit_settings : dict[str, str | list[str]] | None, default=None A dictionary mapping parameter names to fit settings. The fit settings can be provided as strings or lists of strings. If None, default fit settings are used. - display_name : str | None, default="ParameterAnalysis" + display_name : str | None, default='ParameterAnalysis' Display name of the analysis. unique_name : str | None, default=None Unique name of the analysis. If None, a unique name is automatically generated. By From dcee2673ba057cc23270451447d047b7cdf5c65b Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 29 Apr 2026 20:46:21 +0200 Subject: [PATCH 12/19] move outdated tests Co-authored-by: Copilot --- src/easydynamics/analysis/fit_bindings.py | 3 + .../analysis/parameter_analysis.py | 13 +- .../analysis/test_parameter_analysis.py | 169 +++++------------- 3 files changed, 53 insertions(+), 132 deletions(-) diff --git a/src/easydynamics/analysis/fit_bindings.py b/src/easydynamics/analysis/fit_bindings.py index a3e8bab6..3320722b 100644 --- a/src/easydynamics/analysis/fit_bindings.py +++ b/src/easydynamics/analysis/fit_bindings.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index c209f87b..e2cfb70f 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -49,12 +49,9 @@ def __init__( parameters : sc.Dataset | Analysis | None, default=None The parameters to analyze. Can be provided as a sc.Dataset or as an Analysis (in which case the parameters will be extracted from the Analysis). - fit_functions : dict[str, FIT_FUNCTION_TYPE] | None, default=None - Dictionary mapping parameter names to fit functions. The fit functions can be provided - as ModelComponents, ComponentCollections, or DiffusionModelBase objects. - fit_settings : dict[str, str | list[str]] | None, default=None - A dictionary mapping parameter names to fit settings. The fit settings can be provided - as strings or lists of strings. If None, default fit settings are used. + bindings : FitBinding | list[FitBinding] | None, default=None + The fit bindings to use for fitting the parameters. Can be a single FitBinding or a + list of FitBindings. If None, no fit bindings are provided. display_name : str | None, default='ParameterAnalysis' Display name of the analysis. unique_name : str | None, default=None @@ -137,6 +134,10 @@ def fit(self) -> FitResults: ValueError If no parameters DataSet is provided. If no fit functions are provided. If no parameter names are found for the fit functions. + + RuntimeError + If there is a mismatch between the number of parameter names and the number of + callables in the fit bindings. """ if self._parameters is None: diff --git a/tests/unit/easydynamics/analysis/test_parameter_analysis.py b/tests/unit/easydynamics/analysis/test_parameter_analysis.py index b7fb0036..ed5c5864 100644 --- a/tests/unit/easydynamics/analysis/test_parameter_analysis.py +++ b/tests/unit/easydynamics/analysis/test_parameter_analysis.py @@ -5,12 +5,6 @@ import pytest import scipp as sc -from easydynamics.analysis.parameter_analysis import ParameterAnalysis -from easydynamics.sample_model.component_collection import ComponentCollection -from easydynamics.sample_model.components import Gaussian -from easydynamics.sample_model.components import Lorentzian -from easydynamics.sample_model.diffusion_model import BrownianTranslationalDiffusion - class TestParameterAnalysis: @pytest.fixture @@ -42,123 +36,46 @@ def dataset(self): } ) - @pytest.fixture - def parameter_analysis(self, dataset): - func1 = Gaussian() - func2 = Lorentzian() - return ParameterAnalysis( - parameters=dataset, - fit_functions={'parameter1': func1, 'parameter3 area': func2}, - ) - - @pytest.fixture - def parameter_analysis_diffusion(self, dataset): - func = BrownianTranslationalDiffusion() - return ParameterAnalysis( - parameters=dataset, - fit_functions={'parameter3': func}, - fit_settings={'parameter3': ['area', 'width']}, - ) - - @pytest.fixture - def parameter_analysis_diffusion_and_componentcollection(self, dataset): - func1 = Gaussian() - func2 = Lorentzian() - funcs = ComponentCollection() - funcs.append_component(func1) - funcs.append_component(func2) - - func3 = BrownianTranslationalDiffusion() - return ParameterAnalysis( - parameters=dataset, - fit_functions={'parameter1': funcs, 'parameter3': func3}, - fit_settings={'parameter3': ['area', 'width']}, - ) - - def test_parameter_analysis_initialization(self, parameter_analysis): - # WHEN THEN EXPECT - assert isinstance(parameter_analysis, ParameterAnalysis) - - # Parameters - assert isinstance(parameter_analysis.parameters, sc.Dataset) - assert set(parameter_analysis.parameters.keys()) == { - 'parameter1', - 'parameter2', - 'parameter3 area', - 'parameter3 width', - } - - # Fit functions - assert isinstance(parameter_analysis.fit_functions, dict) - assert set(parameter_analysis.fit_functions.keys()) == { - 'parameter1', - 'parameter3 area', - } - - # Fit settings default - assert isinstance(parameter_analysis.fit_settings, dict) - assert parameter_analysis.fit_settings == {} - - # Prepared fit data - prepared = parameter_analysis._prepared_fit_data - assert isinstance(prepared.fit_function_callables, list) - assert isinstance(prepared.fit_objects, list) - assert isinstance(prepared.fit_function_display_names, list) - assert isinstance(prepared.parameter_names, list) - assert isinstance(prepared.expanded_parameter_names, list) - - # Consistency checks - n_funcs = len(prepared.fit_function_callables) - assert len(prepared.fit_objects) == n_funcs - assert len(prepared.fit_function_display_names) == n_funcs - - # Parameter names should match input keys - assert prepared.parameter_names == ['parameter1', 'parameter3 area'] - - # Expanded names should match what exists in dataset - for name in prepared.expanded_parameter_names: - assert name in parameter_analysis.parameters - - def test_parameter_property(self, parameter_analysis): - # WHEN - parameters = parameter_analysis.parameters - - # THEN EXPECT - assert isinstance(parameters, sc.Dataset) - assert set(parameters.keys()) == { - 'parameter1', - 'parameter2', - 'parameter3 area', - 'parameter3 width', - } - - # WHEN - Q = sc.array(dims=['Q'], values=[0.1, 0.2]) - new_data = sc.Dataset( - data={ - 'parameter4': sc.DataArray( - data=sc.array( - dims=['Q'], - values=[71.0, 12.0], - variances=[1.1, 2.2], - unit='meV', - ), - coords={'Q': Q}, - ), - 'parameter5': sc.DataArray( - data=sc.array( - dims=['Q'], - values=[8.5, 0.5], - variances=[2.15, 1.25], - unit='1/meV', - ), - coords={'Q': Q}, - ), - } - ) - - # THEN - parameter_analysis.parameters = new_data - - # EXPECT - assert parameter_analysis.parameters is new_data + # def test_parameter_property(self, parameter_analysis): + # # WHEN + # parameters = parameter_analysis.parameters + + # # THEN EXPECT + # assert isinstance(parameters, sc.Dataset) + # assert set(parameters.keys()) == { + # 'parameter1', + # 'parameter2', + # 'parameter3 area', + # 'parameter3 width', + # } + + # # WHEN + # Q = sc.array(dims=['Q'], values=[0.1, 0.2]) + # new_data = sc.Dataset( + # data={ + # 'parameter4': sc.DataArray( + # data=sc.array( + # dims=['Q'], + # values=[71.0, 12.0], + # variances=[1.1, 2.2], + # unit='meV', + # ), + # coords={'Q': Q}, + # ), + # 'parameter5': sc.DataArray( + # data=sc.array( + # dims=['Q'], + # values=[8.5, 0.5], + # variances=[2.15, 1.25], + # unit='1/meV', + # ), + # coords={'Q': Q}, + # ), + # } + # ) + + # # THEN + # parameter_analysis.parameters = new_data + + # # EXPECT + # assert parameter_analysis.parameters is new_data From 125ad1bbd8236b1febbf1ffde3de4ef686e9e2bc Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Fri, 1 May 2026 20:42:36 +0200 Subject: [PATCH 13/19] Adding some tests Co-authored-by: Copilot --- src/easydynamics/analysis/fit_bindings.py | 8 +- .../analysis/parameter_analysis.py | 20 +- .../analysis/test_parameter_analysis.py | 213 ++++++++++++++---- 3 files changed, 182 insertions(+), 59 deletions(-) diff --git a/src/easydynamics/analysis/fit_bindings.py b/src/easydynamics/analysis/fit_bindings.py index 3320722b..c6dd1110 100644 --- a/src/easydynamics/analysis/fit_bindings.py +++ b/src/easydynamics/analysis/fit_bindings.py @@ -70,15 +70,17 @@ def __init__( 2. Usage with a DiffusionModelBase and specific modes: >>> brownian_diffusion_model = sm.BrownianTranslationalDiffusion( - ... display_name='Brownian Diffusion', diffusion_coefficient=2.4e-9, scale=0.5 + ... display_name='Brownian Translational Diffusion', + ... diffusion_coefficient=2.4e-9, + ... scale=0.5, ... ) >>> binding = edyn.FitBinding( ... parameter_name='Lorentzian', ... model=brownian_diffusion_model, ... modes=['area', 'width'], ... ) - FitBinding(parameter_name=Lorentzian, model=Brownian Diffusion, modes=['area', 'width'], - display_name=FitBinding_1, unique_name=FitBinding_1) + FitBinding(parameter_name=Lorentzian, model=Brownian Translational Diffusion, + modes=['area', 'width'], display_name=FitBinding_1, unique_name=FitBinding_1) """ super().__init__(display_name=display_name, unique_name=unique_name) diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index e2cfb70f..2228857c 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -132,16 +132,12 @@ def fit(self) -> FitResults: Raises ------ ValueError - If no parameters DataSet is provided. If no fit functions are provided. If no parameter + If no parameters Dataset is provided. If no fit functions are provided. If no parameter names are found for the fit functions. - - RuntimeError - If there is a mismatch between the number of parameter names and the number of - callables in the fit bindings. """ if self._parameters is None: - raise ValueError('No parameters DataSet provided.') + raise ValueError('No parameters Dataset provided.') if not self._bindings: raise ValueError('No fit bindings provided.') @@ -155,13 +151,11 @@ def fit(self) -> FitResults: param_names = binding.get_parameter_names() callables = binding.build_callables() - if len(param_names) != len(callables): - raise RuntimeError('Mismatch between parameter names and callables.') - for pname, func in zip(param_names, callables, strict=True): if pname not in self._parameters: raise ValueError( - f"Dataset key '{pname}' from binding '{binding.parameter_name}' not found." + f"Parameter '{pname}' from binding '{binding.unique_name}' " + f'not found in parameters Dataset.' ) x, y, weight = self._get_xyweight_from_dataset(pname) @@ -321,10 +315,10 @@ def get_all_variables(self) -> list: list A list of all variables from the fit functions. """ - variables = [] + variables = set() for b in self._bindings: - variables.extend(b.model.get_all_variables()) - return variables + variables.update(b.model.get_all_variables()) + return list(variables) ############# # Private methods: verification and preparation diff --git a/tests/unit/easydynamics/analysis/test_parameter_analysis.py b/tests/unit/easydynamics/analysis/test_parameter_analysis.py index ed5c5864..467c9acc 100644 --- a/tests/unit/easydynamics/analysis/test_parameter_analysis.py +++ b/tests/unit/easydynamics/analysis/test_parameter_analysis.py @@ -2,9 +2,20 @@ # SPDX-License-Identifier: BSD-3-Clause +from unittest.mock import MagicMock +from unittest.mock import patch + +import numpy as np import pytest import scipp as sc +from easydynamics.analysis.fit_bindings import FitBinding +from easydynamics.analysis.parameter_analysis import ParameterAnalysis +from easydynamics.sample_model.components.polynomial import Polynomial +from easydynamics.sample_model.diffusion_model.brownian_translational_diffusion import ( + BrownianTranslationalDiffusion, +) + class TestParameterAnalysis: @pytest.fixture @@ -36,46 +47,162 @@ def dataset(self): } ) - # def test_parameter_property(self, parameter_analysis): - # # WHEN - # parameters = parameter_analysis.parameters - - # # THEN EXPECT - # assert isinstance(parameters, sc.Dataset) - # assert set(parameters.keys()) == { - # 'parameter1', - # 'parameter2', - # 'parameter3 area', - # 'parameter3 width', - # } - - # # WHEN - # Q = sc.array(dims=['Q'], values=[0.1, 0.2]) - # new_data = sc.Dataset( - # data={ - # 'parameter4': sc.DataArray( - # data=sc.array( - # dims=['Q'], - # values=[71.0, 12.0], - # variances=[1.1, 2.2], - # unit='meV', - # ), - # coords={'Q': Q}, - # ), - # 'parameter5': sc.DataArray( - # data=sc.array( - # dims=['Q'], - # values=[8.5, 0.5], - # variances=[2.15, 1.25], - # unit='1/meV', - # ), - # coords={'Q': Q}, - # ), - # } - # ) - - # # THEN - # parameter_analysis.parameters = new_data - - # # EXPECT - # assert parameter_analysis.parameters is new_data + @pytest.fixture + def parameter_analysis(self, dataset): + model = Polynomial(coefficients=[1.0, 0.5]) + diffusion_model = BrownianTranslationalDiffusion() + + fit_binding1 = FitBinding(parameter_name='parameter1', model=model) + fit_binding2 = FitBinding(parameter_name='parameter3', model=diffusion_model) + + return ParameterAnalysis(parameters=dataset, bindings=[fit_binding1, fit_binding2]) + + def test_initialization(self, parameter_analysis): + # WHEN THEN EXPECT + assert isinstance(parameter_analysis, ParameterAnalysis) + assert len(parameter_analysis.bindings) == 2 + assert parameter_analysis.bindings[0].parameter_name == 'parameter1' + assert parameter_analysis.bindings[1].parameter_name == 'parameter3' + + def test_parameter_property(self, parameter_analysis): + # WHEN + parameters = parameter_analysis.parameters + + # THEN EXPECT + assert isinstance(parameters, sc.Dataset) + assert set(parameters.keys()) == { + 'parameter1', + 'parameter2', + 'parameter3 area', + 'parameter3 width', + } + + # WHEN + Q = sc.array(dims=['Q'], values=[0.1, 0.2]) + new_data = sc.Dataset( + data={ + 'parameter4': sc.DataArray( + data=sc.array( + dims=['Q'], + values=[71.0, 12.0], + variances=[1.1, 2.2], + unit='meV', + ), + coords={'Q': Q}, + ), + 'parameter5': sc.DataArray( + data=sc.array( + dims=['Q'], + values=[8.5, 0.5], + variances=[2.15, 1.25], + unit='1/meV', + ), + coords={'Q': Q}, + ), + } + ) + + # THEN + parameter_analysis.parameters = new_data + + # EXPECT + assert parameter_analysis.parameters is new_data + + def test_bindings_property(self, parameter_analysis): + # WHEN + bindings = parameter_analysis.bindings + + # THEN EXPECT + assert isinstance(bindings, list) + assert len(bindings) == 2 + assert all(isinstance(b, FitBinding) for b in bindings) + + # WHEN + model = Polynomial(coefficients=[2.0, 1.0]) + new_binding = FitBinding(parameter_name='parameter2', model=model) + parameter_analysis.bindings = new_binding + + # THEN EXPECT + assert parameter_analysis.bindings == [new_binding] + + def test_fit_no_bindings_raises(self, parameter_analysis): + # WHEN + + # THEN + parameter_analysis.bindings = None + + # EXPECT + with pytest.raises(ValueError, match='No fit bindings provided'): + parameter_analysis.fit() + + def test_fit_no_parameters_raises(self, parameter_analysis): + # WHEN + + # THEN + parameter_analysis.parameters = None + + # EXPECT + with pytest.raises(ValueError, match='No parameters Dataset provided'): + parameter_analysis.fit() + + def test_fit_wrong_parameter_name_raises(self, parameter_analysis): + # WHEN + model = Polynomial(coefficients=[2.0, 1.0]) + incorrect_binding = FitBinding(parameter_name='nonexistent_parameter', model=model) + parameter_analysis.bindings = incorrect_binding + + # THEN EXPECT + with pytest.raises( + ValueError, + match="Parameter 'nonexistent_parameter' from binding", + ): + parameter_analysis.fit() + + def test_fit_success(self, parameter_analysis): + # WHEN + mock_result = MagicMock() + + with patch('easydynamics.analysis.parameter_analysis.MultiFitter') as MockMultiFitter: + instance = MockMultiFitter.return_value + instance.fit.return_value = mock_result + + # THEN + result = parameter_analysis.fit() + + # EXPECT + assert MockMultiFitter.called + + kwargs = MockMultiFitter.call_args.kwargs + assert 'fit_objects' in kwargs + assert 'fit_functions' in kwargs + + # Expect 3 fits: + # - parameter1 → 1 callable + # - parameter3 → 2 callables (area + width) + assert len(kwargs['fit_objects']) == 3 + assert len(kwargs['fit_functions']) == 3 + + # --- Fit called correctly --- + instance.fit.assert_called_once() + + call_kwargs = instance.fit.call_args.kwargs + + x = call_kwargs['x'] + y = call_kwargs['y'] + w = call_kwargs['weights'] + + assert len(x) == 3 + assert len(y) == 3 + assert len(w) == 3 + + # Check one concrete value + np.testing.assert_allclose(x[0], [0.1, 0.2]) + np.testing.assert_allclose(y[0], [1.0, 2.0]) + + expected_w = 1 / np.sqrt([0.1, 0.2]) + np.testing.assert_allclose(w[0], expected_w) + + assert result is mock_result + + +# TEST PLOT From 131e5c4d922d2825957573cab173f6faeff5e27e Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Sun, 3 May 2026 21:57:37 +0200 Subject: [PATCH 14/19] more tests Co-authored-by: Copilot --- .../analysis/parameter_analysis.py | 57 +++- .../analysis/test_parameter_analysis.py | 312 +++++++++++++++++- 2 files changed, 365 insertions(+), 4 deletions(-) diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 2228857c..7940ca05 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -211,7 +211,7 @@ def plot( if self._parameters is None: raise ValueError('No parameters available to plot.') - model_dataset = self.build_model_dataset(self._bindings) + model_dataset = self.calculate_model_dataset(self._bindings) # Need a check of names # for name in names: @@ -277,10 +277,38 @@ def plot( return pp.plot(data_and_model, **plot_kwargs) - def build_model_dataset(self, bindings: list[FitBinding]) -> sc.Dataset: + def calculate_model_dataset(self, bindings: list[FitBinding]) -> sc.Dataset: """ Evaluate all bindings into a sc.Dataset of model predictions. + + Parameters + ---------- + bindings : list[FitBinding] + The bindings to evaluate. + + Returns + ------- + sc.Dataset + A sc.Dataset containing the model predictions for all bindings. + + Raises + ------ + ValueError + If any parameter name from the bindings is not found in the parameters DataSet. + + TypeError + If bindings is not a list of FitBinding objects. """ + + if self.parameters is None: + raise ValueError('No parameters Dataset provided.') + + if not bindings: + raise ValueError('No fit bindings provided.') + + if not isinstance(bindings, list) or not all(isinstance(b, FitBinding) for b in bindings): + raise TypeError('bindings must be a list of FitBinding objects.') + arrays = {} for b in bindings: @@ -289,7 +317,12 @@ def build_model_dataset(self, bindings: list[FitBinding]) -> sc.Dataset: callables = b.build_callables() for pname, mname, func in zip(param_names, model_names, callables, strict=True): - da = self._parameters[pname] + if pname not in self.parameters: + raise ValueError( + f"Parameter '{pname}' from binding '{b.unique_name}' " + f'not found in parameters Dataset.' + ) + da = self.parameters[pname] x = da.coords['Q'] y_model = func(x.values) @@ -301,9 +334,27 @@ def build_model_dataset(self, bindings: list[FitBinding]) -> sc.Dataset: return sc.Dataset(arrays) def append_binding(self, binding: FitBinding) -> None: + """ + Append a FitBinding to the list of bindings for the parameter analysis. + + Parameters + ---------- + binding : FitBinding + The FitBinding to append. + + Raises + ------ + TypeError + If binding is not a FitBinding object. + """ + if not isinstance(binding, FitBinding): + raise TypeError('binding must be a FitBinding object.') self._bindings.append(binding) def clear_bindings(self) -> None: + """ + Clear all FitBindings from the list of bindings for the parameter analysis. + """ self._bindings.clear() def get_all_variables(self) -> list: diff --git a/tests/unit/easydynamics/analysis/test_parameter_analysis.py b/tests/unit/easydynamics/analysis/test_parameter_analysis.py index 467c9acc..1585ad41 100644 --- a/tests/unit/easydynamics/analysis/test_parameter_analysis.py +++ b/tests/unit/easydynamics/analysis/test_parameter_analysis.py @@ -11,6 +11,7 @@ from easydynamics.analysis.fit_bindings import FitBinding from easydynamics.analysis.parameter_analysis import ParameterAnalysis +from easydynamics.sample_model.components.gaussian import Gaussian from easydynamics.sample_model.components.polynomial import Polynomial from easydynamics.sample_model.diffusion_model.brownian_translational_diffusion import ( BrownianTranslationalDiffusion, @@ -204,5 +205,314 @@ def test_fit_success(self, parameter_analysis): assert result is mock_result + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + # TEST PLOT + + @pytest.mark.parametrize( + 'set_pars_none, bindings, expected_exception, match', + [ + # No parameters + ( + True, + [MagicMock(spec=FitBinding)], + ValueError, + 'No parameters Dataset provided', + ), + # No bindings + ( + False, + [], + ValueError, + 'No fit bindings provided', + ), + # Wrong bindings type + ( + False, + 'not_a_list', + TypeError, + 'bindings must be a list of FitBinding objects', + ), + ], + ids=['no_parameters', 'no_bindings', 'wrong_bindings_type'], + ) + def test_calculate_model_dataset_invalid_inputs( + self, parameter_analysis, set_pars_none, bindings, expected_exception, match + ): + # WHEN + if set_pars_none: + parameter_analysis.parameters = None + else: + # keep existing valid dataset + pass + + # THEN EXPECT + with pytest.raises(expected_exception, match=match): + parameter_analysis.calculate_model_dataset(bindings) + + def test_calculate_model_dataset_missing_parameter_raises(self, parameter_analysis): + binding = parameter_analysis.bindings[0] + + with ( + patch.object(binding, 'get_parameter_names', return_value=['missing_name']), + patch.object(binding, 'get_model_names', return_value=['model']), + patch.object(binding, 'build_callables', return_value=[lambda x: x]), + pytest.raises(ValueError, match='not found in parameters Dataset'), + ): + parameter_analysis.calculate_model_dataset([binding]) + + def test_calculate_model_dataset_single_binding(self, parameter_analysis): + # WHEN + binding = parameter_analysis.bindings[0] + + # Mock callable to return predictable output + mock_callable = MagicMock(return_value=np.array([10.0, 20.0])) + + with ( + patch.object(binding, 'build_callables', return_value=[mock_callable]), + patch.object(binding, 'get_model_names', return_value=['model1']), + patch.object(binding, 'get_parameter_names', return_value=['parameter1']), + ): + # THEN + result = parameter_analysis.calculate_model_dataset([binding]) + + # EXPECT + assert isinstance(result, sc.Dataset) + assert 'model1' in result + + da = result['model1'] + + np.testing.assert_allclose(da.values, [10.0, 20.0]) + np.testing.assert_allclose(da.coords['Q'].values, [0.1, 0.2]) + assert da.unit == parameter_analysis.parameters['parameter1'].unit + mock_callable.assert_called_once() + + args, kwargs = mock_callable.call_args + np.testing.assert_allclose(args[0], [0.1, 0.2]) + assert kwargs == {} + + def test_calculate_model_dataset_multiple_bindings(self, parameter_analysis): + # WHEN + binding1 = parameter_analysis.bindings[0] + binding2 = parameter_analysis.bindings[1] + + # Mock callable to return predictable output + mock_callable1 = MagicMock(return_value=np.array([10.0, 20.0])) + mock_callable2 = MagicMock(return_value=np.array([30.0, 40.0])) + + with ( + patch.object(binding1, 'build_callables', return_value=[mock_callable1]), + patch.object(binding1, 'get_model_names', return_value=['model1']), + patch.object(binding1, 'get_parameter_names', return_value=['parameter1']), + patch.object(binding2, 'build_callables', return_value=[mock_callable2]), + patch.object(binding2, 'get_model_names', return_value=['model2']), + patch.object(binding2, 'get_parameter_names', return_value=['parameter2']), + ): + # THEN + result = parameter_analysis.calculate_model_dataset([binding1, binding2]) + + # EXPECT + assert isinstance(result, sc.Dataset) + assert 'model1' in result + assert 'model2' in result + + da1 = result['model1'] + da2 = result['model2'] + + np.testing.assert_allclose(da1.values, [10.0, 20.0]) + np.testing.assert_allclose(da1.coords['Q'].values, [0.1, 0.2]) + assert da1.unit == parameter_analysis.parameters['parameter1'].unit + + np.testing.assert_allclose(da2.values, [30.0, 40.0]) + np.testing.assert_allclose(da2.coords['Q'].values, [0.1, 0.2]) + assert da2.unit == parameter_analysis.parameters['parameter2'].unit + mock_callable1.assert_called_once() + args, kwargs = mock_callable1.call_args + np.testing.assert_allclose(args[0], [0.1, 0.2]) + assert kwargs == {} + + mock_callable2.assert_called_once() + args, kwargs = mock_callable2.call_args + np.testing.assert_allclose(args[0], [0.1, 0.2]) + assert kwargs == {} + + def test_calculate_model_dataset_multiple_bindings_diffusion(self, parameter_analysis): + # WHEN + binding1 = parameter_analysis.bindings[0] + binding2 = parameter_analysis.bindings[1] + + # Mock callable to return predictable output + mock_callable1 = MagicMock(return_value=np.array([10.0, 20.0])) + mock_callable2a = MagicMock(return_value=np.array([30.0, 40.0])) + mock_callable2w = MagicMock(return_value=np.array([50.0, 60.0])) + + with ( + patch.object(binding1, 'build_callables', return_value=[mock_callable1]), + patch.object(binding1, 'get_model_names', return_value=['model1']), + patch.object(binding1, 'get_parameter_names', return_value=['parameter1']), + patch.object( + binding2, + 'build_callables', + return_value=[mock_callable2a, mock_callable2w], + ), + patch.object(binding2, 'get_model_names', return_value=['model2a', 'model2w']), + patch.object( + binding2, + 'get_parameter_names', + return_value=['parameter3 area', 'parameter3 width'], + ), + ): + # THEN + result = parameter_analysis.calculate_model_dataset([binding1, binding2]) + + # EXPECT + assert isinstance(result, sc.Dataset) + assert 'model1' in result + assert 'model2a' in result + assert 'model2w' in result + + da1 = result['model1'] + da2a = result['model2a'] + da2w = result['model2w'] -# TEST PLOT + np.testing.assert_allclose(da1.values, [10.0, 20.0]) + np.testing.assert_allclose(da1.coords['Q'].values, [0.1, 0.2]) + assert da1.unit == parameter_analysis.parameters['parameter1'].unit + + np.testing.assert_allclose(da2a.values, [30.0, 40.0]) + np.testing.assert_allclose(da2a.coords['Q'].values, [0.1, 0.2]) + assert da2a.unit == parameter_analysis.parameters['parameter3 area'].unit + + np.testing.assert_allclose(da2w.values, [50.0, 60.0]) + np.testing.assert_allclose(da2w.coords['Q'].values, [0.1, 0.2]) + assert da2w.unit == parameter_analysis.parameters['parameter3 width'].unit + + args, kwargs = mock_callable1.call_args + np.testing.assert_allclose(args[0], [0.1, 0.2]) + assert kwargs == {} + + args, kwargs = mock_callable2a.call_args + np.testing.assert_allclose(args[0], [0.1, 0.2]) + assert kwargs == {} + + args, kwargs = mock_callable2w.call_args + np.testing.assert_allclose(args[0], [0.1, 0.2]) + assert kwargs == {} + + def test_append_binding(self, parameter_analysis): + # WHEN + model = Polynomial(coefficients=[2.0, 1.0]) + new_binding = FitBinding(parameter_name='parameter2', model=model) + + # THEN + parameter_analysis.append_binding(new_binding) + + # EXPECT + assert len(parameter_analysis.bindings) == 3 + assert parameter_analysis.bindings[-1] == new_binding + + def test_append_binding_wrong_type_raises(self, parameter_analysis): + # WHEN + new_binding = 'not_a_fit_binding' + + # THEN EXPECT + with pytest.raises(TypeError, match='binding must be a FitBinding object'): + parameter_analysis.append_binding(new_binding) + + def test_clear_bindings(self, parameter_analysis): + # WHEN + parameter_analysis.clear_bindings() + + # THEN EXPECT + assert len(parameter_analysis.bindings) == 0 + + def test_get_all_variables(self, parameter_analysis): + # WHEN + + # THEN + variables = parameter_analysis.get_all_variables() + + # EXPECT + expected_variables = [] + expected_variables.extend(parameter_analysis.bindings[0].model.get_all_variables()) + expected_variables.extend(parameter_analysis.bindings[1].model.get_all_variables()) + assert set(variables) == set(expected_variables) + + def test_get_all_variables_no_bindings(self, parameter_analysis): + # WHEN + parameter_analysis.clear_bindings() + + # THEN EXPECT + variables = parameter_analysis.get_all_variables() + assert variables == [] + + def test_get_all_variables_overlapping_variables(self, parameter_analysis): + # WHEN + # Create two bindings with overlapping variables + model1 = Gaussian(display_name='model1') + + binding1 = FitBinding(parameter_name='parameter1', model=model1) + binding2 = FitBinding(parameter_name='parameter2', model=model1) + + parameter_analysis.bindings = [binding1, binding2] + + # THEN + variables = parameter_analysis.get_all_variables() + + # EXPECT + assert isinstance(variables, list) + assert set(variables) == set(model1.get_all_variables()) + assert len(variables) == len(model1.get_all_variables()) + + @pytest.mark.parametrize( + 'input_bindings, expected_length', + [ + (None, 0), + ('single', 1), + ('list', 2), + ], + ) + def test_verify_bindings_valid(self, parameter_analysis, input_bindings, expected_length): + # WHEN + b1 = parameter_analysis.bindings[0] + b2 = parameter_analysis.bindings[1] + + if input_bindings == 'single': + input_bindings = b1 + elif input_bindings == 'list': + input_bindings = [b1, b2] + + # THEN + result = parameter_analysis._verify_bindings(input_bindings) + + # EXPECT + assert isinstance(result, list) + assert len(result) == expected_length + + if expected_length == 1: + assert result[0] is b1 + if expected_length == 2: + assert result == [b1, b2] + + @pytest.mark.parametrize( + 'invalid_bindings', + [ + 'not_a_list_or_fit_binding', + [MagicMock(spec=FitBinding), 'not_a_fit_binding'], + ], + ids=['wrong_type', 'list_with_wrong_type'], + ) + def test_verify_bindings_invalid(self, parameter_analysis, invalid_bindings): + # WHEN THEN EXPECT + with pytest.raises((ValueError, TypeError)): + parameter_analysis._verify_bindings(invalid_bindings) From 8b27049783337a3b58154b20a05a119a35972f36 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Mon, 4 May 2026 15:27:16 +0200 Subject: [PATCH 15/19] more tests Co-authored-by: Copilot --- docs/docs/tutorials/tutorial1_brownian.ipynb | 10 + src/easydynamics/__init__.py | 2 +- .../{fit_bindings.py => fit_binding.py} | 0 .../analysis/parameter_analysis.py | 165 +++++++++----- .../analysis/test_parameter_analysis.py | 209 +++++++++++++++++- 5 files changed, 325 insertions(+), 61 deletions(-) rename src/easydynamics/analysis/{fit_bindings.py => fit_binding.py} (100%) diff --git a/docs/docs/tutorials/tutorial1_brownian.ipynb b/docs/docs/tutorials/tutorial1_brownian.ipynb index 58eede4d..ea0b90df 100644 --- a/docs/docs/tutorials/tutorial1_brownian.ipynb +++ b/docs/docs/tutorials/tutorial1_brownian.ipynb @@ -530,6 +530,16 @@ "parameter_analysis.plot()" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "57b76d06", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_analysis.plot(names=['Polynomial_c0'])" + ] + }, { "cell_type": "markdown", "id": "64babb01", diff --git a/src/easydynamics/__init__.py b/src/easydynamics/__init__.py index 93c7084c..f4c956e5 100644 --- a/src/easydynamics/__init__.py +++ b/src/easydynamics/__init__.py @@ -3,7 +3,7 @@ """EasyDynamics library.""" from easydynamics.analysis import Analysis -from easydynamics.analysis.fit_bindings import FitBinding +from easydynamics.analysis.fit_binding import FitBinding from easydynamics.analysis.parameter_analysis import ParameterAnalysis from easydynamics.experiment import Experiment from easydynamics.settings.convolution_settings import ConvolutionSettings diff --git a/src/easydynamics/analysis/fit_bindings.py b/src/easydynamics/analysis/fit_binding.py similarity index 100% rename from src/easydynamics/analysis/fit_bindings.py rename to src/easydynamics/analysis/fit_binding.py diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 7940ca05..80a338f2 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -13,7 +13,7 @@ from plopp.backends.matplotlib.figure import InteractiveFigure from easydynamics.analysis.analysis import Analysis -from easydynamics.analysis.fit_bindings import FitBinding +from easydynamics.analysis.fit_binding import FitBinding from easydynamics.base_classes.easydynamics_modelbase import EasyDynamicsModelBase from easydynamics.sample_model.component_collection import ComponentCollection from easydynamics.sample_model.components.model_component import ModelComponent @@ -187,7 +187,7 @@ def plot( Parameters ---------- names : str | list[str] | None, default=None - The names of the parameters to plot. If None, all parameters are plotted. + The names of the parameters to plot. If None, all parameters with bindings are plotted. **kwargs : dict[str, Any] Additional keyword arguments to pass to the plotting function. @@ -199,42 +199,32 @@ def plot( Raises ------ ValueError - If any of the specified parameter names are not found in the parameters DataSet. If the - units of the specified parameters are not consistent. + If the units of the specified parameters are not consistent. RuntimeError - If plot_data() is called outside of a Jupyter notebook environment. + If plot() is called outside of a Jupyter notebook environment. """ if not _in_notebook(): - raise RuntimeError('plot_data() can only be used in a Jupyter notebook environment.') + raise RuntimeError('plot() can only be used in a Jupyter notebook environment.') - if self._parameters is None: + if self.parameters is None: raise ValueError('No parameters available to plot.') - model_dataset = self.calculate_model_dataset(self._bindings) - - # Need a check of names - # for name in names: - # if name not in self._parameters: - # raise ValueError( - # f"Parameter name '{name}' not found in parameters DataSet." - # ) - - if names is not None: - if isinstance(names, str): - names = [names] + full_model_dataset = None + if self.bindings is not None: + full_model_dataset = self.calculate_model_dataset(self.bindings) - model_dataset = sc.Dataset({k: model_dataset[k] for k in names}) + if names is None: + names = [] + for b in self.bindings: + names.extend(b.get_parameter_names()) - # # Make a new Dataset with only the desired parameters. - # data = sc.Dataset(coords=self._parameters.coords) + names = self._normalize_names(names) - # units = [self._parameters[name].unit for name in names] - # first_unit = units[0] - # if any(unit != first_unit for unit in units): - # raise ValueError( - # f"Units are not consistent, and cannot be plotted together: {units}" - # ) + units = [self.parameters[name].unit for name in names] + first_unit = units[0] + if any(unit != first_unit for unit in units): + raise ValueError(f'Units are not consistent, and cannot be plotted together: {units}') color_cycle = itertools.cycle(rcParams['axes.prop_cycle'].by_key()['color']) markers = itertools.cycle(['o', 's', 'D', '^', 'v', '<', '>']) @@ -248,21 +238,31 @@ def plot( } data_arrays = {} + model_arrays = {} - for b in self._bindings: - param_names = b.get_parameter_names() - model_names = b.get_model_names() + # map parameter names to model names + param_to_model = {} + if self.bindings is not None: + for b in self.bindings: + param_names = b.get_parameter_names() + model_names = b.get_model_names() - for pname, mname in zip(param_names, model_names, strict=True): - data_arrays[pname] = self._parameters[pname] - color = next(color_cycle) - marker = next(markers) + param_to_model.update(dict(zip(param_names, model_names, strict=True))) - # Data styling - plot_kwargs['linestyle'][pname] = 'none' - plot_kwargs['marker'][pname] = marker - plot_kwargs['color'][pname] = color - plot_kwargs['markerfacecolor'][pname] = 'none' + for pname in names: + data_arrays[pname] = self.parameters[pname] + color = next(color_cycle) + marker = next(markers) + + # Data styling + plot_kwargs['linestyle'][pname] = 'none' + plot_kwargs['marker'][pname] = marker + plot_kwargs['color'][pname] = color + plot_kwargs['markerfacecolor'][pname] = 'none' + + if full_model_dataset is not None and pname in param_to_model: + mname = param_to_model[pname] + model_arrays[mname] = full_model_dataset[mname] # Model styling plot_kwargs['linestyle'][mname] = '--' @@ -273,7 +273,7 @@ def plot( plot_kwargs.update(kwargs) data_and_model = sc.Dataset(data_arrays) - data_and_model.update(model_dataset) + data_and_model.update(model_arrays) return pp.plot(data_and_model, **plot_kwargs) @@ -294,7 +294,7 @@ def calculate_model_dataset(self, bindings: list[FitBinding]) -> sc.Dataset: Raises ------ ValueError - If any parameter name from the bindings is not found in the parameters DataSet. + If any parameter name from the bindings is not found in the parameters Dataset. TypeError If bindings is not a list of FitBinding objects. @@ -423,18 +423,58 @@ def _verify_parameters(self, parameters: sc.Dataset | Analysis | None) -> sc.Dat ValueError If parameters is a sc.Dataset but does not have a 'Q' coordinate. """ - if parameters is not None and not isinstance(parameters, (sc.Dataset, Analysis)): - raise TypeError('parameters must be an sc.Dataset, an Analysis, or None.') + if parameters is None: + return None + + if not isinstance(parameters, (sc.Dataset, Analysis)): + raise TypeError(r'parameters must be a sc.Dataset, an Analysis, or None.') if isinstance(parameters, Analysis): verified_parameters = parameters.parameters_to_dataset() else: verified_parameters = parameters - if verified_parameters is not None and 'Q' not in verified_parameters.coords: - raise ValueError("parameters must have a 'Q' coordinate.") + if 'Q' not in verified_parameters.coords: + raise ValueError(r"parameters must have a 'Q' coordinate.") return verified_parameters + def _normalize_names(self, names: str | list[str] | None) -> list[str] | None: + """ + Normalize the names input to a list of strings and verify that they exist in the parameters + Dataset. + + Parameters + ---------- + names : str | list[str] | None + The names to normalize and verify. + + Returns + ------- + list[str] | None + The normalized list of names, or None if names was None. + + Raises + ------ + ValueError + If any of the specified names are not found in the parameters Dataset, or if names is a + list that contains non-string elements. + """ + if names is None: + return None + if not isinstance(names, (str, list)): + raise ValueError('names must be a string, a list of strings, or None.') + if isinstance(names, list): + if not all(isinstance(name, str) for name in names): + raise ValueError('All names in the list must be strings.') + for name in names: + if name not in self.parameters: + raise ValueError(f"Parameter name '{name}' not found in parameters Dataset.") + if isinstance(names, str): + if names not in self.parameters: + raise ValueError(f"Parameter name '{names}' not found in parameters Dataset.") + names = [names] + return names + ############# # Private methods ############# @@ -443,7 +483,7 @@ def _get_xyweight_from_dataset( self, parameter_name: str ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ - Get the x, y, and weight values for a given parameter name from the parameters DataSet. + Get the x, y, and weight values for a given parameter name from the parameters Dataset. Parameters ---------- @@ -458,17 +498,24 @@ def _get_xyweight_from_dataset( Raises ------ ValueError - If the parameter name is not found in the parameters DataSet. If non-finite weights are + If the parameter name is not found in the parameters Dataset. If non-finite weights are found for the parameter. """ if self._parameters is None: - raise ValueError('No parameters DataSet provided.') + raise ValueError('No parameters Dataset provided.') if parameter_name not in self._parameters: - raise ValueError(f"Parameter name '{parameter_name}' not found in parameters DataSet.") - - weight = 1 / self._parameters[parameter_name].variances ** 0.5 - if not np.all(np.isfinite(weight)): - raise ValueError(f"Non-finite weights found for parameter '{parameter_name}'") + raise ValueError(f"Parameter name '{parameter_name}' not found in parameters Dataset.") + + variances = self._parameters[parameter_name].variances + if variances is None: + weight = np.ones_like(self._parameters[parameter_name].values) + elif np.any(~np.isfinite(variances)) or np.any(variances <= 0): + raise ValueError( + f"Non-finite variances found for parameter '{parameter_name}', " + f'cannot compute weights.' + ) + else: + weight = 1 / np.sqrt(variances) return ( self._parameters[parameter_name].coords['Q'].values, @@ -499,10 +546,10 @@ def __repr__(self) -> str: return ( f'{cls}(\n' - f' display_name={self.display_name!r},\n' - f' unique_name={self.unique_name!r},\n' - f' n_parameters={n_params},\n' - f' parameter_names={param_names},\n' - f' bindings={binding_info}\n' + f'display_name={self.display_name},\n' + f'unique_name={self.unique_name},\n' + f'n_parameters={n_params},\n' + f'parameter_names={param_names},\n' + f'bindings={binding_info}\n' f')' ) diff --git a/tests/unit/easydynamics/analysis/test_parameter_analysis.py b/tests/unit/easydynamics/analysis/test_parameter_analysis.py index 1585ad41..2ac96627 100644 --- a/tests/unit/easydynamics/analysis/test_parameter_analysis.py +++ b/tests/unit/easydynamics/analysis/test_parameter_analysis.py @@ -9,7 +9,8 @@ import pytest import scipp as sc -from easydynamics.analysis.fit_bindings import FitBinding +from easydynamics.analysis.analysis import Analysis +from easydynamics.analysis.fit_binding import FitBinding from easydynamics.analysis.parameter_analysis import ParameterAnalysis from easydynamics.sample_model.components.gaussian import Gaussian from easydynamics.sample_model.components.polynomial import Polynomial @@ -205,6 +206,50 @@ def test_fit_success(self, parameter_analysis): assert result is mock_result + def test_plot_not_in_notebook_raises(self, parameter_analysis): + # WHEN / THEN / EXPECT + with ( + patch( + 'easydynamics.analysis.parameter_analysis._in_notebook', + return_value=False, + ), + pytest.raises( + RuntimeError, + match=r'can only be used in a Jupyter notebook environment', + ), + ): + parameter_analysis.plot() + + def test_plot_no_parameters_raises(self, parameter_analysis): + # WHEN + parameter_analysis.parameters = None + + # THEN EXPECT + with ( + patch( + 'easydynamics.analysis.parameter_analysis._in_notebook', + return_value=True, + ), + pytest.raises(ValueError, match=r'No parameters available to plot.'), + ): + parameter_analysis.plot() + + # def test_plot_inconsistent_units_raises(self, parameter_analysis): + # # WHEN + + # # THEN EXPECT + # with ( + # patch( + # "easydynamics.analysis.parameter_analysis._in_notebook", + # return_value=True, + # ), + # pytest.raises( + # ValueError, + # match=r"Units are not consistent, and cannot be plotted together.", + # ), + # ): + # parameter_analysis.plot() + # TEST PLOT # TEST PLOT # TEST PLOT @@ -516,3 +561,165 @@ def test_verify_bindings_invalid(self, parameter_analysis, invalid_bindings): # WHEN THEN EXPECT with pytest.raises((ValueError, TypeError)): parameter_analysis._verify_bindings(invalid_bindings) + + def test_verify_parameters_none(self, parameter_analysis): + # WHEN THEN EXPECT + assert parameter_analysis._verify_parameters(None) is None + + def test_verify_parameters_wrong_type(self, parameter_analysis): + # WHEN THEN EXPECT + with pytest.raises( + TypeError, match=r'parameters must be a sc.Dataset, an Analysis, or None' + ): + parameter_analysis._verify_parameters('not_a_dataset_or_analysis') + + def test_verify_parameters_wrong_coordinate(self, parameter_analysis): + # WHEN + T = sc.array(dims=['T'], values=[0.1, 0.2]) + wrong_dataset = sc.Dataset( + data={ + 'parameter1': sc.DataArray( + data=sc.array(dims=['T'], values=[1.0, 2.0], variances=[0.1, 0.2], unit='meV'), + coords={'T': T}, + ), + } + ) + + # THEN EXPECT + with pytest.raises(ValueError, match="parameters must have a 'Q' coordinate"): + parameter_analysis._verify_parameters(wrong_dataset) + + @pytest.mark.parametrize( + 'input_type', + ['dataset', 'analysis'], + ) + def test_verify_parameters_valid_inputs(self, parameter_analysis, dataset, input_type): + # WHEN + if input_type == 'dataset': + input_value = dataset + else: + mock_analysis = MagicMock(spec=Analysis) + mock_analysis.parameters_to_dataset.return_value = dataset + input_value = mock_analysis + + # THEN + result = parameter_analysis._verify_parameters(input_value) + + # EXPECT + assert result is dataset + + if input_type == 'analysis': + input_value.parameters_to_dataset.assert_called_once() + + def test_normalize_names_none(self, parameter_analysis): + # WHEN THEN EXPECT + assert parameter_analysis._normalize_names(None) is None + + def test_normalize_names_wrong_type(self, parameter_analysis): + # WHEN THEN EXPECT + with pytest.raises( + ValueError, match=r'names must be a string, a list of strings, or None.' + ): + parameter_analysis._normalize_names(123) + + def test_normalize_names_list_with_non_string(self, parameter_analysis): + # WHEN THEN EXPECT + with pytest.raises(ValueError, match=r'All names in the list must be strings.'): + parameter_analysis._normalize_names(['parameter1', 123]) + + def test_normalize_names_nonexistent_parameter(self, parameter_analysis): + # WHEN THEN EXPECT + with pytest.raises( + ValueError, + match=r"Parameter name 'nonexistent_parameter' not found in parameters Dataset.", + ): + parameter_analysis._normalize_names('nonexistent_parameter') + + with pytest.raises( + ValueError, + match=r"Parameter name 'nonexistent_parameter' not found in parameters Dataset.", + ): + parameter_analysis._normalize_names(['parameter1', 'nonexistent_parameter']) + + def test_normalize_names_valid_string(self, parameter_analysis): + # WHEN + result = parameter_analysis._normalize_names('parameter1') + + # THEN EXPECT + assert result == ['parameter1'] + + def test_normalize_names_valid_list(self, parameter_analysis): + # WHEN + result = parameter_analysis._normalize_names(['parameter1', 'parameter2']) + + # THEN EXPECT + assert result == ['parameter1', 'parameter2'] + + def test_get_xyweight_from_dataset_no_parameters_raises(self, parameter_analysis): + # WHEN + parameter_analysis.parameters = None + + # THEN EXPECT + with pytest.raises(ValueError, match='No parameters Dataset provided'): + parameter_analysis._get_xyweight_from_dataset('parameter1') + + def test_get_xyweight_from_dataset_wrong_parameter_name_raises(self, parameter_analysis): + # WHEN THEN EXPECT + with pytest.raises( + ValueError, + match="Parameter name 'nonexistent_parameter' not found in parameters Dataset", + ): + parameter_analysis._get_xyweight_from_dataset('nonexistent_parameter') + + @pytest.mark.parametrize( + 'non_finite_variance', + [np.inf, -np.inf, np.nan, -1.0, 0.0], + ids=['inf', '-inf', 'nan', 'negative', 'zero'], + ) + def test_get_xyweight_from_dataset_non_finite_weights_raises( + self, parameter_analysis, non_finite_variance + ): + # WHEN + Q = sc.array(dims=['Q'], values=[0.1, 0.2]) + parameter_analysis.parameters = sc.Dataset( + data={ + 'parameter1': sc.DataArray( + data=sc.array( + dims=['Q'], + values=[1.0, 2.0], + variances=[1.0, non_finite_variance], + unit='meV', + ), + coords={'Q': Q}, + ), + } + ) + + # THEN EXPECT + with pytest.raises( + ValueError, match="Non-finite variances found for parameter 'parameter1'" + ): + parameter_analysis._get_xyweight_from_dataset('parameter1') + + def test_get_xyweight_from_dataset_valid(self, parameter_analysis): + # WHEN THEN + x, y, w = parameter_analysis._get_xyweight_from_dataset('parameter1') + + # EXPECT + np.testing.assert_allclose(x, [0.1, 0.2]) + np.testing.assert_allclose(y, [1.0, 2.0]) + expected_w = 1 / np.sqrt([0.1, 0.2]) + np.testing.assert_allclose(w, expected_w) + + def test_repr(self, parameter_analysis): + # WHEN + repr_str = repr(parameter_analysis) + + # THEN EXPECT + assert isinstance(repr_str, str) + assert 'ParameterAnalysis' in repr_str + assert f'display_name={parameter_analysis.display_name}' in repr_str + assert f'unique_name={parameter_analysis.unique_name}' in repr_str + assert f'n_parameters={len(parameter_analysis.parameters)}' in repr_str + assert 'parameter_names=' in repr_str + assert 'bindings=' in repr_str From 525a4f8142809a98ef7c71e23d6327698de41ad6 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 5 May 2026 10:44:25 +0200 Subject: [PATCH 16/19] test fit bindings Co-authored-by: Copilot --- src/easydynamics/analysis/fit_binding.py | 69 ++--- .../easydynamics/analysis/test_fit_binding.py | 289 ++++++++++++++++++ 2 files changed, 324 insertions(+), 34 deletions(-) create mode 100644 tests/unit/easydynamics/analysis/test_fit_binding.py diff --git a/src/easydynamics/analysis/fit_binding.py b/src/easydynamics/analysis/fit_binding.py index c6dd1110..ad11ff60 100644 --- a/src/easydynamics/analysis/fit_binding.py +++ b/src/easydynamics/analysis/fit_binding.py @@ -52,7 +52,7 @@ def __init__( Raises ------ - ValueError + TypeError If parameter_name is not a string, if model is not a ModelComponent, ComponentCollection or DiffusionModelBase, or if modes is not a string, list of strings, or None. @@ -86,15 +86,18 @@ def __init__( super().__init__(display_name=display_name, unique_name=unique_name) if not isinstance(parameter_name, str): - raise ValueError('parameter_name must be a string') + raise TypeError('parameter_name must be a string') if not isinstance(model, (ModelComponent, ComponentCollection, DiffusionModelBase)): - raise ValueError( + raise TypeError( 'model must be a ModelComponent, ComponentCollection, or DiffusionModelBase' ) if modes is not None and not isinstance(modes, (str, list)): - raise ValueError('modes must be a string, list of strings, or None') + raise TypeError('modes must be a string, list of strings, or None') + + if isinstance(modes, list) and not all(isinstance(mode, str) for mode in modes): + raise TypeError('All modes in the list must be strings') self._parameter_name = parameter_name self._model = model @@ -128,11 +131,11 @@ def parameter_name(self, value: str) -> None: Raises ------ - ValueError + TypeError If the value is not a string. """ if not isinstance(value, str): - raise ValueError('parameter_name must be a string') + raise TypeError('parameter_name must be a string') self._parameter_name = value @property @@ -160,12 +163,12 @@ def model(self, value: ModelComponent | ComponentCollection | DiffusionModelBase Raises ------ - ValueError + TypeError If the value is not a ModelComponent, ComponentCollection, or DiffusionModelBase. """ if not isinstance(value, (ModelComponent, ComponentCollection, DiffusionModelBase)): - raise ValueError( - 'model must be a ModelComponent, ComponentCollection, or DiffusionModelBase' + raise TypeError( + 'model must be a ModelComponent, ComponentCollection, or DiffusionModelBase.' ) self._model = value @@ -194,11 +197,16 @@ def modes(self, value: str | list[str] | None) -> None: Raises ------ - ValueError + TypeError If the value is not a string, list of strings, or None. """ if value is not None and not isinstance(value, (str, list)): - raise ValueError('modes must be a string, list of strings, or None') + raise TypeError('modes must be a string, list of strings, or None') + + if isinstance(value, str): + value = [value] + if isinstance(value, list) and not all(isinstance(mode, str) for mode in value): + raise TypeError('All modes in the list must be strings') self._modes = value # ------------------------------------------------------------------ @@ -214,12 +222,7 @@ def build_callables(self) -> list[Callable]: list[Callable] A list of callables for fitting. """ - if isinstance(self.modes, str): - modes = [self.modes] - elif self.modes is None: - modes = ['area', 'width'] # default - else: - modes = self.modes + modes = self._get_modes() if isinstance(self.model, DiffusionModelBase): return [self._build_diffusion_callable(mode) for mode in modes] @@ -235,12 +238,7 @@ def get_model_names(self) -> list[str]: list[str] A list of model names. """ - if isinstance(self.modes, str): - modes = [self.modes] - elif self.modes is None: - modes = ['area', 'width'] - else: - modes = self.modes + modes = self._get_modes() if isinstance(self.model, DiffusionModelBase): return [f'{self.model.display_name} {mode}' for mode in modes] @@ -256,15 +254,7 @@ def get_parameter_names(self) -> list[str]: list[str] A list of parameter names. """ - if isinstance(self.modes, str): - modes = [self.modes] - elif self.modes is None: - modes = ['area', 'width'] - else: - modes = self.modes - - if len(modes) == 1: - return [self.parameter_name] + modes = self._get_modes() if isinstance(self.model, DiffusionModelBase): return [f'{self.parameter_name} {mode}' for mode in modes] @@ -282,7 +272,7 @@ def _build_diffusion_callable(self, mode: str) -> Callable: Parameters ---------- mode : str - The diffusion mode ("area", "width", or "auto"). + The diffusion mode ("area" or "width"). Returns ------- @@ -296,7 +286,7 @@ def _build_diffusion_callable(self, mode: str) -> Callable: """ model = self.model - if mode == 'area' or mode == 'auto': + if mode == 'area': return lambda x, **_: model.calculate_QISF(x) * model.scale.value if mode == 'width': @@ -304,6 +294,17 @@ def _build_diffusion_callable(self, mode: str) -> Callable: raise ValueError(f'Unknown diffusion mode: {mode}') + def _get_modes(self) -> list[str]: + """ + Get the modes to fit for diffusion models, defaulting to ["area", "width"] if not set. + + Returns + ------- + list[str] + The modes to fit for diffusion models. + """ + return ['area', 'width'] if self.modes is None else self.modes + # ------------------------------------------------------------------ # dunder methods # ------------------------------------------------------------------ diff --git a/tests/unit/easydynamics/analysis/test_fit_binding.py b/tests/unit/easydynamics/analysis/test_fit_binding.py new file mode 100644 index 00000000..a59e0f70 --- /dev/null +++ b/tests/unit/easydynamics/analysis/test_fit_binding.py @@ -0,0 +1,289 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +from unittest.mock import Mock + +import pytest + +from easydynamics.analysis.fit_binding import FitBinding +from easydynamics.sample_model.components.gaussian import Gaussian +from easydynamics.sample_model.diffusion_model.brownian_translational_diffusion import ( + BrownianTranslationalDiffusion, +) + + +class TestFitBinding: + @pytest.fixture + def fit_binding(self): + model = Gaussian() + return FitBinding(parameter_name='parameter1', model=model) + + @pytest.fixture + def diffusion_fit_binding(self): + model = BrownianTranslationalDiffusion() + return FitBinding(parameter_name='parameter3', model=model) + + def test_initialization(self, fit_binding): + # WHEN THEN EXPECT + assert isinstance(fit_binding, FitBinding) + assert fit_binding.parameter_name == 'parameter1' + assert isinstance(fit_binding.model, Gaussian) + assert fit_binding.modes is None + + @pytest.mark.parametrize( + 'parameter_name, model, modes, error_msg', + [ + # parameter_name errors + (123, Gaussian(), None, 'parameter_name must be a string'), + (None, Gaussian(), None, 'parameter_name must be a string'), + # model errors + ( + 'param', + 123, + None, + 'model must be a ModelComponent, ComponentCollection, or DiffusionModelBase', + ), + ( + 'param', + 'not_a_model', + None, + 'model must be a ModelComponent, ComponentCollection, or DiffusionModelBase', + ), + # modes type errors + ( + 'param', + Gaussian(), + 123, + 'modes must be a string, list of strings, or None', + ), + ( + 'param', + Gaussian(), + {'mode': 'area'}, + 'modes must be a string, list of strings, or None', + ), + # modes list content errors + ( + 'param', + Gaussian(), + ['area', 123], + 'All modes in the list must be strings', + ), + ('param', Gaussian(), [None], 'All modes in the list must be strings'), + ], + ) + def test_fitbinding_init_errors(self, parameter_name, model, modes, error_msg): + with pytest.raises(TypeError, match=error_msg): + FitBinding( + parameter_name=parameter_name, + model=model, + modes=modes, + ) + + # ------------------------------------------------------------------ + # Properties + # ------------------------------------------------------------------ + + def test_parameter_name_setter(self, fit_binding): + # WHEN + fit_binding.parameter_name = 'new_parameter' + + # THEN EXPECT + assert fit_binding.parameter_name == 'new_parameter' + + def test_parameter_name_setter_errors(self, fit_binding): + with pytest.raises(TypeError, match='parameter_name must be a string'): + fit_binding.parameter_name = 123 + + def test_model_setter(self, fit_binding): + # WHEN + model = BrownianTranslationalDiffusion() + + # THEN + fit_binding.model = model + + # EXPECT + assert fit_binding.model is model + + def test_model_setter_errors(self, fit_binding): + with pytest.raises( + TypeError, + match='model must be a ModelComponent, ComponentCollection, or DiffusionModelBase', + ): + fit_binding.model = 'not_a_model' + + def test_modes_setter(self, fit_binding): + # WHEN + fit_binding.modes = 'area' + + # THEN EXPECT + assert fit_binding.modes == ['area'] + + # WHEN + fit_binding.modes = ['area', 'width'] + + # THEN EXPECT + assert fit_binding.modes == ['area', 'width'] + + def test_modes_setter_errors(self, fit_binding): + with pytest.raises(TypeError, match='modes must be a string, list of strings, or None'): + fit_binding.modes = 123 + + with pytest.raises(TypeError, match='modes must be a string, list of strings, or None'): + fit_binding.modes = {'mode': 'area'} + + with pytest.raises(TypeError, match='All modes in the list must be strings'): + fit_binding.modes = ['area', 123] + + with pytest.raises(TypeError, match='All modes in the list must be strings'): + fit_binding.modes = [None] + + # ------------------------------------------------------------------ + # Other methods + # ------------------------------------------------------------------ + + def test_build_callables_component(self, fit_binding): + # WHEN + mock_model = Mock() + mock_model.evaluate.return_value = 1.0 + fit_binding._model = mock_model + + # THEN + callables = fit_binding.build_callables() + + # EXPECT + assert len(callables) == 1 + assert callable(callables[0]) + assert callables[0](0) == pytest.approx(1.0) + mock_model.evaluate.assert_called_once_with(0) + + def test_build_callables_diffusion(self, diffusion_fit_binding): + # WHEN + mock_model = Mock(spec=BrownianTranslationalDiffusion) + mock_model.calculate_QISF.return_value = 2.0 + mock_model.scale.value = 3.0 + mock_model.calculate_width.return_value = 0.5 + diffusion_fit_binding._model = mock_model + + # THEN + callables = diffusion_fit_binding.build_callables() + + # EXPECT + assert len(callables) == 2 + assert callable(callables[0]) + assert callable(callables[1]) + assert callables[0](0) == pytest.approx(6.0) # 2.0 * 3.0 + assert callables[1](0) == pytest.approx(0.5) + mock_model.calculate_QISF.assert_called_once_with(0) + mock_model.calculate_width.assert_called_once_with(0) + + def test_build_callables_diffusion_with_modes(self, diffusion_fit_binding): + # WHEN + diffusion_fit_binding.modes = 'area' + mock_model = Mock(spec=BrownianTranslationalDiffusion) + mock_model.calculate_QISF.return_value = 2.0 + mock_model.scale.value = 3.0 + diffusion_fit_binding._model = mock_model + + # THEN + callables = diffusion_fit_binding.build_callables() + + # EXPECT + assert len(callables) == 1 + assert callable(callables[0]) + assert callables[0](0) == pytest.approx(6.0) # 2.0 * 3.0 + mock_model.calculate_QISF.assert_called_once_with(0) + + def test_get_model_names(self, fit_binding): + # WHEN THEN + model_names = fit_binding.get_model_names() + + # EXPECT + assert model_names == ['Gaussian'] + + def test_get_model_names_diffusion(self, diffusion_fit_binding): + # WHEN THEN + model_names = diffusion_fit_binding.get_model_names() + + # EXPECT + assert model_names == [ + 'BrownianTranslationalDiffusion area', + 'BrownianTranslationalDiffusion width', + ] + + def test_get_parameter_names(self, fit_binding): + # WHEN THEN + parameter_names = fit_binding.get_parameter_names() + + # EXPECT + assert parameter_names == ['parameter1'] + + def test_get_parameter_names_diffusion(self, diffusion_fit_binding): + # WHEN THEN + parameter_names = diffusion_fit_binding.get_parameter_names() + + # EXPECT + assert parameter_names == ['parameter3 area', 'parameter3 width'] + + # ------------------------------------------------------------------ + # Private methods + # ------------------------------------------------------------------ + + def test_build_diffusion_callable(self, diffusion_fit_binding): + + # WHEN + mock_model = Mock() + mock_model.calculate_QISF.return_value = 2.0 + mock_model.scale.value = 3.0 + mock_model.calculate_width.return_value = 0.5 + diffusion_fit_binding._model = mock_model + + # THEN + area_callable = diffusion_fit_binding._build_diffusion_callable(mode='area') + width_callable = diffusion_fit_binding._build_diffusion_callable(mode='width') + + # EXPECT + assert area_callable(0) == pytest.approx(6.0) # 2.0 * 3.0 + mock_model.calculate_QISF.assert_called_once_with(0) + + assert width_callable(0) == pytest.approx(0.5) + mock_model.calculate_width.assert_called_once_with(0) + + # THEN + result_area = area_callable(0, unused_arg=123) + result_width = width_callable(0, unused_arg=123) + + # EXPECT + assert result_area == pytest.approx(6.0) # Should ignore unused_arg + assert result_width == pytest.approx(0.5) # Should ignore unused_arg + + def test_build_diffusion_callable_errors(self, diffusion_fit_binding): + with pytest.raises(ValueError, match='Unknown diffusion mode: invalid_mode'): + diffusion_fit_binding._build_diffusion_callable(mode='invalid_mode') + + def test_get_modes(self, diffusion_fit_binding): + # WHEN + modes = diffusion_fit_binding._get_modes() + + # EXPECT + assert modes == ['area', 'width'] + + # THEN + diffusion_fit_binding.modes = 'area' + modes = diffusion_fit_binding._get_modes() + # EXPECT + assert modes == ['area'] + + # ------------------------------------------------------------------ + # dunder methods + # ------------------------------------------------------------------ + def test_repr(self, fit_binding): + # WHEN + repr_str = repr(fit_binding) + + # THEN EXPECT + assert 'FitBinding' in repr_str + assert 'parameter_name=parameter1' in repr_str + assert 'model=Gaussian' in repr_str + assert 'modes=None' in repr_str From f1a15ce9354ccb6bfff7863e45ae1aa0721d13b8 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 5 May 2026 11:07:22 +0200 Subject: [PATCH 17/19] final tests --- .../analysis/parameter_analysis.py | 8 +- .../analysis/test_parameter_analysis.py | 91 +++++++++++++------ 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index 80a338f2..cb30b166 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -136,10 +136,10 @@ def fit(self) -> FitResults: names are found for the fit functions. """ - if self._parameters is None: + if self.parameters is None: raise ValueError('No parameters Dataset provided.') - if not self._bindings: + if not self.bindings: raise ValueError('No fit bindings provided.') xs = [] @@ -147,12 +147,12 @@ def fit(self) -> FitResults: ws = [] funcs, models = [], [] - for binding in self._bindings: + for binding in self.bindings: param_names = binding.get_parameter_names() callables = binding.build_callables() for pname, func in zip(param_names, callables, strict=True): - if pname not in self._parameters: + if pname not in self.parameters: raise ValueError( f"Parameter '{pname}' from binding '{binding.unique_name}' " f'not found in parameters Dataset.' diff --git a/tests/unit/easydynamics/analysis/test_parameter_analysis.py b/tests/unit/easydynamics/analysis/test_parameter_analysis.py index 2ac96627..00fb7d6a 100644 --- a/tests/unit/easydynamics/analysis/test_parameter_analysis.py +++ b/tests/unit/easydynamics/analysis/test_parameter_analysis.py @@ -49,6 +49,15 @@ def dataset(self): } ) + @pytest.fixture + def mock_model_dataset(self): + return sc.Dataset({ + 'Polynomial': sc.DataArray(data=sc.array(dims=['Q'], values=[1.1, 2.1], unit='meV')), + 'BrownianTranslationalDiffusion area': sc.DataArray( + data=sc.array(dims=['Q'], values=[6.1, 7.1], unit='meV') + ), + }) + @pytest.fixture def parameter_analysis(self, dataset): model = Polynomial(coefficients=[1.0, 0.5]) @@ -234,35 +243,59 @@ def test_plot_no_parameters_raises(self, parameter_analysis): ): parameter_analysis.plot() - # def test_plot_inconsistent_units_raises(self, parameter_analysis): - # # WHEN - - # # THEN EXPECT - # with ( - # patch( - # "easydynamics.analysis.parameter_analysis._in_notebook", - # return_value=True, - # ), - # pytest.raises( - # ValueError, - # match=r"Units are not consistent, and cannot be plotted together.", - # ), - # ): - # parameter_analysis.plot() - - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT - # TEST PLOT + @patch('easydynamics.analysis.parameter_analysis._in_notebook', return_value=True) + @patch('easydynamics.analysis.parameter_analysis.pp.plot') + def test_plot_calls_dependencies_correctly( + self, + mock_plot, + mock_notebook, + parameter_analysis, + mock_model_dataset, + ): + # WHEN + # Mock calculate_model_dataset + parameter_analysis.calculate_model_dataset = MagicMock(return_value=mock_model_dataset) + + # THEN + result = parameter_analysis.plot(names=['parameter1', 'parameter3 area']) + + # EXPECT + + # 1. Notebook check + mock_notebook.assert_called_once() + + # 2. Model dataset calculation + parameter_analysis.calculate_model_dataset.assert_called_once_with( + parameter_analysis.bindings + ) + + # 3. Plot called + mock_plot.assert_called_once() + + # Extract call args + args, kwargs = mock_plot.call_args + + # 4. Dataset passed correctly + dataset = args[0] + assert isinstance(dataset, sc.Dataset) + + # Data keys + assert 'parameter1' in dataset + assert 'parameter3 area' in dataset + + # Model keys (from bindings) + assert 'Polynomial' in dataset + assert 'BrownianTranslationalDiffusion area' in dataset + + # 5. Check some kwargs + assert kwargs['title'] == parameter_analysis.display_name + + # Ensure styling dictionaries exist + for key in ['linestyle', 'marker', 'color', 'markerfacecolor']: + assert key in kwargs + + # 6. Return value propagated + assert result == mock_plot.return_value @pytest.mark.parametrize( 'set_pars_none, bindings, expected_exception, match', From fbd3ed1a4dddb53759aa57ffdef92bce419753a0 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 5 May 2026 11:12:16 +0200 Subject: [PATCH 18/19] pixi update --- docs/docs/tutorials/tutorial0_basics.ipynb | 2 +- pixi.lock | 1730 ++++++++--------- .../test_fitting_with_diffusion_model.py | 6 +- 3 files changed, 818 insertions(+), 920 deletions(-) diff --git a/docs/docs/tutorials/tutorial0_basics.ipynb b/docs/docs/tutorials/tutorial0_basics.ipynb index 15f2aea6..a562cfb3 100644 --- a/docs/docs/tutorials/tutorial0_basics.ipynb +++ b/docs/docs/tutorials/tutorial0_basics.ipynb @@ -324,7 +324,7 @@ "metadata": {}, "outputs": [], "source": [ - "print(f'The reduced chi-squared value for Q_index=5 is: {fit_result_all_Q[5].reduced_chi}')\n", + "print(f'The reduced chi-squared value for Q_index=5 is: {fit_result_all_Q[5].reduced_chi2}')\n", "\n", "print(f'The minimizer engine is: {fit_result_all_Q[5].minimizer_engine}')" ] diff --git a/pixi.lock b/pixi.lock index 0d2efa1f..b462169a 100644 --- a/pixi.lock +++ b/pixi.lock @@ -19,17 +19,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda @@ -47,10 +47,10 @@ environments: - 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/linux-64/icu-78.3-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_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 @@ -63,10 +63,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda @@ -79,36 +79,36 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.0-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_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/linux-64/ncurses-6.6-hdb14827_0.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-25.8.2-he4ff34a_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.6.2-h35e630c_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-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/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda @@ -124,15 +124,15 @@ environments: - 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.14.4-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/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -155,13 +155,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.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-h41580af_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -169,18 +169,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/1af6666f34e3ced9a2dd2993743c1f70af7b52d5db4c4eba22c42a265eae/chardet-7.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/20/193faab46a68ea550587331a698c3dca8099f8901d10937c4443135c7ed9/chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ca/cc/b1ce2de93f097468d394a71821671f34de34d16d841476c11496edd226b1/copier-9.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.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 @@ -190,19 +190,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -218,26 +218,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -245,19 +245,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -271,7 +271,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/fe/ad0ecbe2393cb690a4b3100a8fea47ecfdb49f6e06f40cf2f626635adc0c/scipp-26.3.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -280,13 +280,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -303,17 +303,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314h3daef5d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda @@ -331,10 +331,10 @@ environments: - 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/osx-arm64/icu-78.3-hef89b57_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_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 @@ -347,10 +347,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda @@ -359,34 +359,34 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.3-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_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/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_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.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.0-py314h6c2aa35_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py314h6c2aa35_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_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/osx-arm64/ncurses-6.6-h1d4f5a5_0.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-25.8.2-h7039424_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.6.2-hd24854e_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-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/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda @@ -404,15 +404,15 @@ environments: - 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.14.4-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/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -435,13 +435,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.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-h4818236_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl @@ -449,18 +449,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/ac/f2661976d435f2e16ed31b2e61cbdf6afcd2289220cf5f35fc981bae828b/chardet-7.4.1-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/ca/cc/b1ce2de93f097468d394a71821671f34de34d16d841476c11496edd226b1/copier-9.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.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 @@ -470,19 +470,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -498,26 +498,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -525,19 +525,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.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/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -551,7 +551,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -560,13 +560,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -582,16 +582,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314he701e3d_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda @@ -609,10 +609,10 @@ environments: - 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/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_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 @@ -625,25 +625,25 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.0-py314h5a2d7ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py314h5a2d7ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda @@ -652,9 +652,9 @@ environments: - 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.6.2-hf411b9b_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda @@ -668,16 +668,16 @@ environments: - 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.14.4-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/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314h8f8f202_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py314h51f0985_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -704,7 +704,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.conda @@ -712,7 +712,7 @@ environments: - 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-h507cc87_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl @@ -720,17 +720,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/52/38714d4cb9d0a7d864aaf405ea7c26bcdb0fce7035a4fbc7a34c548afb2e/chardet-7.4.1-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/2c/cad8b5e3623a987f3c930b68e2bdd06cfc388cd91cd42ed05f1227701b73/chardet-7.4.3-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ca/cc/b1ce2de93f097468d394a71821671f34de34d16d841476c11496edd226b1/copier-9.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.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 @@ -740,19 +740,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -768,26 +768,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -795,19 +795,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.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/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -821,7 +821,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1f/28/3f8aa247d29d010547d52207395cb057ebd0a40b88f64bc1dbac9e17a729/scipp-26.3.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -830,13 +830,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -861,17 +861,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py312h90b7ffd_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda @@ -889,10 +889,10 @@ environments: - 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/linux-64/icu-78.3-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_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 @@ -905,10 +905,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda @@ -921,16 +921,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda @@ -938,20 +938,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.0-py312h4c3975b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py312h4c3975b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_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/linux-64/ncurses-6.6-hdb14827_0.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-25.8.2-he4ff34a_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.6.2-h35e630c_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-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/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda @@ -967,15 +967,15 @@ environments: - 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.12.13-hd8ed1ab_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-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -998,13 +998,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.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-h41580af_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -1012,18 +1012,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/ea/119e9b64e74762ec279f4c742c353e35602437f29ae3ddc2b0cb43071dba/chardet-7.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/59/a32a241d861cf180853a11c8e5a67641cb1b2af13c3a5ccce83ec07e2c9f/chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ca/cc/b1ce2de93f097468d394a71821671f34de34d16d841476c11496edd226b1/copier-9.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.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 @@ -1033,19 +1033,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -1061,26 +1061,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -1088,19 +1088,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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/c4/a8/3a61a721472959ab0ce865ef05d10b0d6bfe27ce8801c99f33d4fa996e65/pandas-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -1114,7 +1114,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1e/e7/cd78635d0ece7e4d3393f2c1d2ebabf6ff4bd615da142891b1d42ad58abf/scipp-26.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1123,13 +1123,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -1146,17 +1146,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py312h87c4bb7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312h0dfefe5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py312h1b4d9a2_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda @@ -1174,10 +1174,10 @@ environments: - 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/osx-arm64/icu-78.3-hef89b57_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_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 @@ -1190,10 +1190,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda @@ -1202,33 +1202,33 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.3-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_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/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_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.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.0-py312h2bbb03f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py312h2bbb03f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_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/osx-arm64/ncurses-6.6-h1d4f5a5_0.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-25.8.2-h7039424_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.6.2-hd24854e_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-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/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda @@ -1246,15 +1246,15 @@ environments: - 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.12.13-hd8ed1ab_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-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -1277,13 +1277,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.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-h4818236_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/47/7be41556bfbb6917069d6a6634bb7dd5e163ba445b783a90d40f5ac7e3a7/aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl @@ -1291,18 +1291,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/53/b1/320ee3b3d8b1b95f48d02a081f28e23caf9bd044ff11e6c1597ffe65fa2f/chardet-7.4.1-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/2f/4c5af01fd1a7506a1d5375403d68925eac70289229492db5aa68b58103d8/chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/ca/cc/b1ce2de93f097468d394a71821671f34de34d16d841476c11496edd226b1/copier-9.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.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 @@ -1312,19 +1312,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -1340,26 +1340,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -1367,19 +1367,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.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/35/d0/4831af68ce30cc2d03c697bea8450e3225a835ef497d0d70f31b8cdde965/pandas-3.0.2-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -1393,7 +1393,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/44/7b/537a61906eac58d94131273084d21d4eb219f5453f0ed30de3aca580a2b4/scipp-26.3.1-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1402,13 +1402,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -1424,16 +1424,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.3.0-py312h06d0912_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py312h06d0912_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py312hc6d9e41_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py312he06e257_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda @@ -1451,10 +1451,10 @@ environments: - 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/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_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 @@ -1467,24 +1467,24 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py312h05f76fc_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.0-py312he06e257_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py312he06e257_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda @@ -1493,9 +1493,9 @@ environments: - 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.6.2-hf411b9b_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda @@ -1509,16 +1509,16 @@ environments: - 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.12.13-hd8ed1ab_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-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py312h829343e_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py312h275cf98_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py312h05f76fc_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -1545,7 +1545,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.conda @@ -1553,7 +1553,7 @@ environments: - 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-h507cc87_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl @@ -1561,17 +1561,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl + - pypi: https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/4c/dc7359553bcb0ff0511ef84bf997ad6308bc1bd0ca268bbcebb2866cebf5/chardet-7.4.1-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/32/60/fca69c534602a7ced04280c952a246ad1edde2a6ca3a164f65d32ac41fe7/chardet-7.4.3-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ca/cc/b1ce2de93f097468d394a71821671f34de34d16d841476c11496edd226b1/copier-9.14.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.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 @@ -1581,19 +1581,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -1609,26 +1609,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -1636,19 +1636,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.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/7b/8b/721a9cff6fa6a91b162eb51019c6243b82b3226c71bb6c8ef4a9bd65cbc6/pandas-3.0.2-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -1662,7 +1662,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1a/1f/86b4d15221096cb5500bcd73bf350745749e3ba056cdd7a7f75f126f154e/scipp-26.3.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1671,13 +1671,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -1702,17 +1702,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda @@ -1730,10 +1730,10 @@ environments: - 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/linux-64/icu-78.3-h33c6efd_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_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 @@ -1746,10 +1746,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda @@ -1762,12 +1762,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda @@ -1778,20 +1778,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.0-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_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/linux-64/ncurses-6.6-hdb14827_0.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-25.8.2-he4ff34a_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.6.2-h35e630c_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-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/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda @@ -1807,15 +1807,15 @@ environments: - 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.14.4-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/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -1838,13 +1838,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.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-h41580af_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -1852,18 +1852,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/30/1af6666f34e3ced9a2dd2993743c1f70af7b52d5db4c4eba22c42a265eae/chardet-7.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/20/193faab46a68ea550587331a698c3dca8099f8901d10937c4443135c7ed9/chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/a9/fe/bce5ec796db178879c286332dbb285cadf9e94f1989df4647afa8c1867ae/copier-9.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.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 @@ -1873,19 +1873,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -1901,26 +1901,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -1928,19 +1928,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -1954,7 +1954,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/43/fe/ad0ecbe2393cb690a4b3100a8fea47ecfdb49f6e06f40cf2f626635adc0c/scipp-26.3.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -1963,13 +1963,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/0e/f083a76cb590e60dff3868779558eefefb8dfb7c9ed020babc7aa014ccbf/virtualenv-21.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -1986,17 +1986,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314h3daef5d_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py314h44086f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda @@ -2014,10 +2014,10 @@ environments: - 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/osx-arm64/icu-78.3-hef89b57_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_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 @@ -2030,10 +2030,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda @@ -2042,12 +2042,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.3-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_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/libev-4.33-h93a5062_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda @@ -2056,20 +2056,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.0-py314h6c2aa35_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py314h6c2aa35_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_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/osx-arm64/ncurses-6.6-h1d4f5a5_0.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-25.8.2-h7039424_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.6.2-hd24854e_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-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/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda @@ -2087,15 +2087,15 @@ environments: - 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.14.4-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/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -2118,13 +2118,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.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.6.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.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-h4818236_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl @@ -2132,18 +2132,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/ac/f2661976d435f2e16ed31b2e61cbdf6afcd2289220cf5f35fc981bae828b/chardet-7.4.1-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/a9/fe/bce5ec796db178879c286332dbb285cadf9e94f1989df4647afa8c1867ae/copier-9.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.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 @@ -2153,19 +2153,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -2181,26 +2181,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -2208,19 +2208,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.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/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -2234,7 +2234,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -2243,13 +2243,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/0e/f083a76cb590e60dff3868779558eefefb8dfb7c9ed020babc7aa014ccbf/virtualenv-21.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -2265,16 +2265,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314he701e3d_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-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-2026.2.25-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py314h5a2d7ad_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda @@ -2292,10 +2292,10 @@ environments: - 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/idna-3.11-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_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 @@ -2308,25 +2308,25 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_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_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.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.28.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.0-py314h5a2d7ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py314h5a2d7ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda @@ -2335,9 +2335,9 @@ environments: - 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.6.2-hf411b9b_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-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.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.6-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda @@ -2351,16 +2351,16 @@ environments: - 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.14.4-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/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py314h8f8f202_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py314h51f0985_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_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 @@ -2387,7 +2387,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-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.9.0-pyhd8ed1ab_0.conda @@ -2395,7 +2395,7 @@ environments: - 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-h507cc87_10.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl @@ -2403,17 +2403,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-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/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/8e/278ea79cf8ee0c395a5ad74625b3465c2fc234bb277f171dd59dd203820d/bumps-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/52/38714d4cb9d0a7d864aaf405ea7c26bcdb0fce7035a4fbc7a34c548afb2e/chardet-7.4.1-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/2c/cad8b5e3623a987f3c930b68e2bdd06cfc388cd91cd42ed05f1227701b73/chardet-7.4.3-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a9/fe/bce5ec796db178879c286332dbb285cadf9e94f1989df4647afa8c1867ae/copier-9.14.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.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 @@ -2423,19 +2423,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl @@ -2451,26 +2451,26 @@ environments: - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.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/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-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/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-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/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl @@ -2478,19 +2478,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.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/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -2504,7 +2504,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1f/28/3f8aa247d29d010547d52207395cb057ebd0a40b88f64bc1dbac9e17a729/scipp-26.3.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl @@ -2513,13 +2513,13 @@ environments: - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/0e/f083a76cb590e60dff3868779558eefefb8dfb7c9ed020babc7aa014ccbf/virtualenv-21.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl @@ -2903,73 +2903,72 @@ packages: - pkg:pypi/babel?source=compressed-mapping size: 7684321 timestamp: 1772555330347 -- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda - sha256: d77a24be15e283d83214121428290dbe55632a6e458378205b39c550afa008cf - md5: 5b8c55fed2e576dde4b0b33693a4fdb1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py312h90b7ffd_0.conda + sha256: e8c83696e6529ac1909a96690c58624bb376312fd0768409380cd9b05e248c9b + md5: 542da724e75cdeef19e29cca23935c25 depends: - python - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - python_abi 3.12.* *_cp312 - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.12.* *_cp312 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 237970 - timestamp: 1767045004512 -- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.3.0-py314h680f03e_0.conda + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 238360 + timestamp: 1777848717715 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.4.0-py314h680f03e_0.conda noarch: generic - sha256: c31ab719d256bc6f89926131e88ecd0f0c5d003fe8481852c6424f4ec6c7eb29 - md5: a2ac7763a9ac75055b68f325d3255265 + sha256: de1755a35258eb1b59f2288559bbf0b76da60bd2fa6cd6f768ead442f85bd666 + md5: b712198b257f378e9bd8cde277218296 depends: - python >=3.14 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: [] - size: 7514 - timestamp: 1767044983590 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.3.0-py312h44dc372_0.conda - sha256: aee745bfca32f7073d3298157bbb2273d6d83383cb266840cf0a7862b3cd8efc - md5: c2d5961bfd98504b930e704426d16572 + size: 7546 + timestamp: 1777848733980 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py312h87c4bb7_0.conda + sha256: 7dbd64d3f06622ef8286be6dfceeb8e6008450fb4e6d9309fbb908b12f3937ff + md5: 95a833465ec45ac1e8f2ed1aaba8ec37 depends: - python - - python 3.12.* *_cpython - __osx >=11.0 - zstd >=1.5.7,<1.6.0a0 - python_abi 3.12.* *_cp312 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 241051 - timestamp: 1767045000787 -- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.3.0-py312h06d0912_0.conda - sha256: c9c97cd644faa6c4fb38017c5ecfd082f56a3126af5925d246364fa4a22b2a74 - md5: 2db2b356f08f19ce4309a79a9ee6b9d8 + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 239305 + timestamp: 1777848727027 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py312h06d0912_0.conda + sha256: 71caf40c0fdeb11fafaac639e6e6f9120112aa105a7a5e9dfb5b4b06db9ca97a + md5: 77d0a2bdd46dd8d502bb27eb80353fcd depends: - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - python_abi 3.12.* *_cp312 - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.12.* *_cp312 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 236635 - timestamp: 1767045021157 -- pypi: https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 237107 + timestamp: 1777848740547 +- pypi: https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl name: backrefs - version: '6.2' - sha256: e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7 + version: '7.0' + sha256: ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12 requires_dist: - regex ; extra == 'extras' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl name: backrefs - version: '6.2' - sha256: c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90 + version: '7.0' + sha256: a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9 requires_dist: - regex ; extra == 'extras' - requires_python: '>=3.9' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda sha256: bf1e71c3c0a5b024e44ff928225a0874fc3c3356ec1a0b6fe719108e6d1288f6 md5: 5267bef8efea4127aacd1f4e1f149b6e @@ -3119,25 +3118,25 @@ packages: - pkg:pypi/brotli?source=hash-mapping size: 335782 timestamp: 1764018443683 -- pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl name: build - version: 1.4.2 - sha256: 7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88 + version: 1.5.0 + sha256: 13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f requires_dist: - packaging>=24.0 - 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' + - keyring ; extra == 'keyring' - 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 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl name: bumps - version: 1.0.3 - sha256: 4f503c814b9ddd2cda760b2e35aaa6285651434fc2e64ccac55b1a666bca17f6 + version: 1.0.4 + sha256: 78b8cfaf9fbcbf2fd77f6d4a2f8c906b0e03a794804ba6caf64d56d6f6cce4d4 requires_dist: - numpy - scipy @@ -3151,7 +3150,7 @@ packages: - plotly - mpld3 - msgpack - - graphlib-backport ; python_full_version < '3.9' + - uncertainties - build ; extra == 'dev' - pre-commit ; extra == 'dev' - pytest ; extra == 'dev' @@ -3161,7 +3160,7 @@ packages: - setuptools ; extra == 'dev' - sphinx ; extra == 'dev' - versioningit ; extra == 'dev' - requires_python: '>=3.9' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 md5: d2ffd7602c02f2b316fd921d39876885 @@ -3216,24 +3215,24 @@ packages: purls: [] size: 180327 timestamp: 1765215064054 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - sha256: 37950019c59b99585cee5d30dbc2cc9696ed4e11f5742606a4db1621ed8f94d6 - md5: f001e6e220355b7f87403a4d0e5bf1ca +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-h4c7d964_0.conda + sha256: 6f4ff81534c19e76acf52fcabf4a258088a932b8f1ac56e9a59e98f6051f8e46 + md5: 56fb2c6c73efc627b40c77d14caecfba depends: - __win license: ISC purls: [] - size: 147734 - timestamp: 1772006322223 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc - md5: 4492fd26db29495f0ba23f146cd5638d + size: 131388 + timestamp: 1776865633471 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-hbd8a1cb_0.conda + sha256: c9dbcc8039a52023660d6d1bbf87594a93dd69c6ac5a2a44323af2c92976728d + md5: e18ad67cf881dcadee8b8d9e2f8e5f73 depends: - __unix license: ISC purls: [] - size: 147413 - timestamp: 1772006283803 + size: 131039 + timestamp: 1776865545798 - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 noarch: python sha256: 561e6660f26c35d137ee150187d89767c988413c978e1b712d53f27ddf70ea17 @@ -3256,16 +3255,16 @@ packages: - pkg:pypi/cached-property?source=hash-mapping size: 11065 timestamp: 1615209567874 -- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda - sha256: a6b118fd1ed6099dc4fc03f9c492b88882a780fadaef4ed4f93dc70757713656 - md5: 765c4d97e877cdbbb88ff33152b86125 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda + sha256: 989db6e5957c4b44fa600c68c681ec2f36a55e48f7c7f1c073d5e91caa8cd878 + md5: 929471569c93acefb30282a22060dcd5 depends: - python >=3.10 license: ISC purls: - - pkg:pypi/certifi?source=hash-mapping - size: 151445 - timestamp: 1772001170301 + - pkg:pypi/certifi?source=compressed-mapping + size: 135656 + timestamp: 1776866680878 - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda sha256: 7dafe8173d5f94e46cf9cd597cc8ff476a8357fbbd4433a8b5697b2864845d9c md5: 648ee28dcd4e07a1940a17da62eccd40 @@ -3367,35 +3366,35 @@ packages: version: 3.5.0 sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/53/b1/320ee3b3d8b1b95f48d02a081f28e23caf9bd044ff11e6c1597ffe65fa2f/chardet-7.4.1-cp312-cp312-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/32/60/fca69c534602a7ced04280c952a246ad1edde2a6ca3a164f65d32ac41fe7/chardet-7.4.3-cp312-cp312-win_amd64.whl name: chardet - version: 7.4.1 - sha256: b726b0b2684d29cd08f602bb4266334386c58741ff34c9e2f6cdf97ad604e235 + version: 7.4.3 + sha256: 4b2799bd58e7245cfa8d4ab2e8ad1d76a5c3a5b1f32318eb6acca4c69a3e7101 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/5a/ea/119e9b64e74762ec279f4c742c353e35602437f29ae3ddc2b0cb43071dba/chardet-7.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/51/ac/b9d68ebddfe1b02c77af5bf81120e12b036b4432dc6af7a303d90e2bc38b/chardet-7.4.3-cp314-cp314-macosx_11_0_arm64.whl name: chardet - version: 7.4.1 - sha256: 277ce1174ea054415a3c2ad5f51aa089a96dda16999de56e4ac1bc366d0d535e + version: 7.4.3 + sha256: acc46d1b8b7d5783216afe15db56d1c179b9a40e5a1558bc13164c4fd20674c4 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/61/52/38714d4cb9d0a7d864aaf405ea7c26bcdb0fce7035a4fbc7a34c548afb2e/chardet-7.4.1-cp314-cp314-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/9c/2f/4c5af01fd1a7506a1d5375403d68925eac70289229492db5aa68b58103d8/chardet-7.4.3-cp312-cp312-macosx_11_0_arm64.whl name: chardet - version: 7.4.1 - sha256: 5d86402a506631af2fb36e3d1c72021477b228fb0dcdb44400b9b681f14b14c0 + version: 7.4.3 + sha256: 29af5999f654e8729d251f1724a62b538b1262d9292cccaefddf8a02aae1ef6a requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/6e/4c/dc7359553bcb0ff0511ef84bf997ad6308bc1bd0ca268bbcebb2866cebf5/chardet-7.4.1-cp312-cp312-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/b1/2c/cad8b5e3623a987f3c930b68e2bdd06cfc388cd91cd42ed05f1227701b73/chardet-7.4.3-cp314-cp314-win_amd64.whl name: chardet - version: 7.4.1 - sha256: fcaed03cefa53f62346091ef92da7a6f44bae6830a6f4c6b097a70cdc31b1199 + version: 7.4.3 + sha256: b95c934b9ad59e2ba8abb9be49df70d3ad1b0d95d864b9fdb7588d4fa8bd921c requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e3/30/1af6666f34e3ced9a2dd2993743c1f70af7b52d5db4c4eba22c42a265eae/chardet-7.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/c2/20/193faab46a68ea550587331a698c3dca8099f8901d10937c4443135c7ed9/chardet-7.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: chardet - version: 7.4.1 - sha256: 3d66d2949754ad924865a47e81857a0792dc8edc651094285116b6df2e218445 + version: 7.4.3 + sha256: 6e3bd9f936e04bae89c254262af08d9e5b98f805175ba1e29d454e6cba3107b7 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f1/ac/f2661976d435f2e16ed31b2e61cbdf6afcd2289220cf5f35fc981bae828b/chardet-7.4.1-cp314-cp314-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/e5/59/a32a241d861cf180853a11c8e5a67641cb1b2af13c3a5ccce83ec07e2c9f/chardet-7.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: chardet - version: 7.4.1 - sha256: be39708b300a80a9f78ef8f81018e2e9c6274a71c0823a4d6e493c72f7b3d2a2 + version: 7.4.3 + sha256: 9a4904dd5f071b7a7d7f50b4a67a86db3c902d243bf31708f1d5cde2f68239cb requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda sha256: 3f9483d62ce24ecd063f8a5a714448445dc8d9e201147c46699fc0033e824457 @@ -3408,10 +3407,10 @@ packages: - pkg:pypi/charset-normalizer?source=compressed-mapping size: 58872 timestamp: 1775127203018 -- pypi: https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl name: click - version: 8.3.2 - sha256: 1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d + version: 8.3.3 + sha256: a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 requires_dist: - colorama ; sys_platform == 'win32' requires_python: '>=3.10' @@ -3598,30 +3597,10 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/ca/cc/b1ce2de93f097468d394a71821671f34de34d16d841476c11496edd226b1/copier-9.14.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl name: copier - version: 9.14.1 - sha256: cec88ca48b653fc251660e7787bca893ede719d265f3e50281f4d54badb404b5 - requires_dist: - - colorama>=0.4.6 - - dunamai>=1.7.0 - - funcy>=1.17 - - jinja2-ansible-filters>=1.3.1 - - jinja2>=3.1.5 - - packaging>=23.0 - - pathspec>=0.9.0 - - platformdirs>=4.3.6 - - plumbum>=1.6.9 - - pydantic>=2.4.2 - - pygments>=2.7.1 - - pyyaml>=5.3.1 - - questionary>=1.8.1 - - typing-extensions>=4.0.0,<5.0.0 ; python_full_version < '3.11' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a9/fe/bce5ec796db178879c286332dbb285cadf9e94f1989df4647afa8c1867ae/copier-9.14.2-py3-none-any.whl - name: copier - version: 9.14.2 - sha256: f27e65944b33cf5ab62ca0da5bd76c450754dbd5269db567c501c29c6417f6a6 + version: 9.15.0 + sha256: 0f59c2ea36df42f3ded85c091c3f1e2c8d3814b537504f0abc8c2e508f7e013d requires_dist: - colorama>=0.4.6 - dunamai>=1.7.0 @@ -3934,36 +3913,55 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.12' -- pypi: https://files.pythonhosted.org/packages/64/f2/9d779717fd4ff4136d009a8023704f7eb37f2231fbfbe49eb9b430315bcc/easyscience-2.2.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl name: easyscience - version: 2.2.0 - sha256: 5a09221feff4fbf9cfad32fe0009a293e4fe3e245d89303495183d8e3b31ed30 + version: 2.3.1 + sha256: 51dd343ff4bcf7c36e8fada32ed3ca2bdc7e1226ae08965a2c11d9fd936e3e40 requires_dist: - asteval - bumps - dfo-ls + - ipykernel + - ipympl + - ipython + - ipywidgets + - jupyterlab - lmfit + - matplotlib - numpy + - pixi-kernel + - pooch - scipp - uncertainties - - build ; extra == 'build' - - hatchling<=1.21.0 ; extra == 'build' - - setuptools-git-versioning ; extra == 'build' - build ; extra == 'dev' - - codecov ; extra == 'dev' - - flake8 ; extra == 'dev' - - jupyterlab ; extra == 'dev' - - matplotlib ; extra == 'dev' + - copier ; extra == 'dev' + - docformatter ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - mike ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-autorefs ; extra == 'dev' + - mkdocs-jupyter ; extra == 'dev' + - mkdocs-markdownextradata-plugin ; extra == 'dev' + - mkdocs-material ; extra == 'dev' + - mkdocs-plugin-inline-svg ; extra == 'dev' + - mkdocstrings-python ; extra == 'dev' + - nbmake ; extra == 'dev' + - nbqa ; extra == 'dev' + - nbstripout ; extra == 'dev' + - pre-commit ; extra == 'dev' - pytest ; extra == 'dev' - pytest-cov ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' - ruff ; extra == 'dev' - - tox-gh-actions ; extra == 'dev' - - doc8 ; extra == 'docs' - - readme-renderer ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinx-gallery ; extra == 'docs' - - toml ; extra == 'docs' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' requires_python: '>=3.11' - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 @@ -3997,10 +3995,10 @@ packages: - pkg:pypi/executing?source=hash-mapping size: 30753 timestamp: 1756729456476 -- pypi: https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl name: filelock - version: 3.25.2 - sha256: ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70 + version: 3.29.0 + sha256: 96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258 requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: fonttools @@ -4278,10 +4276,10 @@ packages: requires_dist: - smmap>=3.0.1,<6 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/fd/6f/b842bfa6f21d6f87c57f9abf7194225e55279d96d869775e19e9f7236fc5/gitpython-3.1.49-py3-none-any.whl name: gitpython - version: 3.1.46 - sha256: 79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058 + version: 3.1.49 + sha256: 024b0422d7f84d15cd794844e029ffebd4c5d42a7eb9b936b458697ef550a02c requires_dist: - gitdb>=4.0.1,<5 - typing-extensions>=3.10.0.2 ; python_full_version < '3.10' @@ -4296,7 +4294,7 @@ packages: - pytest-mock ; extra == 'test' - pytest-sugar ; extra == 'test' - typing-extensions ; python_full_version < '3.11' and extra == 'test' - - sphinx>=7.1.2,<7.2 ; extra == 'doc' + - sphinx>=7.4.7,<8 ; extra == 'doc' - sphinx-rtd-theme ; extra == 'doc' - sphinx-autodoc-typehints ; extra == 'doc' requires_python: '>=3.7' @@ -4454,24 +4452,25 @@ packages: purls: [] size: 12361647 timestamp: 1773822915649 -- pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl name: identify - version: 2.6.18 - sha256: 8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737 + version: 2.6.19 + sha256: 20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a requires_dist: - ukkonen ; extra == 'license' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.11-pyhd8ed1ab_0.conda - sha256: ae89d0299ada2a3162c2614a9d26557a92aa6a77120ce142f8e0109bbf0342b0 - md5: 53abe63df7e10a6ba605dc5f9f961d36 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda + sha256: 9ab620e6f64bb67737bd7bc1ad6f480770124e304c6710617aba7fe60b089f48 + md5: fb7130c190f9b4ec91219840a05ba3ac depends: - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/idna?source=hash-mapping - size: 50721 - timestamp: 1760286526795 + - pkg:pypi/idna?source=compressed-mapping + size: 59038 + timestamp: 1776947141407 - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda sha256: 82ab2a0d91ca1e7e63ab6a4939356667ef683905dea631bc2121aa534d347b16 md5: 080594bf4493e6bae2607e65390c520a @@ -4621,50 +4620,54 @@ packages: - nbval>=0.11.0 ; extra == 'test' - pytest>=9.0.2 ; extra == 'test' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhccfa634_0.conda - sha256: a0d3e4c8e4d7b3801377a03de32951f68d77dd1bfe25082c7915f4e6b0aaa463 - md5: 3734e3b6618ea6e04ad08678d8ed7a45 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda + sha256: a0af49948a1842dfd15a0b0b2fd56c94ddbd07e07a6c8b4bc70d43015eafaff0 + md5: 73e9657cd19605740d21efb14d8d0cb9 depends: - - __win + - __unix - decorator >=5.1.0 - ipython_pygments_lexers >=1.0.0 - jedi >=0.18.2 - matplotlib-inline >=0.1.6 - prompt-toolkit >=3.0.41,<3.1.0 + - psutil >=7 - pygments >=2.14.0 - - python >=3.12 + - python >=3.11 - stack_data >=0.6.0 - traitlets >=5.13.0 - - colorama >=0.4.4 + - typing_extensions >=4.6 + - pexpect >4.6 - python license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/ipython?source=compressed-mapping - size: 648954 - timestamp: 1774610078420 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.12.0-pyhecfbec7_0.conda - sha256: 932044bd893f7adce6c9b384b96a72fd3804cc381e76789398c2fae900f21df7 - md5: b293210beb192c3024683bf6a998a0b8 + size: 651632 + timestamp: 1777038396606 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda + sha256: f252ec33597115ff21cbb31051f6f9be34ca36cbbbf3d266b597660d8d8edde9 + md5: 5631ab99e902463d9dd4221e5b4eab6d depends: - - __unix + - __win - decorator >=5.1.0 - ipython_pygments_lexers >=1.0.0 - jedi >=0.18.2 - matplotlib-inline >=0.1.6 - prompt-toolkit >=3.0.41,<3.1.0 + - psutil >=7 - pygments >=2.14.0 - - python >=3.12 + - python >=3.11 - stack_data >=0.6.0 - traitlets >=5.13.0 - - pexpect >4.6 + - typing_extensions >=4.6 + - colorama >=0.4.4 - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/ipython?source=hash-mapping - size: 649967 - timestamp: 1774609994657 + - pkg:pypi/ipython?source=compressed-mapping + size: 650593 + timestamp: 1777038425499 - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 md5: bd80ba060603cc228d9d81c257093119 @@ -4881,13 +4884,13 @@ packages: - pkg:pypi/jupyter-core?source=hash-mapping size: 65503 timestamp: 1760643864586 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.0-pyhe01879c_0.conda - sha256: e9964aaaf6d24a685cd5ce9d75731b643ed7f010fb979574a6580cd2f974c6cd - md5: 31e11c30bbee1682a55627f953c6725a +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda + sha256: c7edb5682c6316a95ad781dccb1b6589cd2ec0bf94f23c21152974eb0363b5d7 + md5: bf42ee94c750c0b2e7e998b79ac299ea depends: - jsonschema-with-format-nongpl >=4.18.0 - packaging - - python >=3.9 + - python >=3.10 - python-json-logger >=2.0.4 - pyyaml >=5.3 - referencing @@ -4898,12 +4901,12 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyter-events?source=hash-mapping - size: 24306 - timestamp: 1770937604863 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.17.0-pyhcf101f3_0.conda - sha256: 74c4e642be97c538dae1895f7052599dfd740d8bd251f727bce6453ce8d6cd9a - md5: d79a87dcfa726bcea8e61275feed6f83 + - pkg:pypi/jupyter-events?source=compressed-mapping + size: 24002 + timestamp: 1776861872237 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.0-pyhcf101f3_0.conda + sha256: 953b8528e088ad3b38764c043d8c168d28583593a6a8dd02a1e8c1e4c860d378 + md5: 148450224bdca4f51bf4fe66c6e57cd7 depends: - anyio >=3.1.0 - argon2-cffi >=21.1 @@ -4926,11 +4929,10 @@ packages: - websocket-client >=1.7 - python license: BSD-3-Clause - license_family: BSD purls: - - pkg:pypi/jupyter-server?source=hash-mapping - size: 347094 - timestamp: 1755870522134 + - pkg:pypi/jupyter-server?source=compressed-mapping + size: 359130 + timestamp: 1777905221568 - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda sha256: 5eda79ed9f53f590031d29346abd183051263227dd9ee667b5ca1133ce297654 md5: 7b8bace4943e0dc345fc45938826f2b8 @@ -4944,9 +4946,9 @@ packages: - pkg:pypi/jupyter-server-terminals?source=hash-mapping size: 22052 timestamp: 1768574057200 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.6-pyhd8ed1ab_0.conda - sha256: 436a70259a9b4e13ce8b15faa8b37342835954d77a0a74d21dd24547e0871088 - md5: bcbb401d6fa84e0cee34d4926b0e9e93 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda + sha256: b85befad5ba1f50c0cc042a2ffb26441d13ffc2f18572dc20d3541476da0c7b9 + md5: 2ffe77234070324e763a6eddabb5f467 depends: - async-lru >=1.0.0 - httpx >=0.25.0,<1 @@ -4966,9 +4968,9 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyterlab?source=hash-mapping - size: 8245973 - timestamp: 1773240966438 + - pkg:pypi/jupyterlab?source=compressed-mapping + size: 8861204 + timestamp: 1777483115382 - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl name: jupyterlab-widgets version: 3.0.16 @@ -5180,7 +5182,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/lark?source=compressed-mapping + - pkg:pypi/lark?source=hash-mapping size: 94312 timestamp: 1761596921009 - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl @@ -5305,16 +5307,16 @@ packages: purls: [] size: 290754 timestamp: 1764018009077 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.3-h55c6f16_0.conda - sha256: 34cc56c627b01928e49731bcfe92338e440ab6b5952feee8f1dd16570b8b8339 - md5: acbb3f547c4aae16b19e417db0c6e5ed +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda + sha256: 25a0d02148a39b665d9c2957676faf62a4d2a58494d53b201151199a197db4b0 + md5: 448a1af83a9205655ee1cf48d3875ca3 depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 570026 - timestamp: 1775565121045 + size: 569927 + timestamp: 1776816293111 - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 md5: c277e0a4d549b03ac1e9d6cbbe3d017b @@ -5358,45 +5360,45 @@ packages: purls: [] size: 107458 timestamp: 1702146414478 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda - sha256: e8c2b57f6aacabdf2f1b0924bd4831ce5071ba080baa4a9e8c0d720588b6794c - md5: 49f570f3bc4c874a06ea69b7225753af +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda + sha256: ea33c40977ea7a2c3658c522230058395bc2ee0d89d99f0711390b6a1ee80d12 + md5: a3b390520c563d78cc58974de95a03e5 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 constrains: - - expat 2.7.5.* + - expat 2.8.0.* license: MIT license_family: MIT purls: [] - size: 76624 - timestamp: 1774719175983 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.7.5-hf6b4638_0.conda - sha256: 06780dec91dd25770c8cf01e158e1062fbf7c576b1406427475ce69a8af75b7e - md5: a32123f93e168eaa4080d87b0fb5da8a + size: 77241 + timestamp: 1777846112704 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda + sha256: f4b1cafc59afaede8fa0a2d9cf376840f1c553001acd72f6ead18bbc8ac8c49c + md5: 65466e82c09e888ca7560c11a97d5450 depends: - __osx >=11.0 constrains: - - expat 2.7.5.* + - expat 2.8.0.* license: MIT license_family: MIT purls: [] - size: 68192 - timestamp: 1774719211725 -- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda - sha256: 6850c3a4d5dc215b86f58518cfb8752998533d6569b08da8df1da72e7c68e571 - md5: bfb43f52f13b7c56e7677aa7a8efdf0c + size: 68789 + timestamp: 1777846180142 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda + sha256: 2d81d647c1f01108803457cac999b947456f44dd0a3c2325395677feacaeca67 + md5: 264e350e035092b5135a2147c238aec4 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - expat 2.7.5.* + - expat 2.8.0.* license: MIT license_family: MIT purls: [] - size: 70609 - timestamp: 1774719377850 + size: 71094 + timestamp: 1777846223617 - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 md5: a360c33a5abe61c07959e449fa1453eb @@ -5464,42 +5466,42 @@ packages: purls: [] size: 603262 timestamp: 1771378117851 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb - md5: c7c83eecbb72d88b940c249af56c8b17 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d + md5: b88d90cad08e6bc8ad540cb310a761fb depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 constrains: - - xz 5.8.2.* + - xz 5.8.3.* license: 0BSD purls: [] - size: 113207 - timestamp: 1768752626120 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.2-h8088a28_0.conda - sha256: 7bfc7ffb2d6a9629357a70d4eadeadb6f88fa26ebc28f606b1c1e5e5ed99dc7e - md5: 009f0d956d7bfb00de86901d16e486c7 + size: 113478 + timestamp: 1775825492909 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + sha256: 34878d87275c298f1a732c6806349125cebbf340d24c6c23727268184bba051e + md5: b1fd823b5ae54fbec272cea0811bd8a9 depends: - __osx >=11.0 constrains: - - xz 5.8.2.* + - xz 5.8.3.* license: 0BSD purls: [] - size: 92242 - timestamp: 1768752982486 -- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda - sha256: f25bf293f550c8ed2e0c7145eb404324611cfccff37660869d97abf526eb957c - md5: ba0bfd4c3cf73f299ffe46ff0eaeb8e3 + size: 92472 + timestamp: 1775825802659 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda + sha256: d636d1a25234063642f9c531a7bb58d84c1c496411280a36ea000bd122f078f1 + md5: 8f83619ab1588b98dd99c90b0bfc5c6d depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - xz 5.8.2.* + - xz 5.8.3.* license: 0BSD purls: [] - size: 106169 - timestamp: 1768752763559 + size: 106486 + timestamp: 1775825663227 - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 md5: 2c21e66f50753a083cbe6b80f38268fa @@ -5607,18 +5609,6 @@ packages: purls: [] size: 276860 timestamp: 1772479407566 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda - sha256: d716847b7deca293d2e49ed1c8ab9e4b9e04b9d780aea49a97c26925b28a7993 - md5: fd893f6a3002a635b5e50ceb9dd2c0f4 - depends: - - __glibc >=2.17,<3.0.a0 - - icu >=78.2,<79.0a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - license: blessing - purls: [] - size: 951405 - timestamp: 1772818874251 - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda sha256: ec37c79f737933bbac965f5dc0f08ef2790247129a84bb3114fad4900adce401 md5: 810d83373448da85c3f673fbcb7ad3a3 @@ -5631,17 +5621,6 @@ packages: purls: [] size: 958864 timestamp: 1775753750179 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.52.0-h1ae2325_0.conda - sha256: beb0fd5594d6d7c7cd42c992b6bb4d66cbb39d6c94a8234f15956da99a04306c - md5: f6233a3fddc35a2ec9f617f79d6f3d71 - depends: - - __osx >=11.0 - - icu >=78.2,<79.0a0 - - libzlib >=1.3.1,<2.0a0 - license: blessing - purls: [] - size: 918420 - timestamp: 1772819478684 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.0-h1b79a29_0.conda sha256: 1a9d1e3e18dbb0b87cff3b40c3e42703730d7ac7ee9b9322c2682196a81ba0c3 md5: 8423c008105df35485e184066cad4566 @@ -5652,17 +5631,6 @@ packages: purls: [] size: 920039 timestamp: 1775754485962 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda - sha256: 5fccf1e4e4062f8b9a554abf4f9735a98e70f82e2865d0bfdb47b9de94887583 - md5: 8830689d537fda55f990620680934bb1 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: blessing - purls: [] - size: 1297302 - timestamp: 1772818899033 - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.0-hf5d6505_0.conda sha256: 7a6256ea136936df4c4f3b227ba1e273b7d61152f9811b52157af497f07640b0 md5: 4152b5a8d2513fd7ae9fb9f221a5595d @@ -5956,10 +5924,10 @@ packages: - pkg:pypi/markupsafe?source=hash-mapping size: 30022 timestamp: 1772445159549 -- pypi: https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl name: matplotlib - version: 3.10.8 - sha256: dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf + version: 3.10.9 + sha256: d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -5972,13 +5940,13 @@ packages: - 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-scm>=7,<10 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: matplotlib - version: 3.10.8 - sha256: 3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04 + version: 3.10.9 + sha256: 34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -5991,13 +5959,13 @@ packages: - 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-scm>=7,<10 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: matplotlib - version: 3.10.8 - sha256: 32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b + version: 3.10.9 + sha256: ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6010,13 +5978,13 @@ packages: - 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-scm>=7,<10 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl name: matplotlib - version: 3.10.8 - sha256: b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58 + version: 3.10.9 + sha256: 97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6029,13 +5997,13 @@ packages: - 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-scm>=7,<10 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl name: matplotlib - version: 3.10.8 - sha256: 83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11 + version: 3.10.9 + sha256: 336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6048,13 +6016,13 @@ packages: - 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-scm>=7,<10 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl name: matplotlib - version: 3.10.8 - sha256: 2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008 + version: 3.10.9 + sha256: 41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -6067,7 +6035,7 @@ packages: - 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-scm>=7,<10 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda @@ -6106,13 +6074,13 @@ packages: version: 1.3.4 sha256: 70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/48/f7/10f5e101db25741b91e4f4792c5d97b4fa834ead5cf509ae91097d939424/mike-2.1.4-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl name: mike - version: 2.1.4 - sha256: 39933e992e155dd70f2297e749a0ed78d8fd7942bc33a3666195d177758a280e + version: 2.2.0 + sha256: e1f4981c1152eec7c2490a3401142292cc47d686194188416db2648fdfe1d040 requires_dist: - jinja2>=2.7 - - mkdocs>=1.0 + - mkdocs~=1.0 - pyparsing>=3.0 - pyyaml>=5.1 - pyyaml-env-tag @@ -6127,19 +6095,18 @@ packages: - flake8-quotes ; extra == 'test' - flake8>=3.0 ; extra == 'test' - shtab ; extra == 'test' -- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.0-pyhcf101f3_0.conda - sha256: d3fb4beb5e0a52b6cc33852c558e077e1bfe44df1159eb98332d69a264b14bae - md5: b11e360fc4de2b0035fc8aaa74f17fd6 +- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + sha256: b52dc6c78fbbe7a3008535cb8bfd87d70d8053e9250bbe16e387470a9df07070 + md5: b97e84d1553b4a1c765b87fff83453ad depends: - python >=3.10 - typing_extensions - python license: BSD-3-Clause - license_family: BSD purls: - - pkg:pypi/mistune?source=hash-mapping - size: 74250 - timestamp: 1766504456031 + - pkg:pypi/mistune?source=compressed-mapping + size: 74567 + timestamp: 1777824616382 - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl name: mkdocs version: 1.6.1 @@ -6195,10 +6162,10 @@ packages: - platformdirs>=2.2.0 - pyyaml>=5.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl name: mkdocs-jupyter - version: 0.26.1 - sha256: 527242c2c8f1d30970764bbab752de41243e5703f458d8bc05336ec53828192e + version: 0.26.3 + sha256: cd6644fb578131157194d750fd4d10fc2fd8f1e84e00036ee62df3b5b4b84c82 requires_dist: - ipykernel>6.0.0,<8 - jupytext>1.13.8,<2 @@ -6251,10 +6218,10 @@ packages: requires_dist: - mkdocs requires_python: '>=3.5' -- pypi: https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl name: mkdocstrings - version: 1.0.3 - sha256: 0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046 + version: 1.0.4 + sha256: 63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b requires_dist: - jinja2>=3.1 - markdown>=3.6 @@ -6326,9 +6293,9 @@ packages: version: 1.1.2 sha256: 1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.0-py312h4c3975b_0.conda - sha256: d651bb4b32f54480f4d161a0051da65db57855e0698ae60b5537c53660f6e46c - md5: d2c0302fd76aa32563ed6d5fb3fa6677 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py312h4c3975b_0.conda + sha256: 25eb262c378a922eeed85c941ab7de2687ea842daed80521b861b7472b5a7f9a + md5: 5e07dc45b4458c19fdc085bd6c1aa51f depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -6337,12 +6304,12 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/msgspec?source=compressed-mapping - size: 218811 - timestamp: 1775696215247 -- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.0-py314h5bd0f2a_0.conda - sha256: ac0883f927d25037bfbea24051216ece1e033b782c84492982d797f85fe741c6 - md5: da5026fdb331d3620516df6d0deeaf22 + - pkg:pypi/msgspec?source=hash-mapping + size: 218330 + timestamp: 1776337395109 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py314h5bd0f2a_0.conda + sha256: 52565ceea81e801c59dcaeaf5a9c77fba2fade445e67e0864fda50d4b944e15b + md5: 4a8ea416a56e58f012e445f7af2bbcc8 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -6352,11 +6319,11 @@ packages: license_family: BSD purls: - pkg:pypi/msgspec?source=hash-mapping - size: 219481 - timestamp: 1775696242910 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.0-py312h2bbb03f_0.conda - sha256: 104ff84417d7becfac3a8368126ef17ee928fc013f903f0c448fca4be52b9845 - md5: 4e534755318fd2da7d4cdbfbac569bb4 + size: 220990 + timestamp: 1776337508167 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py312h2bbb03f_0.conda + sha256: 50e284832520f08ef1e37e0ca20459f5df2c048f59dfba1f2e3ee0ccfe7be317 + md5: ae340bdc5bdf5abd3183c5962517cbde depends: - __osx >=11.0 - python >=3.12,<3.13.0a0 @@ -6365,12 +6332,12 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/msgspec?source=compressed-mapping - size: 212340 - timestamp: 1775697034631 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.0-py314h6c2aa35_0.conda - sha256: 3957e5eec5815cf81e8166e55f442305843c9cfd65dd2b422f3d612514928e71 - md5: 971a763ade1f152bab9dee1ab004b019 + - pkg:pypi/msgspec?source=hash-mapping + size: 212357 + timestamp: 1776338798628 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py314h6c2aa35_0.conda + sha256: 24a9105921e94fa526ffde1e956fa550c48ddb9ce4b0cf19ae22e79ed267261e + md5: 26fce586b13842a0f9f9a3aabae3e943 depends: - __osx >=11.0 - python >=3.14,<3.15.0a0 @@ -6380,11 +6347,11 @@ packages: license_family: BSD purls: - pkg:pypi/msgspec?source=hash-mapping - size: 215942 - timestamp: 1775697332597 -- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.0-py312he06e257_0.conda - sha256: f05694bcfead5fd12a7a9418fcb83eb981e28e59f60837901c665dcbde6e736a - md5: e31166527a61528c710df095d202997f + size: 216965 + timestamp: 1776338889692 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py312he06e257_0.conda + sha256: 003de3343b481937b5eb500ecdbfc882e87cea608be3741dc1fb13d22f8ed95e + md5: 1f32f4f6aa595377a7e651e67ba53d30 depends: - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 @@ -6394,12 +6361,12 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/msgspec?source=compressed-mapping - size: 199507 - timestamp: 1775696599867 -- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.0-py314h5a2d7ad_0.conda - sha256: 15482fa258347f1cefc99f6bd5ce151062214873bc2329f60928b36092498632 - md5: df988e54419bb4d8db78bf2fbba836c8 + - pkg:pypi/msgspec?source=hash-mapping + size: 199413 + timestamp: 1776337631789 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py314h5a2d7ad_0.conda + sha256: 6a076225fa315d29c5d556e3912a6319aea60b4f458c23f23f5ce66495cb9414 + md5: a4b20f401c93cf8651093fcc8380e3c9 depends: - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 @@ -6410,8 +6377,8 @@ packages: license_family: BSD purls: - pkg:pypi/msgspec?source=hash-mapping - size: 202257 - timestamp: 1775696433908 + size: 201836 + timestamp: 1776337750218 - pypi: https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl name: multidict version: 6.7.1 @@ -6454,10 +6421,10 @@ packages: requires_dist: - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/37/72/e61e3091e0e00fae9d3a8ef85ece9d2cd4b5966058e1f2901ce42679eebf/narwhals-2.19.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl name: narwhals - version: 2.19.0 - sha256: 1f8dfa4a33a6dbff878c3e9be4c3b455dfcaf2a9322f1357db00e4e92e95b84b + version: 2.20.0 + sha256: 16e750ea5507d4ba6e8d03455b5f93a535e0405976561baea235bca5dc9f475d requires_dist: - cudf-cu12>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' @@ -6573,25 +6540,25 @@ packages: requires_dist: - nbformat requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 - md5: 47e340acb35de30501a76c7c799c41d7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 + md5: fc21868a1a5aacc937e7a18747acb8a5 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: X11 AND BSD-3-Clause purls: [] - size: 891641 - timestamp: 1738195959188 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - sha256: 2827ada40e8d9ca69a153a45f7fd14f32b2ead7045d3bbb5d10964898fe65733 - md5: 068d497125e4bf8a66bf707254fff5ae + size: 918956 + timestamp: 1777422145199 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + sha256: 4ea6c620b87bd1d42bb2ccc2c87cd2483fa2d7f9e905b14c223f11ff3f4c455d + md5: 343d10ed5b44030a2f67193905aea159 depends: - __osx >=11.0 license: X11 AND BSD-3-Clause purls: [] - size: 797030 - timestamp: 1738196177597 + size: 805509 + timestamp: 1777423252320 - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 md5: 598fd7d4d0de2455fb74f56063969a97 @@ -6755,18 +6722,18 @@ packages: - pkg:pypi/overrides?source=hash-mapping size: 30139 timestamp: 1734587755455 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 - md5: b76541e68fea4d511b1ac46a28dcd2c6 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 + md5: 4c06a92e74452cfa53623a81592e8934 depends: - python >=3.8 - python license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/packaging?source=hash-mapping - size: 72010 - timestamp: 1769093650580 + - pkg:pypi/packaging?source=compressed-mapping + size: 91574 + timestamp: 1777103621679 - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl name: paginate version: 0.5.7 @@ -7326,28 +7293,26 @@ packages: - pkg:pypi/pandocfilters?source=hash-mapping size: 11627 timestamp: 1631603397334 -- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.6-pyhcf101f3_0.conda - sha256: 42b2d77ccea60752f3aa929a6413a7835aaacdbbde679f2f5870a744fa836b94 - md5: 97c1ce2fffa1209e7afb432810ec6e12 +- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + sha256: 611882f7944b467281c46644ffde6c5145d1a7730388bcde26e7e86819b0998e + md5: 39894c952938276405a1bd30e4ce2caf depends: - python >=3.10 - python license: MIT license_family: MIT purls: - - pkg:pypi/parso?source=hash-mapping - size: 82287 - timestamp: 1770676243987 -- pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl + - pkg:pypi/parso?source=compressed-mapping + size: 82472 + timestamp: 1777722955579 +- pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl name: pathspec - version: 1.0.4 - sha256: fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723 + version: 1.1.1 + sha256: a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189 requires_dist: - hyperscan>=0.7 ; extra == 'hyperscan' - typing-extensions>=4 ; extra == 'optional' - google-re2>=1.1 ; extra == 're2' - - pytest>=9 ; extra == 'tests' - - typing-extensions>=4.15 ; extra == 'tests' requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a @@ -7581,20 +7546,21 @@ packages: - pkg:pypi/platformdirs?source=compressed-mapping size: 25862 timestamp: 1775741140609 -- pypi: https://files.pythonhosted.org/packages/31/8b/9e8baf7dacac8d0c174925c38ff43c6d94bc9abb35503f67762caccb6869/plopp-26.3.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl name: plopp - version: 26.3.1 - sha256: 56531f2f71fa4f7f33c172312d2423d969deb9b9dd29b2524ad3ed7e33d220eb + version: 26.4.2 + sha256: 5cab99bb0905ce08a1d1d7d82f0f64cee7d594269ec1bd01a8a361bd14ab7bff requires_dist: - lazy-loader>=0.4 - matplotlib>=3.8 - - scipp>=25.5.0 ; extra == 'scipp' - - scipp>=25.5.0 ; extra == 'all' + - scipp>=25.8.0 ; extra == 'scipp' + - plopp[scipp] ; extra == 'all' - ipympl>0.8.4 ; extra == 'all' - pythreejs>=2.4.1 ; extra == 'all' - mpltoolbox>=24.6.0 ; extra == 'all' - ipywidgets>=8.1.0 ; extra == 'all' - graphviz>=0.20.3 ; extra == 'all' + - plopp[scipp] ; extra == 'test' - graphviz>=0.20.3 ; extra == 'test' - h5py>=3.12 ; extra == 'test' - ipympl>=0.8.4 ; extra == 'test' @@ -7607,51 +7573,10 @@ packages: - pyarrow>=13.0.0 ; extra == 'test' - pytest>=8.0 ; extra == 'test' - pythreejs>=2.4.1 ; extra == 'test' - - scipp>=25.5.0 ; extra == 'test' - scipy>=1.10.0 ; extra == 'test' - xarray>=2024.5.0 ; extra == 'test' - anywidget>=0.9.0 ; extra == 'test' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl - name: plotly - version: 6.6.0 - sha256: 8d6daf0f87412e0c0bfe72e809d615217ab57cc715899a1e5145135a7800d1d0 - requires_dist: - - narwhals>=1.15.1 - - packaging - - numpy ; extra == 'express' - - kaleido>=1.1.0 ; extra == 'kaleido' - - pytest ; extra == 'dev-core' - - requests ; extra == 'dev-core' - - ruff==0.11.12 ; extra == 'dev-core' - - plotly[dev-core] ; extra == 'dev-build' - - build ; extra == 'dev-build' - - jupyter ; extra == 'dev-build' - - plotly[dev-build] ; extra == 'dev-optional' - - plotly[kaleido] ; extra == 'dev-optional' - - anywidget ; extra == 'dev-optional' - - colorcet ; extra == 'dev-optional' - - fiona<=1.9.6 ; python_full_version < '3.9' and extra == 'dev-optional' - - geopandas ; extra == 'dev-optional' - - inflect ; extra == 'dev-optional' - - numpy ; extra == 'dev-optional' - - orjson ; extra == 'dev-optional' - - pandas ; extra == 'dev-optional' - - pdfrw ; extra == 'dev-optional' - - pillow ; extra == 'dev-optional' - - plotly-geo ; extra == 'dev-optional' - - polars[timezone] ; extra == 'dev-optional' - - pyarrow ; extra == 'dev-optional' - - pyshp ; extra == 'dev-optional' - - pytz ; extra == 'dev-optional' - - scikit-image ; extra == 'dev-optional' - - scipy ; extra == 'dev-optional' - - shapely ; extra == 'dev-optional' - - statsmodels ; extra == 'dev-optional' - - vaex ; python_full_version < '3.10' and extra == 'dev-optional' - - xarray ; extra == 'dev-optional' - - plotly[dev-optional] ; extra == 'dev' - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl name: plotly version: 6.7.0 @@ -7757,10 +7682,10 @@ packages: - pytest-httpserver ; extra == 'test' - pytest-localftpserver ; extra == 'test' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl name: pre-commit - version: 4.5.1 - sha256: 3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77 + version: 4.6.0 + sha256: e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b requires_dist: - cfgv>=2.0.0 - identify>=1.0.0 @@ -7952,57 +7877,57 @@ packages: - pkg:pypi/pycparser?source=hash-mapping size: 110100 timestamp: 1733195786147 -- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl name: pydantic - version: 2.12.5 - sha256: e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d + version: 2.13.3 + sha256: 6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927 requires_dist: - annotated-types>=0.6.0 - - pydantic-core==2.41.5 + - pydantic-core==2.46.3 - typing-extensions>=4.14.1 - typing-inspection>=0.4.2 - email-validator>=2.0.0 ; extra == 'email' - tzdata ; python_full_version >= '3.9' and sys_platform == 'win32' and extra == 'timezone' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl name: pydantic-core - version: 2.41.5 - sha256: eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c + version: 2.46.3 + sha256: 8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl name: pydantic-core - version: 2.41.5 - sha256: 8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf + version: 2.46.3 + sha256: ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018 requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl name: pydantic-core - version: 2.41.5 - sha256: 22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375 + version: 2.46.3 + sha256: c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1 requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: pydantic-core - version: 2.41.5 - sha256: 1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14 + version: 2.46.3 + sha256: fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395 requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl name: pydantic-core - version: 2.41.5 - sha256: 1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815 + version: 2.46.3 + sha256: af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089 requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: pydantic-core - version: 2.41.5 - sha256: 070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0 + version: 2.46.3 + sha256: ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f requires_dist: - typing-extensions>=4.14.1 requires_python: '>=3.9' @@ -8405,17 +8330,18 @@ packages: purls: [] size: 49806 timestamp: 1775614307464 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-2.0.7-pyhd8ed1ab_0.conda - sha256: 4790787fe1f4e8da616edca4acf6a4f8ed4e7c6967aa31b920208fc8f95efcca - md5: a61bf9ec79426938ff785eb69dbb1960 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + sha256: 1c55116c22512cef7b01d55ae49697707f2c1fd829407183c19817e2d300fd8d + md5: 1cd2f3e885162ee1366312bd1b1677fd depends: - - python >=3.6 + - python >=3.10 + - typing_extensions license: BSD-2-Clause license_family: BSD purls: - - pkg:pypi/python-json-logger?source=hash-mapping - size: 13383 - timestamp: 1677079727691 + - pkg:pypi/python-json-logger?source=compressed-mapping + size: 18969 + timestamp: 1777318679482 - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl name: python-socketio version: 5.16.1 @@ -8430,17 +8356,17 @@ packages: - sphinx ; extra == 'docs' - furo ; extra == 'docs' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.1-pyhd8ed1ab_0.conda - sha256: b5494ef54bc2394c6c4766ceeafac22507c4fc60de6cbfda45524fc2fcc3c9fc - md5: d8d30923ccee7525704599efd722aa16 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda + sha256: e943f9c15a6bdba2e1b9f423ab913b3f6b02197b0ef9f8e6b7464d78b59965b9 + md5: f6ad7450fc21e00ecc23812baed6d2e4 depends: - python >=3.10 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/tzdata?source=compressed-mapping - size: 147315 - timestamp: 1775223532978 + size: 146639 + timestamp: 1777068997932 - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda build_number: 8 sha256: 80677180dd3c22deb7426ca89d6203f1c7f1f256f2d5a94dc210f6e758229809 @@ -8558,7 +8484,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/pyyaml?source=compressed-mapping + - pkg:pypi/pyyaml?source=hash-mapping size: 202391 timestamp: 1770223462836 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda @@ -8737,9 +8663,9 @@ packages: - pkg:pypi/referencing?source=hash-mapping size: 51788 timestamp: 1760379115194 -- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda - sha256: c0249bc4bf4c0e8e06d0e7b4d117a5d593cc4ab2144d5006d6d47c83cb0af18e - md5: 10afbb4dbf06ff959ad25a92ccee6e59 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + sha256: 7f2c24dd3bd3c104a1d2c9a10ead5ed6758b0976b74f972cfe9c19884ccc4241 + md5: 9659f587a8ceacc21864260acd02fc67 depends: - python >=3.10 - certifi >=2023.5.7 @@ -8748,26 +8674,26 @@ packages: - urllib3 >=1.26,<3 - python constrains: - - chardet >=3.0.2,<6 + - chardet >=3.0.2,<8 license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/requests?source=compressed-mapping - size: 63712 - timestamp: 1774894783063 -- conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.26.0-pyhe01879c_0.conda - sha256: 619962bf637f5cadf967adcec2c5ad1d408418b56830a701aec19e876e5197d0 - md5: bec7ce42bd4cc803e21c43e9b7fb8860 + size: 63728 + timestamp: 1777030058920 +- conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda + sha256: 3b45efeae771f1a20307b36ecdb3a8911a89c05382836b50c62b0a99d8d3dfd8 + md5: da94ff04d97ec5efc42cbe5da3c43a84 depends: - - python >=3.10 + - python >=3.11 - typing_extensions >=4.0,<5.0 - python license: BSD-2-Clause license_family: BSD purls: - pkg:pypi/returns?source=hash-mapping - size: 100610 - timestamp: 1753812221549 + size: 100559 + timestamp: 1776176903101 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 md5: 36de09a8d3e5d5e6f4ee63af49e59706 @@ -8898,35 +8824,20 @@ packages: - pkg:pypi/rpds-py?source=hash-mapping size: 235780 timestamp: 1764543046065 -- pypi: https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl - name: ruff - version: 0.15.9 - sha256: 45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl name: ruff - version: 0.15.9 - sha256: eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8 + version: 0.15.12 + sha256: c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl name: ruff - version: 0.15.9 - sha256: 2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6 + version: 0.15.12 + sha256: fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ruff - version: 0.15.10 - sha256: 28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: ruff - version: 0.15.10 - sha256: 51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl - name: ruff - version: 0.15.10 - sha256: 93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1 + version: 0.15.12 + sha256: 83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0 requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/0f/0e/0eb94e64f5badef67f11fe1e448dde2a44f00940d8949f4adf71d560552e/scipp-26.3.1-cp314-cp314-macosx_14_0_arm64.whl name: scipp @@ -9624,7 +9535,7 @@ packages: license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/tornado?source=compressed-mapping + - pkg:pypi/tornado?source=hash-mapping size: 912476 timestamp: 1774358032579 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py312h2bbb03f_0.conda @@ -9696,10 +9607,10 @@ packages: - pkg:pypi/traitlets?source=hash-mapping size: 110051 timestamp: 1733367480074 -- pypi: https://files.pythonhosted.org/packages/bb/4a/2e5583e544bc437d5e8e54b47db87430df9031b29b48d17f26d129fa60c0/trove_classifiers-2026.1.14.14-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl name: trove-classifiers - version: 2026.1.14.14 - sha256: 1f9553927f18d0513d8e5ff80ab8980b8202ce37ecae0e3274ed2ef11880e74d + version: 2026.4.28.13 + sha256: 8f4b1eb4e16296b57d612965444f87a83861cc989a0451ac97fe4265ddef03b8 - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c md5: edd329d7d3a4ab45dcf905899a7a6115 @@ -9865,30 +9776,17 @@ packages: - mypy ; extra == 'test' - pretend ; extra == 'test' - pytest ; extra == 'test' -- pypi: https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl - name: virtualenv - version: 21.2.0 - sha256: 1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f - requires_dist: - - distlib>=0.3.7,<1 - - filelock>=3.24.2,<4 ; python_full_version >= '3.10' - - filelock>=3.16.1,<=3.19.1 ; python_full_version < '3.10' - - importlib-metadata>=6.6 ; python_full_version < '3.8' - - platformdirs>=3.9.1,<5 - - python-discovery>=1 - - typing-extensions>=4.13.2 ; python_full_version < '3.11' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/20/0e/f083a76cb590e60dff3868779558eefefb8dfb7c9ed020babc7aa014ccbf/virtualenv-21.2.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl name: virtualenv - version: 21.2.1 - sha256: bd16b49c53562b28cf1a3ad2f36edb805ad71301dee70ddc449e5c88a9f919a2 + version: 21.3.1 + sha256: d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35 requires_dist: - distlib>=0.3.7,<1 - filelock>=3.24.2,<4 ; python_full_version >= '3.10' - filelock>=3.16.1,<=3.19.1 ; python_full_version < '3.10' - importlib-metadata>=6.6 ; python_full_version < '3.8' - platformdirs>=3.9.1,<5 - - python-discovery>=1 + - python-discovery>=1.2.2 - typing-extensions>=4.13.2 ; python_full_version < '3.11' requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl @@ -9919,17 +9817,17 @@ packages: requires_dist: - pyyaml>=3.10 ; extra == 'watchmedo' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.6.0-pyhd8ed1ab_0.conda - sha256: e298b508b2473c4227206800dfb14c39e4b14fd79d4636132e9e1e4244cdf4aa - md5: c3197f8c0d5b955c904616b716aca093 +- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.7.0-pyhd8ed1ab_0.conda + sha256: 1ee2d8384972ecbf8630ce8a3ea9d16858358ad3e8566675295e66996d5352da + md5: eb9538b8e55069434a18547f43b96059 depends: - python >=3.10 license: MIT license_family: MIT purls: - - pkg:pypi/wcwidth?source=hash-mapping - size: 71550 - timestamp: 1770634638503 + - pkg:pypi/wcwidth?source=compressed-mapping + size: 82917 + timestamp: 1777744489106 - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda sha256: 21f6c8a20fe050d09bfda3fb0a9c3493936ce7d6e1b3b5f8b01319ee46d6c6f6 md5: 6639b6b0d8b5a284f027a2003669aa65 @@ -10124,18 +10022,18 @@ packages: purls: [] size: 265665 timestamp: 1772476832995 -- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae - md5: 30cd29cb87d819caead4d55184c1d115 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda + sha256: 523616c0530d305d2216c2b4a8dfd3872628b60083255b89c5e0d8c42e738cca + md5: e1c36c6121a7c9c76f2f148f1e83b983 depends: - python >=3.10 - python license: MIT license_family: MIT purls: - - pkg:pypi/zipp?source=hash-mapping - size: 24194 - timestamp: 1764460141901 + - pkg:pypi/zipp?source=compressed-mapping + size: 24461 + timestamp: 1776131454755 - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 diff --git a/tests/integration/fitting/test_fitting_with_diffusion_model.py b/tests/integration/fitting/test_fitting_with_diffusion_model.py index 22ca71f9..0b3abc82 100644 --- a/tests/integration/fitting/test_fitting_with_diffusion_model.py +++ b/tests/integration/fitting/test_fitting_with_diffusion_model.py @@ -61,7 +61,7 @@ def test_fitting_with_diffusion_model(self): assert fit_result_independent_single_Q.success assert fit_result_independent_single_Q.chi2 < 75.0 - assert fit_result_independent_single_Q.reduced_chi < 0.4 + assert fit_result_independent_single_Q.reduced_chi2 < 0.4 diffusion_experiment = Experiment('Diffusion') @@ -101,7 +101,7 @@ def test_fitting_with_diffusion_model(self): assert fit_result[0].success assert fit_result[0].chi2 < 43.0 - assert fit_result[0].reduced_chi < 0.3 + assert fit_result[0].reduced_chi2 < 0.3 ############### # Diffusion model @@ -142,7 +142,7 @@ def test_fitting_with_diffusion_model(self): assert fit_result[0].success assert fit_result[0].chi2 < 56.0 - assert fit_result[0].reduced_chi < 0.4 + assert fit_result[0].reduced_chi2 < 0.4 pars = diffusion_model.get_all_parameters() From 2aad9fe390b9f94dee53a41a1ed30f97ffbf380c Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 5 May 2026 11:44:58 +0200 Subject: [PATCH 19/19] Fix a small bug --- .../analysis/parameter_analysis.py | 15 ++- .../analysis/test_parameter_analysis.py | 127 ++++++++++++++++++ 2 files changed, 138 insertions(+), 4 deletions(-) diff --git a/src/easydynamics/analysis/parameter_analysis.py b/src/easydynamics/analysis/parameter_analysis.py index cb30b166..beef90fe 100644 --- a/src/easydynamics/analysis/parameter_analysis.py +++ b/src/easydynamics/analysis/parameter_analysis.py @@ -211,20 +211,27 @@ def plot( raise ValueError('No parameters available to plot.') full_model_dataset = None - if self.bindings is not None: + if self.bindings: full_model_dataset = self.calculate_model_dataset(self.bindings) + # If no names are provided, default to plot all parameters that have bindings. + # If no bindings are provided, plot all parameters. if names is None: names = [] - for b in self.bindings: - names.extend(b.get_parameter_names()) + + if not self.bindings: + names = list(self.parameters.keys()) + else: + for b in self.bindings: + names.extend(b.get_parameter_names()) names = self._normalize_names(names) + # Check that the units of the specified parameters are consistent. units = [self.parameters[name].unit for name in names] first_unit = units[0] if any(unit != first_unit for unit in units): - raise ValueError(f'Units are not consistent, and cannot be plotted together: {units}') + raise ValueError(f'Units are not compatible, and cannot be plotted together: {units}') color_cycle = itertools.cycle(rcParams['axes.prop_cycle'].by_key()['color']) markers = itertools.cycle(['o', 's', 'D', '^', 'v', '<', '>']) diff --git a/tests/unit/easydynamics/analysis/test_parameter_analysis.py b/tests/unit/easydynamics/analysis/test_parameter_analysis.py index 00fb7d6a..94fe9ddb 100644 --- a/tests/unit/easydynamics/analysis/test_parameter_analysis.py +++ b/tests/unit/easydynamics/analysis/test_parameter_analysis.py @@ -56,6 +56,9 @@ def mock_model_dataset(self): 'BrownianTranslationalDiffusion area': sc.DataArray( data=sc.array(dims=['Q'], values=[6.1, 7.1], unit='meV') ), + 'BrownianTranslationalDiffusion width': sc.DataArray( + data=sc.array(dims=['Q'], values=[8.1, 9.1], unit='meV') + ), }) @pytest.fixture @@ -243,6 +246,74 @@ def test_plot_no_parameters_raises(self, parameter_analysis): ): parameter_analysis.plot() + def test_plot_incompatible_units_raises(self, parameter_analysis): + # WHEN THEN EXPECT + with ( + patch( + 'easydynamics.analysis.parameter_analysis._in_notebook', + return_value=True, + ), + pytest.raises(ValueError, match=r'Units are not compatible'), + ): + # parameter1 in meV, parameter2 in 1/meV + parameter_analysis.plot(names=['parameter1', 'parameter2']) + + @patch('easydynamics.analysis.parameter_analysis._in_notebook', return_value=True) + @patch('easydynamics.analysis.parameter_analysis.pp.plot') + def test_plot_calls_dependencies_correctly_names_none( + self, mock_plot, mock_notebook, parameter_analysis, mock_model_dataset, dataset + ): + # WHEN + # ensure compatible units for all parameters + dataset.pop('parameter2') + parameter_analysis.parameters = dataset + + # Mock calculate_model_dataset + parameter_analysis.calculate_model_dataset = MagicMock(return_value=mock_model_dataset) + + # THEN + result = parameter_analysis.plot() + + # EXPECT + + # 1. Notebook check + mock_notebook.assert_called_once() + + # 2. Model dataset calculation + parameter_analysis.calculate_model_dataset.assert_called_once_with( + parameter_analysis.bindings + ) + + # 3. Plot called + mock_plot.assert_called_once() + + # Extract call args + args, kwargs = mock_plot.call_args + + # 4. Dataset passed correctly + dataset = args[0] + assert isinstance(dataset, sc.Dataset) + + # Data keys + assert 'parameter1' in dataset + assert 'parameter3 area' in dataset + assert 'parameter3 width' in dataset + + # Model keys (from bindings) + assert 'Polynomial' in dataset + assert 'BrownianTranslationalDiffusion area' in dataset + assert 'BrownianTranslationalDiffusion width' in dataset + + # 5. Check some kwargs + assert kwargs['title'] == parameter_analysis.display_name + + # Ensure styling dictionaries exist + for key in ['linestyle', 'marker', 'color', 'markerfacecolor']: + assert key in kwargs + + # 6. Return value propagated + assert result == mock_plot.return_value + @patch('easydynamics.analysis.parameter_analysis._in_notebook', return_value=True) @patch('easydynamics.analysis.parameter_analysis.pp.plot') def test_plot_calls_dependencies_correctly( @@ -286,6 +357,62 @@ def test_plot_calls_dependencies_correctly( # Model keys (from bindings) assert 'Polynomial' in dataset assert 'BrownianTranslationalDiffusion area' in dataset + assert 'BrownianTranslationalDiffusion width' not in dataset # not requested + + # 5. Check some kwargs + assert kwargs['title'] == parameter_analysis.display_name + + # Ensure styling dictionaries exist + for key in ['linestyle', 'marker', 'color', 'markerfacecolor']: + assert key in kwargs + + # 6. Return value propagated + assert result == mock_plot.return_value + + @patch('easydynamics.analysis.parameter_analysis._in_notebook', return_value=True) + @patch('easydynamics.analysis.parameter_analysis.pp.plot') + def test_plot_no_bindings( + self, mock_plot, mock_notebook, parameter_analysis, mock_model_dataset, dataset + ): + # WHEN + # ensure compatible units for all parameters + dataset.pop('parameter2') + parameter_analysis.parameters = dataset + parameter_analysis.bindings = None + + # Mock calculate_model_dataset + parameter_analysis.calculate_model_dataset = MagicMock(return_value=mock_model_dataset) + + # THEN + result = parameter_analysis.plot() + + # EXPECT + + # 1. Notebook check + mock_notebook.assert_called_once() + + # 2. Model dataset calculation + parameter_analysis.calculate_model_dataset.assert_not_called() + + # 3. Plot called + mock_plot.assert_called_once() + + # Extract call args + args, kwargs = mock_plot.call_args + + # 4. Dataset passed correctly + dataset = args[0] + assert isinstance(dataset, sc.Dataset) + + # Data keys + assert 'parameter1' in dataset + assert 'parameter3 area' in dataset + assert 'parameter3 width' in dataset + + # Model keys should be absent + assert 'Polynomial' not in dataset + assert 'BrownianTranslationalDiffusion area' not in dataset + assert 'BrownianTranslationalDiffusion width' not in dataset # 5. Check some kwargs assert kwargs['title'] == parameter_analysis.display_name