From bd31104b66bdda8da51d44e2b9b011d2b24076ee Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 23 Mar 2026 11:18:44 +0100 Subject: [PATCH 01/38] Refl1d fix (#302) * assure refl1d actually runs from the GUI * don't fail on codecov upload --- .github/workflows/python-ci.yml | 2 +- .../tutorials/fitting/simple_fitting.ipynb | 117 +++++++++++++++++- src/easyreflectometry/project.py | 10 ++ tests/test_project.py | 14 +++ 4 files changed, 136 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 90967cd9..1d33313f 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -61,7 +61,7 @@ jobs: name: unit-tests-job flags: unittests files: ./coverage-unit.xml - fail_ci_if_error: true + fail_ci_if_error: false verbose: true token: ${{ secrets.CODECOV_TOKEN }} slug: EasyScience/EasyReflectometryLib diff --git a/docs/src/tutorials/fitting/simple_fitting.ipynb b/docs/src/tutorials/fitting/simple_fitting.ipynb index 5dcd5eb8..ea761e75 100644 --- a/docs/src/tutorials/fitting/simple_fitting.ipynb +++ b/docs/src/tutorials/fitting/simple_fitting.ipynb @@ -30,7 +30,9 @@ "source": [ "%matplotlib inline\n", "\n", + "import matplotlib.pyplot as plt\n", "import pooch\n", + "import refl1d\n", "import refnx\n", "\n", "import easyreflectometry\n", @@ -63,7 +65,8 @@ "outputs": [], "source": [ "print(f'easyreflectometry: {easyreflectometry.__version__}')\n", - "print(f'refnx: {refnx.__version__}')" + "print(f'refnx: {refnx.__version__}')\n", + "print(f'refl1d: {refl1d.__version__}')" ] }, { @@ -395,7 +398,7 @@ "## Choosing our calculation engine\n", "\n", "The `easyreflectometry` package enables the calculation of the reflectometry profile using either [*refnx*](https://refnx.readthedocs.io/) or [*Refl1D*](https://refl1d.readthedocs.io/en/latest/).\n", - "For this tutorial, we will stick to the current default, which is *refnx*. \n", + "We will first run the fit with the current default, *refnx*, and then rebuild the same starting model with *Refl1D* to compare the results.\n", "The calculator must be created and associated with the model that we are to fit. " ] }, @@ -464,7 +467,8 @@ "metadata": {}, "outputs": [], "source": [ - "analysed = fitter.fit(data)" + "analysed_refnx = fitter.fit(data)\n", + "analysed = analysed_refnx" ] }, { @@ -513,18 +517,119 @@ "model" ] }, + { + "cell_type": "markdown", + "id": "2b2fe558", + "metadata": {}, + "source": [ + "## Repeating the optimisation with Refl1D\n", + "\n", + "To compare backends fairly, we rebuild the same initial model and then switch the calculator to `refl1d`.\n", + "This ensures that the second optimisation starts from the same parameter guesses rather than from the already-optimised `refnx` result." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "341abc6f", + "metadata": {}, + "outputs": [], + "source": [ + "print(f'refl1d: {refl1d.__version__}')\n", + "\n", + "si_refl1d = Material(sld=2.07, isld=0, name='Si')\n", + "sio2_refl1d = Material(sld=3.47, isld=0, name='SiO2')\n", + "film_refl1d = Material(sld=2.0, isld=0, name='Film')\n", + "d2o_refl1d = Material(sld=6.36, isld=0, name='D2O')\n", + "\n", + "si_layer_refl1d = Layer(material=si_refl1d, thickness=0, roughness=0, name='Si layer')\n", + "sio2_layer_refl1d = Layer(material=sio2_refl1d, thickness=30, roughness=3, name='SiO2 layer')\n", + "film_layer_refl1d = Layer(material=film_refl1d, thickness=250, roughness=3, name='Film Layer')\n", + "subphase_refl1d = Layer(material=d2o_refl1d, thickness=0, roughness=3, name='D2O Subphase')\n", + "\n", + "superphase_refl1d = Multilayer([si_layer_refl1d, sio2_layer_refl1d], name='Si/SiO2 Superphase')\n", + "sample_refl1d = Sample(superphase_refl1d, Multilayer(film_layer_refl1d), Multilayer(subphase_refl1d), name='Film Structure')\n", + "\n", + "resolution_function_refl1d = PercentageFwhm(0.02)\n", + "model_refl1d = Model(\n", + " sample=sample_refl1d,\n", + " scale=1,\n", + " background=1e-6,\n", + " resolution_function=resolution_function_refl1d,\n", + " name='Film Model (Refl1D)'\n", + ")\n", + "\n", + "sio2_layer_refl1d.thickness.bounds = (15, 50)\n", + "film_layer_refl1d.thickness.bounds = (200, 300)\n", + "sio2_layer_refl1d.roughness.bounds = (1, 15)\n", + "film_layer_refl1d.roughness.bounds = (1, 15)\n", + "subphase_refl1d.roughness.bounds = (1, 15)\n", + "film_layer_refl1d.material.sld.bounds = (0.1, 3)\n", + "model_refl1d.background.bounds = (1e-8, 1e-5)\n", + "model_refl1d.scale.bounds = (0.5, 1.5)\n", + "\n", + "interface_refl1d = CalculatorFactory()\n", + "interface_refl1d.switch('refl1d')\n", + "model_refl1d.interface = interface_refl1d\n", + "\n", + "print(interface_refl1d.current_interface.name)" + ] + }, + { + "cell_type": "markdown", + "id": "2f764d79", + "metadata": {}, + "source": [ + "We can now fit the same dataset with `refl1d` and compare the fitted curve and reduced chi-squared with the earlier `refnx` result." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70229cf9", + "metadata": {}, + "outputs": [], + "source": [ + "fitter_refl1d = MultiFitter(model_refl1d)\n", + "analysed_refl1d = fitter_refl1d.fit(data)\n", + "\n", + "print(f'refnx reduced chi: {analysed_refnx[\"reduced_chi\"]:.4f}')\n", + "print(f'refl1d reduced chi: {analysed_refl1d[\"reduced_chi\"]:.4f}')\n", + "\n", + "qz = data['coords']['Qz_0'].values\n", + "reflectivity = data['data']['R_0'].values\n", + "uncertainty = data['data']['R_0'].variances**0.5\n", + "\n", + "plt.figure(figsize=(8, 5))\n", + "plt.errorbar(qz, reflectivity, yerr=uncertainty, fmt='o', markersize=3, color='black', alpha=0.6, label='Data')\n", + "plt.plot(qz, analysed_refnx['R_0_model'].values, linewidth=2.5, label='refnx fit')\n", + "plt.plot(qz, analysed_refl1d['R_0_model'].values, linewidth=2.5, label='refl1d fit')\n", + "plt.yscale('log')\n", + "plt.xlabel(r'$Q_z$ (1/Å)')\n", + "plt.ylabel('Reflectivity')\n", + "plt.grid(True, which='both', alpha=0.25)\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "print('refnx model')\n", + "print(model)\n", + "print()\n", + "print('refl1d model')\n", + "print(model_refl1d)" + ] + }, { "cell_type": "markdown", "id": "df6db8c3-6515-478b-bd2e-aac967579231", "metadata": {}, "source": [ - "We note here that the results obtained are very similar to those from the [*refnx* tutorial](https://refnx.readthedocs.io/en/latest/getting_started.html#Fitting-a-neutron-reflectometry-dataset), which is hardly surprising given that we have used the *refnx* engine in this example." + "We note here that the results obtained with [*refnx*](https://refnx.readthedocs.io/en/latest/) and [*Refl1D*](https://refl1d.readthedocs.io/en/latest/) are very similar for this simple slab model, with only small backend-dependent differences in the fitted curve and optimised parameters." ] } ], "metadata": { "kernelspec": { - "display_name": "easyref", + "display_name": ".venv (3.11.9)", "language": "python", "name": "python3" }, @@ -538,7 +643,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.9" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 6f7096c1..67febcc0 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -220,7 +220,17 @@ def calculator(self) -> str: @calculator.setter def calculator(self, calculator: str) -> None: + if calculator == self._calculator.current_interface_name: + return + self._calculator.switch(calculator) + self._calculator.reset_storage() + + for model in self._models: + model.generate_bindings() + + self._fitter = None + self._fitter_model_index = None @property def minimizer(self) -> AvailableMinimizers: diff --git a/tests/test_project.py b/tests/test_project.py index b93df068..3876e4b8 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -268,6 +268,20 @@ def test_fitter_new_model_index(self): # Expect assert fitter_0 is not fitter_1 + def test_switch_calculator_rebuilds_model_bindings(self): + # When + project = Project() + project.default_model() + + # Then + project.calculator = 'refl1d' + reflectivity = project.model_data_for_model_at_index(0, np.array([0.01, 0.05, 0.1, 0.5])) + + # Expect + assert project.calculator == 'refl1d' + assert len(reflectivity.y) == 4 + assert np.all(np.isfinite(reflectivity.y)) + def test_experiments(self): # When project = Project() From de04b4970464e177c8dc21b3fe13d664174e4eb5 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 13 Apr 2026 11:26:25 +0200 Subject: [PATCH 02/38] Improved limits for certain parameters (#304) * initial implementation * set `disable` status on relevant sub/super-phase parameters * fixed tests * fixes after CR * fixed tests --- src/easyreflectometry/limits.py | 44 +++++ src/easyreflectometry/model/model.py | 2 + src/easyreflectometry/project.py | 49 ++++++ .../sample/elements/layers/layer.py | 5 + .../sample/elements/materials/material.py | 4 + tests/model/test_model.py | 4 +- .../elements/materials/test_material.py | 25 ++- tests/test_limits.py | 150 ++++++++++++++++++ tests/test_project.py | 29 ++++ 9 files changed, 297 insertions(+), 15 deletions(-) create mode 100644 src/easyreflectometry/limits.py create mode 100644 tests/test_limits.py diff --git a/src/easyreflectometry/limits.py b/src/easyreflectometry/limits.py new file mode 100644 index 00000000..691f86e3 --- /dev/null +++ b/src/easyreflectometry/limits.py @@ -0,0 +1,44 @@ +import numpy as np +from easyscience.variable import Parameter + +# Fixed-range limit definitions +SLD_LIMITS = (-1.0, 10.0) +SCALE_LIMITS = (0.0, 10.0) + + +def apply_default_limits(parameter: Parameter, kind: str) -> None: + """Apply default min/max to a parameter if current bounds are infinite. + + :param parameter: The parameter to adjust. + :type parameter: Parameter + :param kind: One of 'thickness', 'roughness', 'sld', 'isld', 'scale'. + :type kind: str + """ + if not parameter.independent: + return + + if kind in ('thickness', 'roughness'): + _apply_percentage_limits(parameter) + elif kind in ('sld', 'isld'): + _apply_fixed_limits(parameter, *SLD_LIMITS) + elif kind == 'scale': + _apply_fixed_limits(parameter, *SCALE_LIMITS) + + +def _apply_percentage_limits(parameter: Parameter) -> None: + """Set min to 50% and max to 200% of the current value, only if current bounds are inf.""" + value = parameter.value + if value == 0.0: + return + if np.isinf(parameter.min): + parameter.min = 0.5 * value + if np.isinf(parameter.max): + parameter.max = 2.0 * value + + +def _apply_fixed_limits(parameter: Parameter, low: float, high: float) -> None: + """Set fixed min/max, only if current bounds are inf.""" + if np.isinf(parameter.min) and low <= parameter.value: + parameter.min = low + if np.isinf(parameter.max) and high >= parameter.value: + parameter.max = high diff --git a/src/easyreflectometry/model/model.py b/src/easyreflectometry/model/model.py index e4bdaac5..7f651fa2 100644 --- a/src/easyreflectometry/model/model.py +++ b/src/easyreflectometry/model/model.py @@ -12,6 +12,7 @@ from easyscience import global_object from easyscience.variable import Parameter +from easyreflectometry.limits import apply_default_limits from easyreflectometry.sample import BaseAssembly from easyreflectometry.sample import Sample from easyreflectometry.utils import get_as_parameter @@ -86,6 +87,7 @@ def __init__( resolution_function = PercentageFwhm(DEFAULTS['resolution']['value']) scale = get_as_parameter('scale', scale, DEFAULTS) + apply_default_limits(scale, 'scale') background = get_as_parameter('background', background, DEFAULTS) self.color = color self._is_default = False diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 67febcc0..7e1ab3c9 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -21,6 +21,7 @@ from easyreflectometry.data.measurement import extract_orso_title from easyreflectometry.data.measurement import load_data_from_orso_file from easyreflectometry.fitting import MultiFitter +from easyreflectometry.limits import apply_default_limits from easyreflectometry.model import Model from easyreflectometry.model import ModelCollection from easyreflectometry.model import PercentageFwhm @@ -91,6 +92,52 @@ def parameters(self) -> List[Parameter]: parameters.append(param) return parameters + def _sync_parameter_states(self) -> None: + """Apply project-level parameter enablement and default limits. + + Superphase thickness/roughness and subphase thickness are physically + meaningless and are marked as disabled. Thickness and roughness default + ranges are then applied only to enabled parameters created from project + defaults, leaving explicit user-provided bounds untouched. + """ + if self._models is None: + return + + disabled_ids: set[int] = set() + for model in self._models: + sample = model.sample + if sample is None or len(sample) == 0: + continue + superphase = sample.superphase + if superphase is not None: + disabled_ids.add(id(superphase.thickness)) + disabled_ids.add(id(superphase.roughness)) + subphase = sample.subphase + if subphase is not None: + disabled_ids.add(id(subphase.thickness)) + + for model in self._models: + sample = model.sample + if sample is None or len(sample) == 0: + continue + for assembly in sample: + for layer in assembly.layers: + self._sync_layer_parameter_state(layer.thickness, 'thickness', disabled_ids) + self._sync_layer_parameter_state(layer.roughness, 'roughness', disabled_ids) + + def _sync_layer_parameter_state(self, parameter: Parameter, kind: str, disabled_ids: set[int]) -> None: + """Update a layer parameter's enabled state and pending default limits.""" + if id(parameter) in disabled_ids: + parameter.enabled = False + return + + if getattr(parameter, 'default_limits_pending', False): + delattr(parameter, 'default_limits_pending') + if getattr(parameter, 'enabled', True): + parameter.min = -np.inf + parameter.max = np.inf + apply_default_limits(parameter, kind) + @property def q_min(self): if self._q_min is None: @@ -204,6 +251,7 @@ def models(self, models: ModelCollection) -> None: self._materials.extend(self._get_materials_in_models()) for model in self._models: model.interface = self._calculator + self._sync_parameter_states() @property def fitter(self) -> MultiFitter: @@ -334,6 +382,7 @@ def add_sample_from_orso(self, sample: Sample) -> None: model.interface = self._calculator # Extract materials from the new model and add to project materials self._materials.extend(self._get_materials_from_model(model)) + self._sync_parameter_states() # Switch to the newly added model so its data is visible in the UI self.current_model_index = len(self._models) - 1 diff --git a/src/easyreflectometry/sample/elements/layers/layer.py b/src/easyreflectometry/sample/elements/layers/layer.py index 28f13a38..513b18f9 100644 --- a/src/easyreflectometry/sample/elements/layers/layer.py +++ b/src/easyreflectometry/sample/elements/layers/layer.py @@ -65,18 +65,23 @@ def __init__( if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) + thickness_value = thickness thickness = get_as_parameter( name='thickness', value=thickness, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Thickness', ) + thickness.default_limits_pending = not isinstance(thickness_value, Parameter) + + roughness_value = roughness roughness = get_as_parameter( name='roughness', value=roughness, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Roughness', ) + roughness.default_limits_pending = not isinstance(roughness_value, Parameter) super().__init__( name=name, diff --git a/src/easyreflectometry/sample/elements/materials/material.py b/src/easyreflectometry/sample/elements/materials/material.py index 249cd160..8c030031 100644 --- a/src/easyreflectometry/sample/elements/materials/material.py +++ b/src/easyreflectometry/sample/elements/materials/material.py @@ -7,6 +7,7 @@ from easyscience import global_object from easyscience.variable import Parameter +from easyreflectometry.limits import apply_default_limits from easyreflectometry.utils import get_as_parameter from ...base_core import BaseCore @@ -62,12 +63,15 @@ def __init__( default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Sld', ) + apply_default_limits(sld, 'sld') + isld = get_as_parameter( name='isld', value=isld, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Isld', ) + apply_default_limits(isld, 'isld') super().__init__( name=name, diff --git a/tests/model/test_model.py b/tests/model/test_model.py index 5745501e..49ce60fc 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -37,7 +37,7 @@ def test_default(self): assert_equal(str(p.scale.unit), 'dimensionless') assert_equal(p.scale.value, 1.0) assert_equal(p.scale.min, 0.0) - assert_equal(p.scale.max, np.inf) + assert_equal(p.scale.max, 10.0) assert_equal(p.scale.fixed, True) assert_equal(p.background.display_name, 'background') assert_equal(str(p.background.unit), 'dimensionless') @@ -73,7 +73,7 @@ def test_from_pars(self): assert_equal(str(mod.scale.unit), 'dimensionless') assert_equal(mod.scale.value, 2.0) assert_equal(mod.scale.min, 0.0) - assert_equal(mod.scale.max, np.inf) + assert_equal(mod.scale.max, 10.0) assert_equal(mod.scale.fixed, True) assert_equal(mod.background.display_name, 'background') assert_equal(str(mod.background.unit), 'dimensionless') diff --git a/tests/sample/elements/materials/test_material.py b/tests/sample/elements/materials/test_material.py index a9ff1dde..c07a2217 100644 --- a/tests/sample/elements/materials/test_material.py +++ b/tests/sample/elements/materials/test_material.py @@ -5,7 +5,6 @@ __author__ = 'github.com/arm61' __version__ = '0.0.1' -import numpy as np from easyscience import global_object from easyreflectometry.sample.elements.materials.material import DEFAULTS @@ -21,14 +20,14 @@ def test_no_arguments(self): assert p.sld.display_name == 'sld' assert str(p.sld.unit) == '1/Å^2' assert p.sld.value == 4.186 - assert p.sld.min == -np.inf - assert p.sld.max == np.inf + assert p.sld.min == -1.0 + assert p.sld.max == 10.0 assert p.sld.fixed is True assert p.isld.display_name == 'isld' assert str(p.isld.unit) == '1/Å^2' assert p.isld.value == 0.0 - assert p.isld.min == -np.inf - assert p.isld.max == np.inf + assert p.isld.min == -1.0 + assert p.isld.max == 10.0 assert p.isld.fixed is True def test_shuffled_arguments(self): @@ -38,14 +37,14 @@ def test_shuffled_arguments(self): assert p.sld.display_name == 'sld' assert str(p.sld.unit) == '1/Å^2' assert p.sld.value == 6.908 - assert p.sld.min == -np.inf - assert p.sld.max == np.inf + assert p.sld.min == -1.0 + assert p.sld.max == 10.0 assert p.sld.fixed is True assert p.isld.display_name == 'isld' assert str(p.isld.unit) == '1/Å^2' assert p.isld.value == -0.278 - assert p.isld.min == -np.inf - assert p.isld.max == np.inf + assert p.isld.min == -1.0 + assert p.isld.max == 10.0 assert p.isld.fixed is True def test_only_sld_key(self): @@ -53,8 +52,8 @@ def test_only_sld_key(self): assert p.sld.display_name == 'sld' assert str(p.sld.unit) == '1/Å^2' assert p.sld.value == 10 - assert p.sld.min == -np.inf - assert p.sld.max == np.inf + assert p.sld.min == -1.0 + assert p.sld.max == 10.0 assert p.sld.fixed is True def test_only_sld_key_parameter(self): @@ -69,8 +68,8 @@ def test_only_isld_key(self): assert p.isld.display_name == 'isld' assert str(p.isld.unit) == '1/Å^2' assert p.isld.value == 10 - assert p.isld.min == -np.inf - assert p.isld.max == np.inf + assert p.isld.min == -1.0 + assert p.isld.max == 10.0 assert p.isld.fixed is True def test_only_isld_key_parameter(self): diff --git a/tests/test_limits.py b/tests/test_limits.py new file mode 100644 index 00000000..e8320eb7 --- /dev/null +++ b/tests/test_limits.py @@ -0,0 +1,150 @@ +import numpy as np +import pytest +from easyscience import global_object +from easyscience.variable import Parameter + +from easyreflectometry.limits import SCALE_LIMITS +from easyreflectometry.limits import SLD_LIMITS +from easyreflectometry.limits import apply_default_limits + + +class TestApplyDefaultLimits: + def test_sld_with_inf_bounds(self): + param = Parameter('sld', 4.186, min=-np.inf, max=np.inf) + apply_default_limits(param, 'sld') + assert param.min == SLD_LIMITS[0] + assert param.max == SLD_LIMITS[1] + + def test_isld_with_inf_bounds(self): + param = Parameter('isld', 0.0, min=-np.inf, max=np.inf) + apply_default_limits(param, 'isld') + assert param.min == SLD_LIMITS[0] + assert param.max == SLD_LIMITS[1] + + def test_sld_preserves_finite_bounds(self): + param = Parameter('sld', 4.0, min=-2.0, max=8.0) + apply_default_limits(param, 'sld') + assert param.min == -2.0 + assert param.max == 8.0 + + def test_scale_with_inf_bounds(self): + param = Parameter('scale', 1.0, min=0, max=np.inf) + apply_default_limits(param, 'scale') + assert param.min == SCALE_LIMITS[0] + assert param.max == SCALE_LIMITS[1] + + def test_scale_preserves_finite_bounds(self): + param = Parameter('scale', 1.0, min=0.5, max=2.0) + apply_default_limits(param, 'scale') + assert param.min == 0.5 + assert param.max == 2.0 + + def test_thickness_percentage_limits(self): + param = Parameter('thickness', 10.0, min=0.0, max=np.inf) + apply_default_limits(param, 'thickness') + assert param.min == 0.0 # 0.0 is finite, not overwritten + assert param.max == 20.0 # 2.0 * 10.0 + + def test_thickness_both_inf(self): + param = Parameter('thickness', 10.0, min=-np.inf, max=np.inf) + apply_default_limits(param, 'thickness') + assert param.min == 5.0 # 0.5 * 10.0 + assert param.max == 20.0 # 2.0 * 10.0 + + def test_roughness_percentage_limits(self): + param = Parameter('roughness', 3.3, min=0.0, max=np.inf) + apply_default_limits(param, 'roughness') + assert param.min == 0.0 # 0.0 is finite, not overwritten + assert param.max == pytest.approx(6.6) # 2.0 * 3.3 + + def test_thickness_zero_value_unchanged(self): + param = Parameter('thickness', 0.0, min=0.0, max=np.inf) + apply_default_limits(param, 'thickness') + assert param.min == 0.0 + assert param.max == np.inf # unchanged, zero-valued skip + + def test_roughness_zero_value_unchanged(self): + param = Parameter('roughness', 0.0, min=-np.inf, max=np.inf) + apply_default_limits(param, 'roughness') + assert param.min == -np.inf + assert param.max == np.inf + + def test_thickness_preserves_finite_bounds(self): + param = Parameter('thickness', 10.0, min=2.0, max=25.0) + apply_default_limits(param, 'thickness') + assert param.min == 2.0 + assert param.max == 25.0 + + def test_dependent_parameter_skipped(self): + independent_param = Parameter('sld_main', 4.0, min=-np.inf, max=np.inf) + dependent_param = Parameter('sld_dep', 4.0, min=-np.inf, max=np.inf) + dependent_param.make_dependent_on(dependency_expression='a', dependency_map={'a': independent_param}) + apply_default_limits(dependent_param, 'sld') + assert np.isinf(dependent_param.min) + assert np.isinf(dependent_param.max) + + def test_unknown_kind_is_noop(self): + param = Parameter('foo', 5.0, min=-np.inf, max=np.inf) + apply_default_limits(param, 'unknown') + assert np.isinf(param.min) + assert np.isinf(param.max) + + +class TestIntegrationWithConstructors: + def setup_method(self): + global_object.map._clear() + + def test_material_gets_sld_limits(self): + from easyreflectometry.sample.elements.materials.material import Material + + mat = Material(sld=6.36, isld=0.0) + assert mat.sld.min == SLD_LIMITS[0] + assert mat.sld.max == SLD_LIMITS[1] + assert mat.isld.min == SLD_LIMITS[0] + assert mat.isld.max == SLD_LIMITS[1] + + def test_layer_gets_percentage_limits(self): + from easyreflectometry.project import Project + + project = Project() + project.default_model() + layer = project.models[0].sample[1].layers[0] + assert layer.thickness.min == 50.0 + assert layer.thickness.max == 200.0 + assert layer.roughness.min == 1.5 + assert layer.roughness.max == 6.0 + + def test_layer_constructor_keeps_default_bounds_until_project_sync(self): + from easyreflectometry.sample.elements.layers.layer import Layer + + layer = Layer(thickness=20.0, roughness=5.0) + assert layer.thickness.min == 0.0 + assert layer.thickness.max == np.inf + assert layer.roughness.min == 0.0 + assert layer.roughness.max == np.inf + + def test_layer_zero_thickness_unchanged(self): + from easyreflectometry.project import Project + + project = Project() + project.default_model() + layer = project.models[0].sample[0].layers[0] + assert layer.thickness.min == 0.0 + assert layer.thickness.max == np.inf + assert layer.roughness.min == 0.0 + assert layer.roughness.max == np.inf + + def test_model_gets_scale_limits(self): + from easyreflectometry.model.model import Model + + model = Model(scale=1.0) + assert model.scale.min == SCALE_LIMITS[0] + assert model.scale.max == SCALE_LIMITS[1] + + def test_existing_parameter_bounds_preserved(self): + from easyreflectometry.sample.elements.materials.material import Material + + custom_sld = Parameter('sld', 4.0, min=-0.5, max=7.0) + mat = Material(sld=custom_sld) + assert mat.sld.min == -0.5 + assert mat.sld.max == 7.0 diff --git a/tests/test_project.py b/tests/test_project.py index 3876e4b8..3c390960 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -720,6 +720,35 @@ def test_parameters(self): assert len(parameters) == 14 assert isinstance(parameters[0], Parameter) + def test_parameters_enabled_flags(self): + global_object.map._clear() + project = Project() + project.default_model() + + sample = project.models[0].sample + superphase = sample.superphase + subphase = sample.subphase + middle_layer = sample[1].front_layer + + assert superphase.thickness.enabled is False + assert superphase.roughness.enabled is False + assert subphase.thickness.enabled is False + assert getattr(subphase.roughness, 'enabled', True) is True + assert getattr(middle_layer.thickness, 'enabled', True) is True + assert getattr(middle_layer.roughness, 'enabled', True) is True + + def test_parameters_read_does_not_overwrite_enabled_flag(self): + global_object.map._clear() + project = Project() + project.default_model() + + parameter = project.models[0].sample[0].layers[0].thickness + parameter.enabled = True + + _ = project.parameters + + assert parameter.enabled is True + def test_current_experiment_index_getter_and_setter(self): global_object.map._clear() project = Project() From 303d6aa95f9deb1c8fd930fab67e3a09a5cc6dfb Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 27 Apr 2026 14:25:18 +0200 Subject: [PATCH 03/38] Mighell transform (#303) * initial implementation of the mighell algorithm for data with zero variances * don't fail on codecov upload * fixed chi2 reporting, added MD file with description * unit test update * added docs about Mighell * updated easyscience branch, deleted script * chi -> chi2 * use develop for core --- CHANGELOG.md | 9 + docs/src/api/api.rst | 9 + docs/src/api/fitting.rst | 112 ++++ notebooks/zero_variance_fitting.ipynb | 724 ++++++++++++++++++++++++++ pyproject.toml | 3 +- src/easyreflectometry/fitting.py | 318 +++++++++-- tests/test_fitting.py | 454 +++++++++++++++- 7 files changed, 1557 insertions(+), 72 deletions(-) create mode 100644 docs/src/api/fitting.rst create mode 100644 notebooks/zero_variance_fitting.ipynb diff --git a/CHANGELOG.md b/CHANGELOG.md index bb6a1617..b786ccd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# Version 1.4.0 (unreleased) + +Add Mighell-based handling of zero-variance points in fitting (issue #256). +Zero-variance data points are no longer forcibly discarded; instead, a hybrid +objective applies a Mighell substitution for zero-variance points while using +standard weighted least squares for the rest. The previous masking behavior is +available via `objective='legacy_mask'`. New `objective` parameter on +`MultiFitter`, `fit()`, and `fit_single_data_set_1d()`. + # Version 1.3.3 (17 June 2025) Added Chi^2 and fit status to fitting results. diff --git a/docs/src/api/api.rst b/docs/src/api/api.rst index ea8e2661..55bd611a 100644 --- a/docs/src/api/api.rst +++ b/docs/src/api/api.rst @@ -28,6 +28,15 @@ Project provides a higher-level interface for managing models, experiments, and project +Fitting +======= +Fitting helpers and objective functions. + +.. toctree:: + :maxdepth: 1 + + fitting + Assemblies ========== Assemblies are collections of layers that are used to represent a specific physical setup. diff --git a/docs/src/api/fitting.rst b/docs/src/api/fitting.rst new file mode 100644 index 00000000..f5748e59 --- /dev/null +++ b/docs/src/api/fitting.rst @@ -0,0 +1,112 @@ +Fitting +======= + +.. currentmodule:: easyreflectometry.fitting + +Objective functions and zero-variance handling +---------------------------------------------- + +:class:`MultiFitter` supports several objective modes for handling reflectometry +data during fitting, especially when measured variances are zero or invalid. + +The default objective is ``hybrid``. This uses ordinary weighted least squares +for points with positive variance and applies a Mighell-style substitution only +to points whose variance is zero or invalid. The older ``legacy_mask`` mode +drops zero-variance points before fitting. The ``mighell`` mode applies the +Mighell transform to every point. + +Mighell objective +~~~~~~~~~~~~~~~~~ + +The full ``mighell`` objective follows the algebraic form of the +``chi^2_gamma`` statistic described by Mighell for Poisson-distributed count +data: + +.. math:: + + \chi^2_\gamma = + \sum_i \frac{[n_i + \min(n_i, 1) - m_i]^2}{n_i + 1} + +where ``n_i`` are observed counts and ``m_i`` are model values. + +In EasyReflectometry this is implemented as a weighted least-squares problem. +For each observed value ``y_i`` the fitted target is shifted to + +.. math:: + + y_{\mathrm{eff},i} = y_i + \min(y_i, 1) + +and the effective uncertainty is + +.. math:: + + \sigma_i = \sqrt{y_i + 1} + +so the minimized objective is + +.. math:: + + \sum_i \left(\frac{y_{\mathrm{eff},i} - f_i}{\sigma_i}\right)^2 = + \sum_i \frac{[y_i + \min(y_i, 1) - f_i]^2}{y_i + 1} + +This is the same algebraic form as Mighell's statistic, with the model value +``f_i`` replacing ``m_i``. + +Scope and interpretation +~~~~~~~~~~~~~~~~~~~~~~~~ + +Mighell's statistic was derived for Poisson-distributed count data. In +reflectometry workflows, the fitted values are usually normalized +reflectivities or intensities rather than raw counts. They may already have +been processed, scaled, background-corrected, or otherwise transformed before +they reach the fitter. + +This distinction matters when interpreting the result. The full ``mighell`` +objective is not only a reweighting of residuals; it also changes the fitted +target from ``y`` to ``y + min(y, 1)``. For values between zero and one, this +can substantially increase the target value. A fit can therefore have a good +Mighell objective value while looking poorer against the originally plotted +reflectivity curve, or while having a worse classical chi-square. + +For reflectometry data, ``hybrid`` is generally the recommended compromise: +it preserves ordinary weighted least-squares behavior where valid variances are +available, while still allowing zero-variance points to contribute through the +Mighell-style substitution. + +Objective modes +~~~~~~~~~~~~~~~ + +``hybrid`` + Default. Use standard weighted least squares for points with positive + variance and apply the Mighell substitution only where variance is zero or + invalid. + +``mighell`` + Apply the Mighell transform to all points. The reported objective chi-square + is evaluated in transformed objective space and should not be interpreted as + a classical chi-square against the original reflectivity values. + +``legacy_mask`` + Remove zero-variance points before fitting and use standard weighted least + squares for the remaining points. + +``auto`` + Alias for ``hybrid``. + +Fit metrics +~~~~~~~~~~~ + +The fitter exposes both objective-space and classical fit metrics after fitting. +``objective_chi2`` and ``objective_reduced_chi`` describe the minimized +objective, which may include transformed targets under ``hybrid`` or +``mighell``. ``classical_chi2`` and ``classical_reduced_chi`` are computed +against the original observed reflectivity values using only points with +positive variance. + +API reference +------------- + +.. automodule:: easyreflectometry.fitting + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/notebooks/zero_variance_fitting.ipynb b/notebooks/zero_variance_fitting.ipynb new file mode 100644 index 00000000..ea3ede96 --- /dev/null +++ b/notebooks/zero_variance_fitting.ipynb @@ -0,0 +1,724 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1a8a52ac", + "metadata": {}, + "source": [ + "# Zero-Variance Handling in Reflectometry Fitting\n", + "\n", + "This notebook demonstrates how `easyreflectometry` handles data points with **zero variance**\n", + "during fitting. We compare three objective strategies:\n", + "\n", + "| Objective | Behaviour |\n", + "|---|---|\n", + "| `legacy_mask` | Drops zero-variance points entirely (old default) |\n", + "| `hybrid` | Keeps all points; applies Mighell substitution only to zero-variance entries (**new default**) |\n", + "| `mighell` | Applies the Mighell (1999) transform to every point |\n", + "\n", + "The experimental data comes from `tests/_static/ref_zero_var.txt`, which contains 192 data points,\n", + "6 of which have zero error (= zero variance)." + ] + }, + { + "cell_type": "markdown", + "id": "5c6429ac", + "metadata": {}, + "source": [ + "## 1. Import Required Libraries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4983acb8", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import warnings\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from easyreflectometry.calculators import CalculatorFactory\n", + "from easyreflectometry.data.measurement import load\n", + "from easyreflectometry.fitting import MultiFitter\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import PercentageFwhm\n", + "from easyreflectometry.sample import Layer\n", + "from easyreflectometry.sample import Material\n", + "from easyreflectometry.sample import Multilayer\n", + "from easyreflectometry.sample import Sample\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "96e174d4", + "metadata": {}, + "source": [ + "## 2. Load Experimental Data\n", + "\n", + "The file `ref_zero_var.txt` is a comma-separated file with columns: `q (Å⁻¹)`, `R`, `error`.\n", + "Several points near the high-Q end have `error = 0.0`, meaning zero variance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47fb3264", + "metadata": {}, + "outputs": [], + "source": [ + "DATA_PATH = os.path.join('..', 'tests', '_static', 'ref_zero_var.txt')\n", + "\n", + "# Load raw data for inspection\n", + "raw = np.loadtxt(DATA_PATH, delimiter=',', comments='#')\n", + "q_raw, r_raw, err_raw = raw[:, 0], raw[:, 1], raw[:, 2]\n", + "\n", + "# Load through easyreflectometry (produces a scipp DataGroup)\n", + "data = load(DATA_PATH)\n", + "data_key = next(iter(data['data'].keys()))\n", + "coord_key = next(iter(data['coords'].keys()))\n", + "result_model_key = f'{data_key}_model'\n", + "\n", + "print(f'Total data points : {len(q_raw)}')\n", + "print(f'Q range : [{q_raw.min():.4e}, {q_raw.max():.4e}] Å⁻¹')\n", + "print(f'R range : [{r_raw.min():.4e}, {r_raw.max():.4e}]')\n", + "print(f'Data key : {data_key}')\n", + "print(f'Coord key : {coord_key}')\n", + "print(f'Model key : {result_model_key}')" + ] + }, + { + "cell_type": "markdown", + "id": "c4495939", + "metadata": {}, + "source": [ + "## 3. Inspect Data and Identify Zero-Variance Points" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1be2ca78", + "metadata": {}, + "outputs": [], + "source": [ + "zero_mask = err_raw == 0.0\n", + "zero_indices = np.where(zero_mask)[0]\n", + "print(f'Number of zero-variance points: {zero_mask.sum()}')\n", + "print(f'Indices: {zero_indices}')\n", + "print('Q-values with zero variance:')\n", + "for idx in zero_indices:\n", + " print(f' [{idx:3d}] Q = {q_raw[idx]:.4e} Å⁻¹, R = {r_raw[idx]:.4e}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "373cdc62", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(10, 6))\n", + "\n", + "# Plot all data\n", + "valid = ~zero_mask\n", + "ax.errorbar(q_raw[valid], r_raw[valid], yerr=err_raw[valid],\n", + " fmt='o', ms=3, color='C0', alpha=0.7, label='Valid points')\n", + "ax.plot(q_raw[zero_mask], r_raw[zero_mask],\n", + " 'rx', ms=10, mew=2, label=f'Zero-variance points ({zero_mask.sum()})')\n", + "\n", + "ax.set_yscale('log')\n", + "ax.set_xlabel('Q (Å⁻¹)')\n", + "ax.set_ylabel('Reflectivity')\n", + "ax.set_title('Raw experimental data — zero-variance points highlighted')\n", + "ax.legend()\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "428759d3", + "metadata": {}, + "source": [ + "## 4. Define Materials\n", + "\n", + "A simple film-on-substrate structure: **Si** substrate / **SiO₂** native oxide / **Film** / **D₂O** superphase." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "006fb3cf", + "metadata": {}, + "outputs": [], + "source": [ + "si = Material(2.07, 0, 'Si')\n", + "sio2 = Material(3.47, 0, 'SiO2')\n", + "film = Material(2.0, 0, 'Film')\n", + "d2o = Material(6.36, 0, 'D2O')" + ] + }, + { + "cell_type": "markdown", + "id": "6031b9ad", + "metadata": {}, + "source": [ + "## 5. Define Layers and Sample Structure" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2643141f", + "metadata": {}, + "outputs": [], + "source": [ + "si_layer = Layer(si, 0, 0, 'Si layer')\n", + "sio2_layer = Layer(sio2, 30, 3, 'SiO2 layer')\n", + "film_layer = Layer(film, 250, 3, 'Film layer')\n", + "superphase = Layer(d2o, 0, 3, 'D2O superphase')\n", + "\n", + "sample = Sample(\n", + " Multilayer(si_layer),\n", + " Multilayer(sio2_layer),\n", + " Multilayer(film_layer),\n", + " Multilayer(superphase),\n", + " name='Film Structure',\n", + ")\n", + "print(sample)" + ] + }, + { + "cell_type": "markdown", + "id": "51bcedeb", + "metadata": {}, + "source": [ + "## 6. Create the Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22e6fbd9", + "metadata": {}, + "outputs": [], + "source": [ + "resolution_function = PercentageFwhm(0.02)\n", + "model = Model(sample, 1, 1e-6, resolution_function, 'Film Model')" + ] + }, + { + "cell_type": "markdown", + "id": "7eaf6993", + "metadata": {}, + "source": [ + "## 7. Set Calculator Backend and Compute Initial Curve" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "175afdcc", + "metadata": {}, + "outputs": [], + "source": [ + "interface = CalculatorFactory()\n", + "model.interface = interface\n", + "\n", + "# Compute initial model reflectivity\n", + "r_init = interface.fit_func(q_raw, model.unique_name)" + ] + }, + { + "cell_type": "markdown", + "id": "fff782bb", + "metadata": {}, + "source": [ + "## 8. Plot Initial Model vs Experimental Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7656b8b", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(10, 6))\n", + "\n", + "ax.errorbar(q_raw[valid], r_raw[valid], yerr=err_raw[valid],\n", + " fmt='o', ms=3, color='C0', alpha=0.6, label='Experiment (valid)')\n", + "ax.plot(q_raw[zero_mask], r_raw[zero_mask],\n", + " 'rx', ms=10, mew=2, label='Experiment (zero variance)')\n", + "ax.plot(q_raw, r_init, '-', color='C1', lw=1.5, label='Initial model')\n", + "\n", + "ax.set_yscale('log')\n", + "ax.set_xlabel('Q (Å⁻¹)')\n", + "ax.set_ylabel('Reflectivity')\n", + "ax.set_title('Experimental data vs initial model (before fitting)')\n", + "ax.legend()\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "6c916d41", + "metadata": {}, + "source": [ + "## 9. Set Fitting Parameters and Constraints" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a85ab7e8", + "metadata": {}, + "outputs": [], + "source": [ + "sio2_layer.thickness.fixed = False\n", + "sio2_layer.thickness.bounds = (15, 50)\n", + "\n", + "film_layer.thickness.fixed = False\n", + "film_layer.thickness.bounds = (200, 300)\n", + "\n", + "film.sld.fixed = False\n", + "film.sld.bounds = (0.1, 3)\n", + "\n", + "model.background.fixed = False\n", + "model.background.bounds = (1e-7, 1e-5)\n", + "\n", + "model.scale.fixed = False\n", + "model.scale.bounds = (0.5, 1.5)\n", + "\n", + "print('Free parameters:')\n", + "for p in model.get_fit_parameters():\n", + " print(f' {p.name:20s} = {float(p.value):.4g} bounds={p.bounds}')" + ] + }, + { + "cell_type": "markdown", + "id": "b83af903", + "metadata": {}, + "source": [ + "## 10. Zero-Variance Handling — Three Objective Modes\n", + "\n", + "We now fit the same data with each objective mode and compare the results.\n", + "\n", + "### How each mode handles zero-variance points\n", + "\n", + "- **`legacy_mask`** — zero-variance points are dropped before fitting. The fitter sees fewer\n", + " data points, and those Q-values have no influence on the result.\n", + "- **`hybrid`** (default) — valid points use standard weighted least-squares ($w = 1/\\sigma$).\n", + " Zero-variance points get the **Mighell (1999)** substitution:\n", + " $y_\\text{eff} = y + \\min(y, 1)$, $\\sigma = \\sqrt{\\max(y + 1,\\, \\varepsilon)}$.\n", + " This keeps all data in the fit while giving zero-variance points reasonable weight.\n", + "- **`mighell`** — applies the Mighell transform to *every* point, not just zero-variance ones.\n", + " This changes both the weighting and the fitted target for the entire dataset.\n", + "\n", + "Below we report two metric families:\n", + "\n", + "- **objective chi²** — the quantity actually minimized by the selected objective\n", + "- **classical chi²** — a standard variance-weighted chi² computed only on the original\n", + " positive-variance points, for comparison" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8182f0a9", + "metadata": {}, + "outputs": [], + "source": [ + "def build_fresh_model():\n", + " \"\"\"Build a fresh model+interface so each fit starts from the same initial state.\"\"\"\n", + " _si = Material(2.07, 0, 'Si')\n", + " _sio2 = Material(3.47, 0, 'SiO2')\n", + " _film = Material(2.0, 0, 'Film')\n", + " _d2o = Material(6.36, 0, 'D2O')\n", + "\n", + " _si_layer = Layer(_si, 0, 0, 'Si layer')\n", + " _sio2_layer = Layer(_sio2, 30, 3, 'SiO2 layer')\n", + " _film_layer = Layer(_film, 250, 3, 'Film layer')\n", + " _superphase = Layer(_d2o, 0, 3, 'D2O superphase')\n", + "\n", + " _sample = Sample(\n", + " Multilayer(_si_layer),\n", + " Multilayer(_sio2_layer),\n", + " Multilayer(_film_layer),\n", + " Multilayer(_superphase),\n", + " name='Film Structure',\n", + " )\n", + " _resolution = PercentageFwhm(0.02)\n", + " _model = Model(_sample, 1, 1e-6, _resolution, 'Film Model')\n", + "\n", + " _sio2_layer.thickness.fixed = False\n", + " _sio2_layer.thickness.bounds = (15, 50)\n", + " _film_layer.thickness.fixed = False\n", + " _film_layer.thickness.bounds = (200, 300)\n", + " _film.sld.fixed = False\n", + " _film.sld.bounds = (0.1, 3)\n", + " _model.background.fixed = False\n", + " _model.background.bounds = (1e-7, 1e-5)\n", + " _model.scale.fixed = False\n", + " _model.scale.bounds = (0.5, 1.5)\n", + "\n", + " _model.interface = CalculatorFactory()\n", + " return _model" + ] + }, + { + "cell_type": "markdown", + "id": "fd6f4833", + "metadata": {}, + "source": [ + "### 11. Fit with `legacy_mask`\n", + "\n", + "Zero-variance points are simply dropped." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e856e00f", + "metadata": {}, + "outputs": [], + "source": [ + "model_mask = build_fresh_model()\n", + "fitter_mask = MultiFitter(model_mask, objective='legacy_mask')\n", + "\n", + "with warnings.catch_warnings(record=True) as w_mask:\n", + " warnings.simplefilter('always')\n", + " result_mask = fitter_mask.fit(data)\n", + "\n", + "for w in w_mask:\n", + " print(f'[WARNING] {w.message}')\n", + "print(f'Success : {result_mask[\"success\"]}')\n", + "print(f'Objective reduced chi² : {result_mask[\"objective_reduced_chi\"]:.6f}')\n", + "print(f'Objective total chi² : {fitter_mask.objective_chi2:.6f}')\n", + "print(f'Classical reduced chi² : {result_mask[\"classical_reduced_chi\"]:.6f}')\n", + "print(f'Classical total chi² : {fitter_mask.classical_chi2:.6f}')\n", + "\n", + "r_fit_mask = result_mask[result_model_key].values" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79755052", + "metadata": {}, + "outputs": [], + "source": [ + "result_mask\n" + ] + }, + { + "cell_type": "markdown", + "id": "24cb6fe0", + "metadata": {}, + "source": [ + "### 12. Fit with `hybrid` (new default)\n", + "\n", + "All points kept; Mighell substitution applied only to zero-variance entries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20c034ba", + "metadata": {}, + "outputs": [], + "source": [ + "model_hybrid = build_fresh_model()\n", + "fitter_hybrid = MultiFitter(model_hybrid, objective='hybrid')\n", + "\n", + "with warnings.catch_warnings(record=True) as w_hybrid:\n", + " warnings.simplefilter('always')\n", + " result_hybrid = fitter_hybrid.fit(data)\n", + "\n", + "for w in w_hybrid:\n", + " print(f'[WARNING] {w.message}')\n", + "print(f'Success : {result_hybrid[\"success\"]}')\n", + "print(f'Objective reduced chi² : {result_hybrid[\"objective_reduced_chi\"]:.6f}')\n", + "print(f'Objective total chi² : {fitter_hybrid.objective_chi2:.6f}')\n", + "print(f'Classical reduced chi² : {result_hybrid[\"classical_reduced_chi\"]:.6f}')\n", + "print(f'Classical total chi² : {fitter_hybrid.classical_chi2:.6f}')\n", + "\n", + "r_fit_hybrid = result_hybrid[result_model_key].values" + ] + }, + { + "cell_type": "markdown", + "id": "79258c5f", + "metadata": {}, + "source": [ + "### 13. Fit with `mighell`\n", + "\n", + "Mighell transform applied to *all* points — the chi² landscape changes entirely." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90b08fdd", + "metadata": {}, + "outputs": [], + "source": [ + "model_mighell = build_fresh_model()\n", + "fitter_mighell = MultiFitter(model_mighell, objective='mighell')\n", + "\n", + "with warnings.catch_warnings(record=True) as w_mighell:\n", + " warnings.simplefilter('always')\n", + " result_mighell = fitter_mighell.fit(data)\n", + "\n", + "for w in w_mighell:\n", + " print(f'[WARNING] {w.message}')\n", + "print(f'Success : {result_mighell[\"success\"]}')\n", + "print(f'Objective reduced chi² : {result_mighell[\"objective_reduced_chi\"]:.6f}')\n", + "print(f'Objective total chi² : {fitter_mighell.objective_chi2:.6f}')\n", + "print(f'Classical reduced chi² : {result_mighell[\"classical_reduced_chi\"]:.6f}')\n", + "print(f'Classical total chi² : {fitter_mighell.classical_chi2:.6f}')\n", + "\n", + "r_fit_mighell = result_mighell[result_model_key].values" + ] + }, + { + "cell_type": "markdown", + "id": "6136693d", + "metadata": {}, + "source": [ + "## 14. Compare Fit Results\n", + "\n", + "### Parameter comparison table" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77c23990", + "metadata": {}, + "outputs": [], + "source": [ + "models = {\n", + " 'legacy_mask': model_mask,\n", + " 'hybrid': model_hybrid,\n", + " 'mighell': model_mighell,\n", + "}\n", + "results = {\n", + " 'legacy_mask': result_mask,\n", + " 'hybrid': result_hybrid,\n", + " 'mighell': result_mighell,\n", + "}\n", + "\n", + "# Build descriptive labels to disambiguate duplicates (e.g. two \"thickness\" params)\n", + "ref_params = model_mask.get_fit_parameters()\n", + "seen = {}\n", + "labels = []\n", + "for p in ref_params:\n", + " n = p.name\n", + " seen[n] = seen.get(n, 0) + 1\n", + " labels.append(f'{n} ({seen[n]})')\n", + "# Simplify if name appears only once\n", + "name_counts = {}\n", + "for p in ref_params:\n", + " name_counts[p.name] = name_counts.get(p.name, 0) + 1\n", + "labels_clean = []\n", + "seen2 = {}\n", + "for p in ref_params:\n", + " n = p.name\n", + " seen2[n] = seen2.get(n, 0) + 1\n", + " if name_counts[n] > 1:\n", + " labels_clean.append(f'{n}_{seen2[n]}')\n", + " else:\n", + " labels_clean.append(n)\n", + "\n", + "obj_keys = ['legacy_mask', 'hybrid', 'mighell']\n", + "\n", + "header = f'{\"Parameter\":<24s} {\"legacy_mask\":>14s} {\"hybrid\":>14s} {\"mighell\":>14s}'\n", + "print(header)\n", + "print('-' * len(header))\n", + "\n", + "for i, label in enumerate(labels_clean):\n", + " vals = []\n", + " for obj in obj_keys:\n", + " params = models[obj].get_fit_parameters()\n", + " vals.append(float(params[i].value))\n", + " print(f'{label:<24s} {vals[0]:>14.4g} {vals[1]:>14.4g} {vals[2]:>14.4g}')\n", + "\n", + "print('-' * len(header))\n", + "print(f'{\"objective red. chi²\":<24s} {result_mask[\"objective_reduced_chi\"]:>14.4f} '\n", + " f'{result_hybrid[\"objective_reduced_chi\"]:>14.4f} {result_mighell[\"objective_reduced_chi\"]:>14.6f}')\n", + "print(f'{\"classical red. chi²\":<24s} {result_mask[\"classical_reduced_chi\"]:>14.4f} '\n", + " f'{result_hybrid[\"classical_reduced_chi\"]:>14.4f} {result_mighell[\"classical_reduced_chi\"]:>14.4f}')\n", + "print(f'{\"success\":<24s} {str(result_mask[\"success\"]):>14s} '\n", + " f'{str(result_hybrid[\"success\"]):>14s} {str(result_mighell[\"success\"]):>14s}')" + ] + }, + { + "cell_type": "markdown", + "id": "906b9cdb", + "metadata": {}, + "source": [ + "### Fitted reflectivity curves — all three objectives" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4f8f3a7", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(10, 6))\n", + "\n", + "# Experimental data\n", + "ax.errorbar(\n", + " q_raw[valid],\n", + " r_raw[valid],\n", + " yerr=err_raw[valid],\n", + " fmt='o',\n", + " ms=2,\n", + " color='grey',\n", + " alpha=0.5,\n", + " label='Experiment',\n", + ")\n", + "ax.plot(q_raw[zero_mask], r_raw[zero_mask], 'rx', ms=10, mew=2, label='Zero-variance points')\n", + "\n", + "# Fitted curves\n", + "ax.plot(\n", + " q_raw,\n", + " r_fit_mask,\n", + " '-',\n", + " lw=1.5,\n", + " label=(\n", + " f'legacy_mask (obj={result_mask[\"objective_reduced_chi\"]:.3g}, '\n", + " f'class={result_mask[\"classical_reduced_chi\"]:.3g})'\n", + " ),\n", + ")\n", + "ax.plot(\n", + " q_raw,\n", + " r_fit_hybrid,\n", + " '--',\n", + " lw=1.5,\n", + " label=(\n", + " f'hybrid (obj={result_hybrid[\"objective_reduced_chi\"]:.3g}, '\n", + " f'class={result_hybrid[\"classical_reduced_chi\"]:.3g})'\n", + " ),\n", + ")\n", + "ax.plot(\n", + " q_raw,\n", + " r_fit_mighell,\n", + " ':',\n", + " lw=1.5,\n", + " label=(\n", + " f'mighell (obj={result_mighell[\"objective_reduced_chi\"]:.3g}, '\n", + " f'class={result_mighell[\"classical_reduced_chi\"]:.3g})'\n", + " ),\n", + ")\n", + "\n", + "ax.set_yscale('log')\n", + "ax.set_xlabel('Q (Å⁻¹)')\n", + "ax.set_ylabel('Reflectivity')\n", + "ax.set_title('Fitted reflectivity — comparison of objective modes')\n", + "ax.legend(fontsize=9)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "52821f2d", + "metadata": {}, + "source": [ + "## 15. Examine Residuals\n", + "\n", + "We compute normalised residuals $(R_\\text{exp} - R_\\text{model}) / \\sigma$ for each objective.\n", + "Zero-variance points are shown separately — for `legacy_mask` they were excluded from\n", + "the fit, while `hybrid` and `mighell` included them with transformed weights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0292ef11", + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(3, 1, figsize=(10, 10), sharex=True)\n", + "\n", + "fits = {\n", + " 'legacy_mask': r_fit_mask,\n", + " 'hybrid': r_fit_hybrid,\n", + " 'mighell': r_fit_mighell,\n", + "}\n", + "\n", + "for ax, (obj_name, r_fit) in zip(axes, fits.items()):\n", + " # Normalised residuals for valid points\n", + " residuals_valid = (r_raw[valid] - r_fit[valid]) / err_raw[valid]\n", + " ax.plot(q_raw[valid], residuals_valid, 'o', ms=2, alpha=0.6, color='C0', label='Valid points')\n", + "\n", + " # Un-normalised residuals at zero-variance points (no sigma to normalise by)\n", + " residuals_zero = r_raw[zero_mask] - r_fit[zero_mask]\n", + " ax.plot(q_raw[zero_mask], np.zeros_like(residuals_zero), 'rx', ms=10, mew=2,\n", + " label='Zero-var Q positions')\n", + "\n", + " ax.axhline(0, color='k', lw=0.5)\n", + " ax.set_ylabel('Normalised residual')\n", + " ax.set_title(f'{obj_name}')\n", + " ax.legend(fontsize=8, loc='upper right')\n", + "\n", + "axes[-1].set_xlabel('Q (Å⁻¹)')\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "a2f39948", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "| Objective | Zero-var handling | Objective chi² interpretation | Classical chi² comparison |\n", + "|---|---|---|---|\n", + "| `legacy_mask` | Dropped from fit | Same as classical chi² on retained points | Directly comparable |\n", + "| `hybrid` | Mighell substitution for zero-var only | Slightly modified objective | Usually close to classical when zero-var fraction is small |\n", + "| `mighell` | Mighell transform for **all** points | **Not** a classical chi²; target and weights both change | Can look visibly worse against plotted reflectivity even when the objective is minimized well |\n", + "\n", + "The `hybrid` mode (new default) is recommended: it keeps all data points in the fit while\n", + "preserving a classical chi² comparison on the original positive-variance points.\n", + "\n", + "The full `mighell` mode matches the Mighell paper's objective mathematically, but that paper is\n", + "derived for Poisson count data. Applied to normalized reflectivity, it should be interpreted as a\n", + "Poisson-style objective rather than a visually comparable reduced chi² fit." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "era", + "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.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index 9833bc40..b8d0df2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,8 @@ classifiers = [ requires-python = ">=3.11,<3.14" dependencies = [ - "easyscience", + #"easyscience", + "easyscience @ git+https://github.com/EasyScience/core.git@develop", "scipp", "refnx", "refl1d>=1.0.0", diff --git a/src/easyreflectometry/fitting.py b/src/easyreflectometry/fitting.py index 86df41e3..0750beb5 100644 --- a/src/easyreflectometry/fitting.py +++ b/src/easyreflectometry/fitting.py @@ -11,14 +11,145 @@ from easyreflectometry.data import DataSet1D from easyreflectometry.model import Model +_VALID_OBJECTIVES = ('legacy_mask', 'mighell', 'hybrid', 'auto') +_EPS = 1e-30 + + +def _validate_objective(objective: str) -> str: + """Validate and resolve the objective string. + + :param objective: The objective mode string. + :type objective: str + :return: Resolved objective string ('auto' becomes 'hybrid'). + :rtype: str + :raises ValueError: If the objective is not one of the valid options. + """ + if objective not in _VALID_OBJECTIVES: + raise ValueError(f'Unknown objective {objective!r}. Valid options: {_VALID_OBJECTIVES}') + if objective == 'auto': + return 'hybrid' + return objective + + +def _prepare_fit_arrays( + x_vals: np.ndarray, + y_vals: np.ndarray, + variances: np.ndarray, + objective: str, +) -> tuple[np.ndarray, np.ndarray, np.ndarray, dict]: + """Prepare x, y_eff, and weights arrays for fitting based on the objective mode. + + For ``legacy_mask``, zero-variance points are removed from all arrays. + For ``hybrid``, valid-variance points use standard WLS while zero-variance + points use Mighell-transformed y and weights. + For ``mighell``, all points use the Mighell transform. + + Note: ``variances`` here means σ² (the scipp convention), not σ. + + :param x_vals: Independent variable values. + :type x_vals: np.ndarray + :param y_vals: Observed dependent variable values. + :type y_vals: np.ndarray + :param variances: Variance (σ²) of each observed point. + :type variances: np.ndarray + :param objective: One of 'legacy_mask', 'hybrid', 'mighell'. + :type objective: str + :return: Tuple of (x_out, y_eff, weights, stats) where stats is a dict + with keys 'valid', 'mighell_substituted', 'masked'. + :rtype: tuple[np.ndarray, np.ndarray, np.ndarray, dict] + """ + n = len(y_vals) + zero_mask = variances <= 0.0 + n_zero = int(np.sum(zero_mask)) + n_valid = n - n_zero + + if objective == 'legacy_mask': + valid = ~zero_mask + x_out = x_vals[valid] + y_eff = y_vals[valid] + if n_valid > 0: + weights = 1.0 / np.sqrt(variances[valid]) + else: + weights = np.array([]) + stats = {'valid': n_valid, 'mighell_substituted': 0, 'masked': n_zero, 'transformed_all_points': False} + return x_out, y_eff, weights, stats + + # hybrid or mighell + y_eff = np.copy(y_vals) + sigma = np.empty(n) + + if objective == 'mighell': + apply_mighell = np.ones(n, dtype=bool) + else: + # hybrid: apply Mighell only to zero-variance points + apply_mighell = zero_mask + + # Standard WLS for non-Mighell points + standard = ~apply_mighell + if np.any(standard): + sigma[standard] = np.sqrt(variances[standard]) + + # Mighell transform for selected points + if np.any(apply_mighell): + y_m = y_vals[apply_mighell] + delta = np.minimum(y_m, 1.0) + y_eff[apply_mighell] = y_m + delta + sigma[apply_mighell] = np.sqrt(np.maximum(y_m + 1.0, _EPS)) + + weights = 1.0 / sigma + n_mighell = int(np.sum(apply_mighell)) + stats = { + 'valid': n - n_mighell, + 'mighell_substituted': n_mighell, + 'masked': 0, + 'transformed_all_points': bool(objective == 'mighell'), + } + return x_vals, y_eff, weights, stats + + +def _compute_weighted_chi2(y_obs: np.ndarray, y_calc: np.ndarray, sigma: np.ndarray) -> float: + """Return weighted chi-square for finite, strictly positive uncertainties.""" + valid = np.isfinite(y_obs) & np.isfinite(y_calc) & np.isfinite(sigma) & (sigma > 0.0) + if not np.any(valid): + return 0.0 + residual = (y_obs[valid] - y_calc[valid]) / sigma[valid] + return float(np.sum(residual**2)) + + +def _compute_reduced_chi2(chi2: float, n_points: int, n_params: int) -> float | None: + """Return reduced chi-square or None when degrees of freedom are not positive.""" + dof = int(n_points) - int(n_params) + if dof <= 0: + return None + return float(chi2 / dof) + + +def _fit_result_reduced_chi(result: FitResults, n_points: int | None = None) -> float: + """Return reduced chi-square from either supported FitResults attribute name.""" + for attribute in ('reduced_chi', 'reduced_chi2'): + value = getattr(result, attribute, None) + if isinstance(value, (int, float, np.number)): + return float(value) + if n_points is not None: + reduced_chi = _compute_reduced_chi2(float(result.chi2), n_points, result.n_pars) + if reduced_chi is not None: + return reduced_chi + raise AttributeError('FitResults object has neither reduced_chi nor reduced_chi2') + class MultiFitter: - def __init__(self, *args: Model): - r"""A convinence class for the :py:class:`easyscience.Fitting.Fitting` + def __init__(self, *args: Model, objective: str = 'hybrid'): + r"""A convenience class for the :py:class:`easyscience.Fitting.Fitting` which will populate the :py:class:`sc.DataGroup` appropriately after the fitting is performed. - :param args: Reflectometry model + :param args: Reflectometry model(s). + :param objective: Zero-variance handling strategy. One of + ``'hybrid'`` (default, Mighell for zero-variance, WLS otherwise), + ``'mighell'`` (Mighell transform for all points), + ``'legacy_mask'`` (drop zero-variance points), + ``'auto'`` (alias for ``'hybrid'``). + :type objective: str """ # This lets the unique_name be passed with the fit_func. @@ -32,22 +163,35 @@ def wrapped(*args, **kwargs): self._models = args self.easy_science_multi_fitter = EasyScienceMultiFitter(args, self._fit_func) self._fit_results: list[FitResults] | None = None - - def fit(self, data: sc.DataGroup, id: int = 0) -> sc.DataGroup: + self._classical_fit_metrics: list[dict] | None = None + self._objective = _validate_objective(objective) + + def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> sc.DataGroup: + """Perform the fitting and populate the DataGroups with the result. + + :param data: DataGroup to be fitted to and populated. + :type data: sc.DataGroup + :param id: Unused parameter kept for backward compatibility. + :type id: int + :param objective: Per-call override for the zero-variance objective. + If ``None``, uses the instance default set at construction. + :type objective: str or None + :return: A new DataGroup with fitted model curves, SLD profiles, and fit statistics. + :rtype: sc.DataGroup + + :note: Under the ``mighell`` objective all points are transformed, + so ``reduced_chi`` is not a classical chi-square statistic. + Under ``hybrid``, only zero-variance points are transformed; + when they are a small fraction of the data the chi-square + remains approximately classical. """ - Perform the fitting and populate the DataGroups with the result. - - :param data: DataGroup to be fitted to and populated - :param method: Optimisation method + obj = _validate_objective(objective) if objective is not None else self._objective - :note: Points with zero variance in the data will be automatically masked - out during fitting. A warning will be issued if any such points - are found, indicating the number of points masked per reflectivity. - """ refl_nums = [k[3:] for k in data['coords'].keys() if 'Qz' == k[:2]] x = [] y = [] dy = [] + original_arrays = [] # Process each reflectivity dataset for i in refl_nums: @@ -55,34 +199,38 @@ def fit(self, data: sc.DataGroup, id: int = 0) -> sc.DataGroup: y_vals = data['data'][f'R_{i}'].values variances = data['data'][f'R_{i}'].variances - # Find points with non-zero variance - zero_variance_mask = variances == 0.0 - num_zero_variance = np.sum(zero_variance_mask) + x_out, y_eff, weights, stats = _prepare_fit_arrays(x_vals, y_vals, variances, obj) - if num_zero_variance > 0: + if stats['masked'] > 0: warnings.warn( - f'Masked {num_zero_variance} data point(s) in reflectivity {i} due to zero variance during fitting.', + f'Masked {stats["masked"]} data point(s) in reflectivity {i} due to zero variance during fitting.', + UserWarning, + ) + if stats.get('transformed_all_points'): + warnings.warn( + f'Applied Mighell transform to all {len(y_vals)} point(s) in reflectivity {i} during fitting.', + UserWarning, + ) + elif stats['mighell_substituted'] > 0: + warnings.warn( + f'Applied Mighell substitution to {stats["mighell_substituted"]} ' + f'zero-variance point(s) in reflectivity {i} during fitting.', UserWarning, ) - # Keep only points with non-zero variances - valid_mask = ~zero_variance_mask - x_vals_masked = x_vals[valid_mask] - y_vals_masked = y_vals[valid_mask] - variances_masked = variances[valid_mask] - - x.append(x_vals_masked) - y.append(y_vals_masked) - dy.append(1 / np.sqrt(variances_masked)) + x.append(x_out) + y.append(y_eff) + dy.append(weights) + original_arrays.append({'x': x_vals, 'y': y_vals, 'variances': variances}) result = self.easy_science_multi_fitter.fit(x, y, weights=dy) self._fit_results = result + self._classical_fit_metrics = [] new_data = data.copy() for i, _ in enumerate(result): id = refl_nums[i] - new_data[f'R_{id}_model'] = sc.array( - dims=[f'Qz_{id}'], values=self._fit_func[i](data['coords'][f'Qz_{id}'].values) - ) + model_curve = self._fit_func[i](data['coords'][f'Qz_{id}'].values) + new_data[f'R_{id}_model'] = sc.array(dims=[f'Qz_{id}'], values=model_curve) sld_profile = self.easy_science_multi_fitter._fit_objects[i].interface.sld_profile(self._models[i].unique_name) new_data[f'SLD_{id}'] = sc.array(dims=[f'z_{id}'], values=sld_profile[1] * 1e-6, unit=sc.Unit('1/angstrom') ** 2) if 'attrs' in new_data: @@ -90,41 +238,88 @@ def fit(self, data: sc.DataGroup, id: int = 0) -> sc.DataGroup: new_data['coords'][f'z_{id}'] = sc.array( dims=[f'z_{id}'], values=sld_profile[0], unit=(1 / new_data['coords'][f'Qz_{id}'].unit).unit ) - new_data['reduced_chi'] = float(result[i].reduced_chi) + original = original_arrays[i] + sigma_classical = np.sqrt(np.clip(original['variances'], 0.0, None)) + n_classical_points = int(np.sum(original['variances'] > 0.0)) + classical_chi2 = _compute_weighted_chi2(original['y'], model_curve, sigma_classical) + classical_reduced_chi = _compute_reduced_chi2(classical_chi2, n_classical_points, result[i].n_pars) + objective_chi2 = float(result[i].chi2) + objective_reduced_chi = _fit_result_reduced_chi(result[i], np.size(result[i].x)) + + self._classical_fit_metrics.append( + { + 'classical_chi2': classical_chi2, + 'classical_reduced_chi': classical_reduced_chi, + 'objective_chi2': objective_chi2, + 'objective_reduced_chi': objective_reduced_chi, + 'n_classical_points': n_classical_points, + } + ) + + new_data['objective_chi2'] = objective_chi2 + new_data['objective_reduced_chi'] = objective_reduced_chi + new_data['classical_chi2'] = classical_chi2 + new_data['classical_reduced_chi'] = classical_reduced_chi + new_data['reduced_chi'] = objective_reduced_chi new_data['success'] = result[i].success return new_data - def fit_single_data_set_1d(self, data: DataSet1D) -> FitResults: + def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) -> FitResults: + """Perform fitting on a single 1D dataset. + + :param data: The 1D dataset to fit. Note that ``data.ye`` stores + variances (σ²), not standard deviations. + :type data: DataSet1D + :param objective: Per-call override for the zero-variance objective. + If ``None``, uses the instance default set at construction. + :type objective: str or None + :return: Fit results from the minimizer. + :rtype: FitResults """ - Perform the fitting and populate the DataGroups with the result. + obj = _validate_objective(objective) if objective is not None else self._objective - :param data: DataGroup to be fitted to and populated - :param method: Optimisation method - """ x_vals = np.asarray(data.x) y_vals = np.asarray(data.y) variances = np.asarray(data.ye) - zero_variance_mask = variances == 0.0 - num_zero_variance = int(np.sum(zero_variance_mask)) + x_out, y_eff, weights, stats = _prepare_fit_arrays(x_vals, y_vals, variances, obj) - if num_zero_variance > 0: + if stats['masked'] > 0: + warnings.warn( + f'Masked {stats["masked"]} data point(s) in single-dataset fit due to zero variance during fitting.', + UserWarning, + ) + if stats.get('transformed_all_points'): + warnings.warn( + f'Applied Mighell transform to all {len(y_vals)} point(s) in single-dataset fit during fitting.', + UserWarning, + ) + elif stats['mighell_substituted'] > 0: warnings.warn( - f'Masked {num_zero_variance} data point(s) in single-dataset fit due to zero variance during fitting.', + f'Applied Mighell substitution to {stats["mighell_substituted"]} ' + 'zero-variance point(s) in single-dataset fit during fitting.', UserWarning, ) - valid_mask = ~zero_variance_mask - if not np.any(valid_mask): + if obj == 'legacy_mask' and len(x_out) == 0: raise ValueError('Cannot fit single dataset: all points have zero variance.') - x_vals_masked = x_vals[valid_mask] - y_vals_masked = y_vals[valid_mask] - variances_masked = variances[valid_mask] - - weights = 1.0 / np.sqrt(variances_masked) - result = self.easy_science_multi_fitter.fit(x=[x_vals_masked], y=[y_vals_masked], weights=[weights])[0] + result = self.easy_science_multi_fitter.fit(x=[x_out], y=[y_eff], weights=[weights])[0] self._fit_results = [result] + sigma_classical = np.sqrt(np.clip(variances, 0.0, None)) + model_curve = self._fit_func[0](x_vals) + n_classical_points = int(np.sum(variances > 0.0)) + classical_chi2 = _compute_weighted_chi2(y_vals, model_curve, sigma_classical) + classical_reduced_chi = _compute_reduced_chi2(classical_chi2, n_classical_points, result.n_pars) + self._classical_fit_metrics = [ + { + 'classical_chi2': classical_chi2, + 'classical_reduced_chi': classical_reduced_chi, + 'objective_chi2': float(result.chi2), + 'objective_reduced_chi': _fit_result_reduced_chi(result, len(x_out)), + 'n_classical_points': n_classical_points, + } + ] return result @property @@ -149,6 +344,33 @@ def reduced_chi(self) -> float | None: return total_chi2 / total_dof + @property + def classical_chi2(self) -> float | None: + """Classical chi-squared using only points with positive variances.""" + if self._classical_fit_metrics is None: + return None + return float(sum(metric['classical_chi2'] for metric in self._classical_fit_metrics)) + + @property + def classical_reduced_chi(self) -> float | None: + """Reduced classical chi-squared using only points with positive variances.""" + if self._classical_fit_metrics is None or self._fit_results is None: + return None + total_chi2 = self.classical_chi2 + total_points = sum(metric['n_classical_points'] for metric in self._classical_fit_metrics) + n_params = self._fit_results[0].n_pars + return _compute_reduced_chi2(total_chi2, total_points, n_params) + + @property + def objective_chi2(self) -> float | None: + """Objective-space chi-squared returned by the minimizer.""" + return self.chi2 + + @property + def objective_reduced_chi(self) -> float | None: + """Objective-space reduced chi-squared returned by the minimizer.""" + return self.reduced_chi + def switch_minimizer(self, minimizer: AvailableMinimizers) -> None: """ Switch the minimizer for the fitting. diff --git a/tests/test_fitting.py b/tests/test_fitting.py index 446f10b9..2a03a98c 100644 --- a/tests/test_fitting.py +++ b/tests/test_fitting.py @@ -5,6 +5,7 @@ import numpy as np import pytest +import scipp as sc from easyscience.fitting.minimizers.factory import AvailableMinimizers import easyreflectometry @@ -12,6 +13,8 @@ from easyreflectometry.data import DataSet1D from easyreflectometry.data.measurement import load from easyreflectometry.fitting import MultiFitter +from easyreflectometry.fitting import _prepare_fit_arrays +from easyreflectometry.fitting import _validate_objective from easyreflectometry.model import Model from easyreflectometry.model import PercentageFwhm from easyreflectometry.sample import Layer @@ -76,7 +79,7 @@ def test_fitting(minimizer): def test_fitting_with_zero_variance(): - """Test that zero variance points are properly detected and masked during fitting when present in the data.""" + """Test that zero variance points are handled via Mighell substitution (hybrid default).""" import warnings import numpy as np @@ -129,26 +132,24 @@ def test_fitting_with_zero_variance(): model.interface = interface fitter = MultiFitter(model) - # Capture warnings during fitting - check if zero variance points still exist in the data - # and are properly handled by the fitting method + # Capture warnings during fitting with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') analysed = fitter.fit(data) - # Check if any zero variance warnings were issued during fitting - fitting_warnings = [str(warning.message) for warning in w if 'zero variance during fitting' in str(warning.message)] + # Under hybrid default, zero variance points trigger Mighell substitution warnings + mighell_warnings = [str(warning.message) for warning in w if 'Mighell substitution' in str(warning.message)] + mask_warnings = [str(warning.message) for warning in w if 'Masked' in str(warning.message)] + + # Hybrid mode should NOT produce mask warnings + assert len(mask_warnings) == 0, f'Unexpected mask warnings under hybrid: {mask_warnings}' - # The fitting method should handle zero variance points gracefully - # If there are any zero variance points remaining in the data, they should be masked - # and a warning should be issued - if len(fitting_warnings) > 0: - # Verify the warning message format and that it mentions masking points - for warning_msg in fitting_warnings: - assert 'Masked' in warning_msg and 'zero variance during fitting' in warning_msg - print(f'Info: {warning_msg}') # Log for debugging + # If there are zero-variance points in the loaded data, Mighell warnings should appear + if len(mighell_warnings) > 0: + for warning_msg in mighell_warnings: + assert 'zero-variance point(s)' in warning_msg # Basic checks that fitting completed - # The keys will be based on the filename, not just '0' model_keys = [k for k in analysed.keys() if k.endswith('_model')] sld_keys = [k for k in analysed.keys() if k.startswith('SLD_')] assert len(model_keys) > 0, f'No model keys found in {list(analysed.keys())}' @@ -157,7 +158,7 @@ def test_fitting_with_zero_variance(): def test_fitting_with_manual_zero_variance(): - """Test the fit method with manually created zero variance points.""" + """Test the fit method with manually created zero variance points using hybrid (default).""" import warnings import numpy as np @@ -217,12 +218,12 @@ def test_fitting_with_manual_zero_variance(): warnings.simplefilter('always') analysed = fitter.fit(data) - # Check that warnings were issued about zero variance points - fitting_warnings = [str(warning.message) for warning in w if 'zero variance during fitting' in str(warning.message)] + # Under hybrid default, should get Mighell substitution warning, not masking + mighell_warnings = [str(warning.message) for warning in w if 'Mighell substitution' in str(warning.message)] + + assert len(mighell_warnings) == 1, f'Expected 1 Mighell warning, got {len(mighell_warnings)}: {mighell_warnings}' + assert '7 zero-variance point(s)' in mighell_warnings[0], f'Unexpected warning content: {mighell_warnings[0]}' - # Should have one warning about the 7 zero variance points (5 + 2) - assert len(fitting_warnings) == 1, f'Expected 1 warning, got {len(fitting_warnings)}: {fitting_warnings}' - assert 'Masked 7 data point(s)' in fitting_warnings[0], f'Unexpected warning content: {fitting_warnings[0]}' # Basic checks that fitting completed despite zero variance points assert 'R_0_model' in analysed.keys() assert 'SLD_0' in analysed.keys() @@ -230,9 +231,10 @@ def test_fitting_with_manual_zero_variance(): def test_fit_single_data_set_1d_masks_zero_variance_points(): + """Legacy mask mode: zero-variance points are dropped.""" model = Model() model.interface = CalculatorFactory() - fitter = MultiFitter(model) + fitter = MultiFitter(model, objective='legacy_mask') captured = {} mock_result = MagicMock() @@ -255,7 +257,7 @@ def _fake_fit(*, x, y, weights): ye=np.array([0.01, 0.0, 0.04]), ) - with pytest.warns(UserWarning, match='Masked 1 data point\(s\) in single-dataset fit'): + with pytest.warns(UserWarning, match='Masked 1 data point\\(s\\) in single-dataset fit'): result = fitter.fit_single_data_set_1d(data) assert result is mock_result @@ -286,9 +288,10 @@ def test_reduced_chi_uses_global_dof_across_fit_results(): def test_fit_single_data_set_1d_all_zero_variance_raises(): + """Legacy mask mode raises when all points have zero variance.""" model = Model() model.interface = CalculatorFactory() - fitter = MultiFitter(model) + fitter = MultiFitter(model, objective='legacy_mask') data = DataSet1D( name='all_zero', @@ -376,3 +379,408 @@ def _fake_fit(*, x, y, weights): assert result is mock_result assert np.allclose(captured['x'][0], np.array([0.01, 0.02, 0.03])) assert np.allclose(captured['y'][0], np.array([1.0, 0.8, 0.6])) + + +# --- New tests for objective-based zero-variance handling --- + + +def test_objective_validation_rejects_unknown_value(): + with pytest.raises(ValueError, match='Unknown objective'): + _validate_objective('bad_value') + + +def test_objective_validation_resolves_auto(): + assert _validate_objective('auto') == 'hybrid' + assert _validate_objective('hybrid') == 'hybrid' + assert _validate_objective('legacy_mask') == 'legacy_mask' + assert _validate_objective('mighell') == 'mighell' + + +def test_prepare_fit_arrays_weights_always_positive_and_finite(): + """Weights must be strictly positive and finite for all inputs and objectives.""" + test_cases = [ + # (y_vals, variances, description) + (np.array([0.0]), np.array([0.0]), 'y=0, var=0'), + (np.array([-0.5]), np.array([0.0]), 'y=-0.5, var=0'), + (np.array([-1.0]), np.array([0.0]), 'y=-1, var=0'), + (np.array([1e6]), np.array([0.0]), 'y=1e6, var=0'), + (np.array([0.5, 0.3, 0.1]), np.array([0.0, 0.0, 0.0]), 'all-zero variances'), + (np.array([0.5, 0.3, 0.1]), np.array([0.01, 0.0, 0.04]), 'mixed variances'), + (np.array([0.0, -0.5, -1.0, 1e6]), np.array([0.0, 0.0, 0.0, 0.0]), 'edge y values'), + ] + + for objective in ('hybrid', 'mighell'): + for y_vals, variances, desc in test_cases: + x = np.arange(len(y_vals), dtype=float) + _, _, weights, _ = _prepare_fit_arrays(x, y_vals, variances, objective) + assert len(weights) == len(y_vals), f'Wrong length for {desc}, {objective}' + assert np.all(weights > 0), f'Non-positive weight for {desc}, {objective}: {weights}' + assert np.all(np.isfinite(weights)), f'Non-finite weight for {desc}, {objective}: {weights}' + + +def test_prepare_fit_arrays_legacy_mask_drops_zero_variance(): + x = np.array([0.01, 0.02, 0.03]) + y = np.array([1.0, 0.8, 0.6]) + var = np.array([0.01, 0.0, 0.04]) + + x_out, y_eff, weights, stats = _prepare_fit_arrays(x, y, var, 'legacy_mask') + + assert np.allclose(x_out, [0.01, 0.03]) + assert np.allclose(y_eff, [1.0, 0.6]) + assert np.allclose(weights, [1.0 / np.sqrt(0.01), 1.0 / np.sqrt(0.04)]) + assert stats == {'valid': 2, 'mighell_substituted': 0, 'masked': 1, 'transformed_all_points': False} + + +def test_prepare_fit_arrays_hybrid_transforms_zero_variance(): + x = np.array([0.01, 0.02, 0.03]) + y = np.array([1.0, 0.8, 0.6]) + var = np.array([0.01, 0.0, 0.04]) + + x_out, y_eff, weights, stats = _prepare_fit_arrays(x, y, var, 'hybrid') + + # x unchanged + assert np.allclose(x_out, x) + # Index 0 and 2: standard WLS (unchanged y) + assert y_eff[0] == pytest.approx(1.0) + assert y_eff[2] == pytest.approx(0.6) + assert weights[0] == pytest.approx(1.0 / np.sqrt(0.01)) + assert weights[2] == pytest.approx(1.0 / np.sqrt(0.04)) + # Index 1: Mighell transform — y_eff = y + min(y, 1) = 0.8 + 0.8 = 1.6 + assert y_eff[1] == pytest.approx(0.8 + 0.8) + # sigma = sqrt(y + 1) = sqrt(1.8) + assert weights[1] == pytest.approx(1.0 / np.sqrt(1.8)) + assert stats == {'valid': 2, 'mighell_substituted': 1, 'masked': 0, 'transformed_all_points': False} + + +def test_prepare_fit_arrays_mighell_transforms_all(): + x = np.array([0.01, 0.02]) + y = np.array([0.5, 0.3]) + var = np.array([0.01, 0.04]) # All valid, but mighell transforms everything + + x_out, y_eff, weights, stats = _prepare_fit_arrays(x, y, var, 'mighell') + + assert np.allclose(x_out, x) + # y_eff = y + min(y, 1) = y + y (since y < 1) + assert y_eff[0] == pytest.approx(0.5 + 0.5) + assert y_eff[1] == pytest.approx(0.3 + 0.3) + # sigma = sqrt(y + 1) + assert weights[0] == pytest.approx(1.0 / np.sqrt(1.5)) + assert weights[1] == pytest.approx(1.0 / np.sqrt(1.3)) + assert stats == {'valid': 0, 'mighell_substituted': 2, 'masked': 0, 'transformed_all_points': True} + + +def test_fit_single_data_set_1d_hybrid_keeps_zero_variance_points(): + """Hybrid mode keeps all points (transforms zero-variance ones).""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) # default objective='hybrid' + + captured = {} + mock_result = MagicMock() + mock_result.chi2 = 1.0 + mock_result.n_pars = 1 + + def _fake_fit(*, x, y, weights): + captured['x'] = x + captured['y'] = y + captured['weights'] = weights + return [mock_result] + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.fit = MagicMock(side_effect=_fake_fit) + + data = DataSet1D( + name='hybrid_test', + x=np.array([0.01, 0.02, 0.03]), + y=np.array([1.0, 0.8, 0.6]), + ye=np.array([0.01, 0.0, 0.04]), + ) + + with pytest.warns(UserWarning, match='Mighell substitution'): + result = fitter.fit_single_data_set_1d(data) + + assert result is mock_result + # All 3 points should be passed through (not masked) + assert len(captured['x'][0]) == 3 + assert len(captured['y'][0]) == 3 + assert len(captured['weights'][0]) == 3 + + +def test_fit_single_data_set_1d_mighell_warning_mentions_all_points(): + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model, objective='mighell') + + mock_result = MagicMock() + mock_result.chi2 = 1.0 + mock_result.reduced_chi = 0.5 + mock_result.n_pars = 1 + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.fit = MagicMock(return_value=[mock_result]) + fitter._fit_func = [lambda x: np.zeros_like(x)] + + data = DataSet1D( + name='mighell_warning', + x=np.array([0.01, 0.02, 0.03]), + y=np.array([1.0, 0.8, 0.6]), + ye=np.array([0.01, 0.02, 0.04]), + ) + + with pytest.warns(UserWarning, match=r'Applied Mighell transform to all 3 point\(s\)'): + fitter.fit_single_data_set_1d(data) + + +def test_classical_and_objective_chi_are_split_for_fit_results(): + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model, objective='mighell') + + fit_result = MagicMock() + fit_result.chi2 = 0.25 + fit_result.reduced_chi = 0.125 + fit_result.n_pars = 1 + fit_result.x = np.array([0.01, 0.02, 0.03]) + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.fit = MagicMock(return_value=[fit_result]) + fitter.easy_science_multi_fitter._fit_objects = [MagicMock(interface=MagicMock())] + fitter.easy_science_multi_fitter._fit_objects[0].interface.sld_profile.return_value = ( + np.array([0.0, 1.0]), + np.array([1.0, 2.0]), + ) + + fitter._models = [MagicMock(unique_name='model_0', as_dict=MagicMock(return_value={'name': 'model_0'}))] + fitter._fit_func = [lambda x: np.array([0.8, 0.75, 0.7])] + + data = sc.DataGroup( + { + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.array([0.01, 0.02, 0.03]), unit=sc.Unit('1/angstrom'))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.array([1.0, 0.9, 0.7]), variances=np.array([0.01, 0.0, 0.04]))}, + 'attrs': {}, + } + ) + + analysed = fitter.fit(data) + + expected_classical_chi2 = ((1.0 - 0.8) / 0.1) ** 2 + ((0.7 - 0.7) / 0.2) ** 2 + expected_classical_reduced = expected_classical_chi2 / (2 - fit_result.n_pars) + + assert analysed['objective_chi2'] == pytest.approx(0.25) + assert analysed['objective_reduced_chi'] == pytest.approx(0.125) + assert analysed['classical_chi2'] == pytest.approx(expected_classical_chi2) + assert analysed['classical_reduced_chi'] == pytest.approx(expected_classical_reduced) + assert fitter.objective_chi2 == pytest.approx(0.25) + assert fitter.objective_reduced_chi == pytest.approx(0.125) + assert fitter.classical_chi2 == pytest.approx(expected_classical_chi2) + assert fitter.classical_reduced_chi == pytest.approx(expected_classical_reduced) + + +def test_fit_single_data_set_1d_all_zero_variance_hybrid_does_not_raise(): + """Hybrid mode handles all-zero-variance data without raising.""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) # default objective='hybrid' + + captured = {} + mock_result = MagicMock() + mock_result.chi2 = 1.0 + mock_result.n_pars = 1 + + def _fake_fit(*, x, y, weights): + captured['x'] = x + captured['y'] = y + captured['weights'] = weights + return [mock_result] + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.fit = MagicMock(side_effect=_fake_fit) + + data = DataSet1D( + name='all_zero_hybrid', + x=np.array([0.01, 0.02, 0.03]), + y=np.array([1.0, 0.8, 0.6]), + ye=np.array([0.0, 0.0, 0.0]), + ) + + with pytest.warns(UserWarning, match='Mighell substitution'): + result = fitter.fit_single_data_set_1d(data) + + assert result is mock_result + assert len(captured['x'][0]) == 3 + + +def test_fit_single_data_set_1d_legacy_mask_preserves_old_behavior(): + """Legacy mask mode drops zero-variance points and warns with old message.""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model, objective='legacy_mask') + + captured = {} + mock_result = MagicMock() + mock_result.chi2 = 1.0 + mock_result.n_pars = 1 + + def _fake_fit(*, x, y, weights): + captured['x'] = x + captured['y'] = y + captured['weights'] = weights + return [mock_result] + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.fit = MagicMock(side_effect=_fake_fit) + + data = DataSet1D( + name='legacy_test', + x=np.array([0.01, 0.02, 0.03]), + y=np.array([1.0, 0.8, 0.6]), + ye=np.array([0.01, 0.0, 0.04]), + ) + + with pytest.warns(UserWarning, match='Masked 1 data point'): + result = fitter.fit_single_data_set_1d(data) + + assert result is mock_result + assert np.allclose(captured['x'][0], np.array([0.01, 0.03])) + assert np.allclose(captured['y'][0], np.array([1.0, 0.6])) + + +def test_fit_multi_dataset_hybrid_uses_transformed_y_and_weights(): + """Multi-dataset fit with hybrid objective transforms zero-variance points.""" + import scipp as sc + + qz_values = np.linspace(0.01, 0.3, 10) + r_values = np.exp(-qz_values * 50) + variances = np.ones_like(r_values) * 0.01 + variances[3:5] = 0.0 # 2 zero-variance points + + data = sc.DataGroup( + { + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=qz_values)}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=r_values, variances=variances)}, + } + ) + + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + captured = {} + + def _fake_fit(x, y, weights): + captured['x'] = x + captured['y'] = y + captured['weights'] = weights + mock_r = MagicMock() + mock_r.reduced_chi = 1.0 + mock_r.success = True + mock_r.chi2 = 1.0 + mock_r.n_pars = 1 + mock_r.x = x[0] + return [mock_r] + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.fit = MagicMock(side_effect=_fake_fit) + fitter.easy_science_multi_fitter._fit_objects = [MagicMock()] + fitter.easy_science_multi_fitter._fit_objects[0].interface.sld_profile.return_value = ( + np.linspace(0, 100, 5), + np.ones(5), + ) + + import warnings + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + fitter.fit(data) + + # All 10 points should be present (not masked) + assert len(captured['x'][0]) == 10 + assert len(captured['y'][0]) == 10 + assert len(captured['weights'][0]) == 10 + + # Zero-variance points should have Mighell-transformed y + for idx in [3, 4]: + y_orig = r_values[idx] + expected_y_eff = y_orig + min(y_orig, 1.0) + assert captured['y'][0][idx] == pytest.approx(expected_y_eff) + + # Check that Mighell warning was emitted + mighell_warnings = [str(ww.message) for ww in w if 'Mighell substitution' in str(ww.message)] + assert len(mighell_warnings) == 1 + assert '2 zero-variance point(s)' in mighell_warnings[0] + + +def test_fit_warnings_objective_specific(): + """Verify that each objective mode produces the correct warning type.""" + import warnings + + model = Model() + model.interface = CalculatorFactory() + + mock_result = MagicMock() + mock_result.chi2 = 1.0 + mock_result.n_pars = 1 + + data = DataSet1D( + name='warn_test', + x=np.array([0.01, 0.02, 0.03]), + y=np.array([1.0, 0.8, 0.6]), + ye=np.array([0.01, 0.0, 0.04]), + ) + + for obj, expected_fragment in [ + ('legacy_mask', 'Masked 1 data point(s)'), + ('hybrid', 'Mighell substitution'), + ('mighell', 'Applied Mighell transform to all 3 point(s)'), + ]: + fitter = MultiFitter(model, objective=obj) + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.fit = MagicMock(return_value=[mock_result]) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + fitter.fit_single_data_set_1d(data) + + matching = [str(ww.message) for ww in w if expected_fragment in str(ww.message)] + assert len(matching) > 0, f'No warning containing {expected_fragment!r} for objective={obj}' + + +def test_multifitter_constructor_rejects_bad_objective(): + model = Model() + model.interface = CalculatorFactory() + with pytest.raises(ValueError, match='Unknown objective'): + MultiFitter(model, objective='nonsense') + + +def test_fit_per_call_objective_override(): + """Per-call objective override in fit_single_data_set_1d works.""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model, objective='hybrid') # default + + captured = {} + mock_result = MagicMock() + mock_result.chi2 = 1.0 + mock_result.n_pars = 1 + + def _fake_fit(*, x, y, weights): + captured['x'] = x + captured['y'] = y + captured['weights'] = weights + return [mock_result] + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.fit = MagicMock(side_effect=_fake_fit) + + data = DataSet1D( + name='override_test', + x=np.array([0.01, 0.02, 0.03]), + y=np.array([1.0, 0.8, 0.6]), + ye=np.array([0.01, 0.0, 0.04]), + ) + + # Override to legacy_mask — should drop the zero-variance point + with pytest.warns(UserWarning, match='Masked 1 data point'): + fitter.fit_single_data_set_1d(data, objective='legacy_mask') + + assert len(captured['x'][0]) == 2 # one point dropped From b883a607a9b6005b1291eb6c7f9c6c0ad4238aac Mon Sep 17 00:00:00 2001 From: rozyczko Date: Thu, 30 Apr 2026 15:07:10 +0200 Subject: [PATCH 04/38] version bump. dependency fix for release. --- pyproject.toml | 3 +-- src/easyreflectometry/__version__.py | 2 +- tests/test_ort_file.py | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b8d0df2c..9833bc40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,8 +30,7 @@ classifiers = [ requires-python = ">=3.11,<3.14" dependencies = [ - #"easyscience", - "easyscience @ git+https://github.com/EasyScience/core.git@develop", + "easyscience", "scipp", "refnx", "refl1d>=1.0.0", diff --git a/src/easyreflectometry/__version__.py b/src/easyreflectometry/__version__.py index 77f1c8e6..51ed7c48 100644 --- a/src/easyreflectometry/__version__.py +++ b/src/easyreflectometry/__version__.py @@ -1 +1 @@ -__version__ = '1.5.0' +__version__ = '1.5.1' diff --git a/tests/test_ort_file.py b/tests/test_ort_file.py index c547b1f5..8ef1de16 100644 --- a/tests/test_ort_file.py +++ b/tests/test_ort_file.py @@ -123,6 +123,7 @@ def fit_model(load_data): fitter1 = MultiFitter(multi_layer_model) fitter1.switch_minimizer(AvailableMinimizers.Bumps_simplex) + fitter1.easy_science_multi_fitter.max_evaluations = 3000 analysed = fitter1.fit(data) return analysed From bd263d028455bf308d82d2a7c3d05ef7b4c1aef7 Mon Sep 17 00:00:00 2001 From: rozyczko Date: Thu, 30 Apr 2026 15:15:00 +0200 Subject: [PATCH 05/38] modified release version --- CHANGELOG.md | 2 +- src/easyreflectometry/__version__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b786ccd6..77c95690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Version 1.4.0 (unreleased) +# Version 1.6.0 (1 May 2026) Add Mighell-based handling of zero-variance points in fitting (issue #256). Zero-variance data points are no longer forcibly discarded; instead, a hybrid diff --git a/src/easyreflectometry/__version__.py b/src/easyreflectometry/__version__.py index 51ed7c48..bcd8d54e 100644 --- a/src/easyreflectometry/__version__.py +++ b/src/easyreflectometry/__version__.py @@ -1 +1 @@ -__version__ = '1.5.1' +__version__ = '1.6.0' From f76077395f2a7bf06e13f0953f001e339b5a6e5d Mon Sep 17 00:00:00 2001 From: rozyczko Date: Thu, 30 Apr 2026 16:37:48 +0200 Subject: [PATCH 06/38] updated docs --- CHANGELOG.md | 10 +++++----- docs/src/api/fitting.rst | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77c95690..69077f1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Version 1.6.0 (1 May 2026) -Add Mighell-based handling of zero-variance points in fitting (issue #256). -Zero-variance data points are no longer forcibly discarded; instead, a hybrid -objective applies a Mighell substitution for zero-variance points while using -standard weighted least squares for the rest. The previous masking behavior is -available via `objective='legacy_mask'`. New `objective` parameter on +Add Mighell-based handling of non-positive-variance points in fitting (issue #256). +Non-positive-variance data points are no longer forcibly discarded; instead, a +hybrid objective applies a Mighell substitution for non-positive-variance points +while using standard weighted least squares for the rest. The previous masking +behavior is available via `objective='legacy_mask'`. New `objective` parameter on `MultiFitter`, `fit()`, and `fit_single_data_set_1d()`. # Version 1.3.3 (17 June 2025) diff --git a/docs/src/api/fitting.rst b/docs/src/api/fitting.rst index f5748e59..65e1ba15 100644 --- a/docs/src/api/fitting.rst +++ b/docs/src/api/fitting.rst @@ -3,16 +3,16 @@ Fitting .. currentmodule:: easyreflectometry.fitting -Objective functions and zero-variance handling ----------------------------------------------- +Objective functions and non-positive variance handling +----------------------------------------------------- :class:`MultiFitter` supports several objective modes for handling reflectometry -data during fitting, especially when measured variances are zero or invalid. +data during fitting, especially when measured variances are non-positive. The default objective is ``hybrid``. This uses ordinary weighted least squares for points with positive variance and applies a Mighell-style substitution only -to points whose variance is zero or invalid. The older ``legacy_mask`` mode -drops zero-variance points before fitting. The ``mighell`` mode applies the +to points whose variance is non-positive. The older ``legacy_mask`` mode +drops non-positive-variance points before fitting. The ``mighell`` mode applies the Mighell transform to every point. Mighell objective @@ -69,8 +69,8 @@ Mighell objective value while looking poorer against the originally plotted reflectivity curve, or while having a worse classical chi-square. For reflectometry data, ``hybrid`` is generally the recommended compromise: -it preserves ordinary weighted least-squares behavior where valid variances are -available, while still allowing zero-variance points to contribute through the +it preserves ordinary weighted least-squares behavior where positive variances are +available, while still allowing non-positive-variance points to contribute through the Mighell-style substitution. Objective modes @@ -78,8 +78,8 @@ Objective modes ``hybrid`` Default. Use standard weighted least squares for points with positive - variance and apply the Mighell substitution only where variance is zero or - invalid. + variance and apply the Mighell substitution only where variance is + non-positive. ``mighell`` Apply the Mighell transform to all points. The reported objective chi-square @@ -87,7 +87,7 @@ Objective modes a classical chi-square against the original reflectivity values. ``legacy_mask`` - Remove zero-variance points before fitting and use standard weighted least + Remove non-positive-variance points before fitting and use standard weighted least squares for the remaining points. ``auto`` From 8e552667c17fa1ff83165588361d02a609227be6 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Thu, 30 Apr 2026 17:39:43 +0200 Subject: [PATCH 07/38] include plopp as a doc dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 9833bc40..ec3df724 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ dev = [ docs = [ "myst_parser", "nbsphinx", + "plopp", "sphinx<=8.1.3", "sphinx_autodoc_typehints", "sphinx_book_theme", From b16ec14864d7ec65ea36c19fa391ac6339ac3355 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Wed, 6 May 2026 11:47:24 +0200 Subject: [PATCH 08/38] Preparations for the release (#350) (#351) * backmerge after 1.6.0 release From d773e7ff56f979776736f46e02fe7520ed149c17 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Tue, 12 May 2026 15:23:41 +0200 Subject: [PATCH 09/38] Apply templates (#353) * initial commit up to and including `pixi run check` step * fixes * updates to pyproject.toml * remade __init__.py * minor fix * a few more fixes * Apply templates with docstrings fixes * Add missing favicon and update SVG assets * moved docs for mkdocs * make "pixi run mkdocs build" and "pixi run nonpy-format-check" work together * try using absolute paths for the wheel * minor fixes * refactor md file headers for docs * minor CR changes * linting * code review fixes * CR fixes * proper globbing * remove __version__ --------- Co-authored-by: Andrew Sazonov --- .badgery.yaml | 72 + .codecov.yml | 10 +- .copier-answers.yml | 30 +- .gitattributes | 2 + .github/actions/download-artifact/action.yml | 50 + .github/actions/github-script/action.yml | 19 + .../actions/setup-easyscience-bot/action.yml | 40 + .github/actions/setup-pixi/action.yml | 44 + .github/actions/upload-artifact/action.yml | 49 + .github/actions/upload-codecov/action.yml | 42 + .github/configs/pages-deployment.json | 6 + .github/configs/rulesets-develop.json | 37 + .github/configs/rulesets-gh-pages.json | 19 + .github/configs/rulesets-master.json | 30 + .github/copilot-instructions.md | 53 +- .github/release-drafter.yml | 32 +- .github/scripts/backmerge-conflict-issue.js | 69 + .github/scripts/publish-dashboard.sh | 77 + .github/workflows/backmerge.yml | 109 + .github/workflows/cleanup.yml | 84 + .github/workflows/codeql-analysis.yml | 72 +- .github/workflows/coverage.yml | 94 + .github/workflows/dashboard.yml | 111 + .github/workflows/docs.yml | 188 + .github/workflows/documentation-build.yml | 66 +- .github/workflows/issues-labels.yml | 149 + .github/workflows/lint-format.yml | 124 + .github/workflows/ossar-analysis.yml | 62 +- .github/workflows/pr-labels.yml | 63 + .github/workflows/pypi-publish.yml | 46 + .github/workflows/pypi-test.yml | 80 + .github/workflows/python-ci.yml | 84 +- .github/workflows/python-package.yml | 42 +- .github/workflows/python-publish.yml | 28 +- .../release-drafter-verify-pr-labels.yml | 18 +- .github/workflows/release-drafter.yml | 1 - .github/workflows/release-notes.yml | 71 + .github/workflows/release-pr.yml | 55 + .github/workflows/security.yml | 93 + .github/workflows/test-trigger.yml | 40 + .github/workflows/test.yml | 306 + .github/workflows/tutorial-tests-trigger.yml | 40 + .github/workflows/tutorial-tests.yml | 60 + .gitignore | 59 +- .pre-commit-config.yaml | 61 + .prettierignore | 32 + CHANGELOG.md | 17 +- CONTRIBUTING.md | 446 + LICENSE | 3 +- README.md | 56 +- codecov.yml | 13 + .../assemblies/gradient_layer.md | 1 + .../api-reference/assemblies/multilayer.md | 1 + .../assemblies/repeating_multilayer.md | 1 + .../assemblies/surfactant_layer.md | 1 + docs/docs/api-reference/data.md | 1 + docs/docs/api-reference/elements/layer.md | 1 + .../elements/layer_area_per_molecule.md | 1 + docs/docs/api-reference/elements/material.md | 1 + .../elements/material_density.md | 1 + .../elements/material_mixture.md | 1 + .../elements/material_solvated.md | 1 + docs/docs/api-reference/fitting.md | 1 + docs/docs/api-reference/index.md | 72 + docs/docs/api-reference/model.md | 1 + docs/docs/api-reference/project.md | 1 + docs/docs/api-reference/sample.md | 1 + docs/docs/assets/images/favicon.png | Bin 0 -> 14318 bytes docs/docs/assets/images/logo_dark.svg | 25 + docs/docs/assets/images/logo_light.svg | 25 + docs/docs/assets/javascripts/extra.js | 27 + docs/docs/assets/javascripts/mathjax.js | 33 + docs/docs/assets/stylesheets/extra.css | 359 + docs/docs/index.md | 21 + docs/docs/installation-and-setup/index.md | 282 + docs/docs/introduction/index.md | 68 + .../advancedfitting/multi_contrast.ipynb | 74 +- .../tutorials/advancedfitting/multiple.ort | 0 .../tutorials/basic/assemblies_library.md | 71 + docs/docs/tutorials/basic/layer_library.md | 69 + docs/docs/tutorials/basic/material_library.md | 78 + docs/docs/tutorials/basic/model.md | 87 + .../tutorials/fitting/d70d2o.ort | 0 docs/{src => docs}/tutorials/fitting/dspc.png | Bin .../tutorials/fitting/eq_monolayer.svg | 0 .../tutorials/fitting/eq_solvated.svg | 0 .../tutorials/fitting/example.ort | 0 .../tutorials/fitting/material_solvated.ipynb | 46 +- .../tutorials/fitting/monolayer.ipynb | 53 +- .../tutorials/fitting/monolayer.png | Bin .../tutorials/fitting/monolayer.svg | 0 .../tutorials/fitting/polymer_film.png | Bin .../tutorials/fitting/polymer_film.svg | 0 .../tutorials/fitting/repeating.ipynb | 42 +- .../tutorials/fitting/repeating.png | Bin .../tutorials/fitting/repeating.svg | 0 .../tutorials/fitting/repeating_layers.ort | 0 .../tutorials/fitting/simple_fitting.ipynb | 45 +- docs/docs/tutorials/index.md | 56 + .../tutorials/simulation/bilayer.ipynb | 59 +- .../tutorials/simulation/magnetism.ipynb | 101 +- .../mod_pointwise_two_layer_sample_dq-0.0.ort | 0 .../mod_pointwise_two_layer_sample_dq-1.0.ort | 0 ...mod_pointwise_two_layer_sample_dq-10.0.ort | 0 .../simulation/resolution_functions.ipynb | 51 +- .../tutorials/simulation/two_layers.png | Bin .../tutorials/simulation/two_layers.svg | 0 docs/docs/user-guide/index.md | 211 + docs/includes/abbreviations.md | 15 + docs/mkdocs.yml | 217 + docs/overrides/.icons/app.svg | 4 + docs/overrides/.icons/easyreflectometry.svg | 16 + docs/overrides/.icons/easyscience.svg | 20 + docs/overrides/.icons/google-colab.svg | 7 + docs/overrides/main.html | 39 + docs/overrides/partials/logo.html | 15 + docs/src/conf.py | 44 +- .../advancedfitting/advancedfitting.rst | 9 - .../tutorials/basic/assemblies_library.rst | 249 - docs/src/tutorials/basic/basic.rst | 13 - docs/src/tutorials/basic/layer_library.rst | 84 - docs/src/tutorials/basic/material_library.rst | 87 - docs/src/tutorials/basic/model.rst | 80 - docs/src/tutorials/extra/extra.rst | 7 - docs/src/tutorials/fitting/fitting.rst | 12 - docs/src/tutorials/simulation/simulation.rst | 11 - docs/src/tutorials/tutorials.rst | 49 - package-lock.json | 65 + package.json | 6 + pixi.lock | 13299 ++++++++++++++++ pixi.toml | 278 + prettierrc.toml | 22 + pyproject.toml | 441 +- src/easyreflectometry/__init__.py | 9 +- src/easyreflectometry/__version__.py | 1 - src/easyreflectometry/calculators/__init__.py | 5 +- .../calculators/bornagain/calculator.py | 98 +- .../calculators/bornagain/wrapper.py | 244 +- .../calculators/calculator_base.py | 118 +- src/easyreflectometry/calculators/factory.py | 8 + .../calculators/refl1d/calculator.py | 9 +- .../calculators/refl1d/wrapper.py | 180 +- .../calculators/refnx/calculator.py | 9 +- .../calculators/refnx/wrapper.py | 166 +- .../calculators/wrapper_base.py | 191 +- src/easyreflectometry/data/__init__.py | 3 + src/easyreflectometry/data/data_store.py | 21 + src/easyreflectometry/data/measurement.py | 15 +- src/easyreflectometry/fitting.py | 160 +- src/easyreflectometry/limits.py | 13 +- src/easyreflectometry/main.py | 4 + src/easyreflectometry/model/__init__.py | 3 + src/easyreflectometry/model/model.py | 84 +- .../model/model_collection.py | 26 +- .../model/resolution_functions.py | 30 +- src/easyreflectometry/orso_utils.py | 42 +- src/easyreflectometry/plot.py | 21 +- src/easyreflectometry/project.py | 210 +- src/easyreflectometry/sample/__init__.py | 3 + .../sample/assemblies/__init__.py | 2 + .../sample/assemblies/base_assembly.py | 39 +- .../sample/assemblies/bilayer.py | 135 +- .../sample/assemblies/gradient_layer.py | 50 +- .../sample/assemblies/multilayer.py | 45 +- .../sample/assemblies/repeating_multilayer.py | 24 +- .../sample/assemblies/surfactant_layer.py | 63 +- src/easyreflectometry/sample/base_core.py | 13 +- .../sample/collections/__init__.py | 2 + .../sample/collections/base_collection.py | 55 +- .../sample/collections/layer_collection.py | 16 +- .../sample/collections/material_collection.py | 18 +- .../sample/collections/sample.py | 56 +- .../sample/elements/__init__.py | 2 + .../sample/elements/layers/__init__.py | 2 + .../sample/elements/layers/layer.py | 29 +- .../layers/layer_area_per_molecule.py | 65 +- .../sample/elements/materials/__init__.py | 2 + .../sample/elements/materials/material.py | 20 +- .../elements/materials/material_density.py | 30 +- .../elements/materials/material_mixture.py | 64 +- .../elements/materials/material_solvated.py | 46 +- src/easyreflectometry/special/calculations.py | 98 +- src/easyreflectometry/special/parsing.py | 66 +- src/easyreflectometry/summary/__init__.py | 5 +- .../summary/html_templates.py | 15 +- src/easyreflectometry/summary/summary.py | 14 + src/easyreflectometry/utils.py | 27 +- .../bornagain/test_bornagain_calculator.py | 5 +- .../bornagain/test_bornagain_wrapper.py | 6 +- .../refl1d/test_refl1d_calculator.py | 6 +- .../calculators/refl1d/test_refl1d_wrapper.py | 6 +- .../refnx/test_refnx_calculator.py | 6 +- tests/calculators/refnx/test_refnx_wrapper.py | 41 +- tests/data/test_data_store.py | 11 +- tests/functional/test_dummy.py | 8 + tests/integration/fitting/test_dummy.py | 17 + .../integration/scipp-analysis/test_dummy.py | 17 + tests/model/test_model.py | 6 +- tests/model/test_model_collection.py | 8 +- tests/model/test_resolution_functions.py | 12 +- tests/package_test.py | 3 +- tests/sample/assemblies/test_base_assembly.py | 3 + tests/sample/assemblies/test_bilayer.py | 4 +- .../sample/assemblies/test_gradient_layer.py | 9 +- tests/sample/assemblies/test_multilayer.py | 6 +- .../assemblies/test_repeating_multilayer.py | 7 +- .../assemblies/test_surfactant_layer.py | 14 +- .../collections/test_base_collection.py | 3 + .../collections/test_layer_collection.py | 6 +- .../collections/test_material_collection.py | 3 + tests/sample/collections/test_sample.py | 7 +- tests/sample/elements/layers/test_layer.py | 6 +- .../layers/test_layer_area_per_molecule.py | 3 + .../elements/materials/test_material.py | 6 +- .../materials/test_material_density.py | 3 + .../materials/test_material_mixture.py | 3 + .../materials/test_material_solvated.py | 3 + tests/special/test_calculations.py | 4 +- tests/summary/test_summary.py | 3 + tests/test_data.py | 10 +- tests/test_fitting.py | 63 +- tests/test_limits.py | 3 + tests/test_measurement_comprehensive.py | 34 +- tests/test_orso_utils.py | 18 +- tests/test_ort_file.py | 5 +- tests/test_parameter_utils.py | 3 + tests/test_project.py | 36 +- tests/test_topmost_nesting.py | 5 +- tests/test_utils.py | 16 +- tests/unit/test_dummy.py | 8 + tools/license_headers.py | 321 + tools/update_docs_assets.py | 91 + tools/update_github_labels.py | 341 + vscode-template/settings.json | 22 +- 234 files changed, 22209 insertions(+), 2201 deletions(-) create mode 100644 .badgery.yaml create mode 100644 .gitattributes create mode 100644 .github/actions/download-artifact/action.yml create mode 100644 .github/actions/github-script/action.yml create mode 100644 .github/actions/setup-easyscience-bot/action.yml create mode 100644 .github/actions/setup-pixi/action.yml create mode 100644 .github/actions/upload-artifact/action.yml create mode 100644 .github/actions/upload-codecov/action.yml create mode 100644 .github/configs/pages-deployment.json create mode 100644 .github/configs/rulesets-develop.json create mode 100644 .github/configs/rulesets-gh-pages.json create mode 100644 .github/configs/rulesets-master.json create mode 100644 .github/scripts/backmerge-conflict-issue.js create mode 100644 .github/scripts/publish-dashboard.sh create mode 100644 .github/workflows/backmerge.yml create mode 100644 .github/workflows/cleanup.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 .github/workflows/dashboard.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/issues-labels.yml create mode 100644 .github/workflows/lint-format.yml create mode 100644 .github/workflows/pr-labels.yml create mode 100644 .github/workflows/pypi-publish.yml create mode 100644 .github/workflows/pypi-test.yml create mode 100644 .github/workflows/release-notes.yml create mode 100644 .github/workflows/release-pr.yml create mode 100644 .github/workflows/security.yml create mode 100644 .github/workflows/test-trigger.yml create mode 100644 .github/workflows/test.yml create mode 100644 .github/workflows/tutorial-tests-trigger.yml create mode 100644 .github/workflows/tutorial-tests.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .prettierignore create mode 100644 CONTRIBUTING.md create mode 100644 codecov.yml create mode 100644 docs/docs/api-reference/assemblies/gradient_layer.md create mode 100644 docs/docs/api-reference/assemblies/multilayer.md create mode 100644 docs/docs/api-reference/assemblies/repeating_multilayer.md create mode 100644 docs/docs/api-reference/assemblies/surfactant_layer.md create mode 100644 docs/docs/api-reference/data.md create mode 100644 docs/docs/api-reference/elements/layer.md create mode 100644 docs/docs/api-reference/elements/layer_area_per_molecule.md create mode 100644 docs/docs/api-reference/elements/material.md create mode 100644 docs/docs/api-reference/elements/material_density.md create mode 100644 docs/docs/api-reference/elements/material_mixture.md create mode 100644 docs/docs/api-reference/elements/material_solvated.md create mode 100644 docs/docs/api-reference/fitting.md create mode 100644 docs/docs/api-reference/index.md create mode 100644 docs/docs/api-reference/model.md create mode 100644 docs/docs/api-reference/project.md create mode 100644 docs/docs/api-reference/sample.md create mode 100644 docs/docs/assets/images/favicon.png create mode 100644 docs/docs/assets/images/logo_dark.svg create mode 100644 docs/docs/assets/images/logo_light.svg create mode 100644 docs/docs/assets/javascripts/extra.js create mode 100644 docs/docs/assets/javascripts/mathjax.js create mode 100644 docs/docs/assets/stylesheets/extra.css create mode 100644 docs/docs/index.md create mode 100644 docs/docs/installation-and-setup/index.md create mode 100644 docs/docs/introduction/index.md rename docs/{src => docs}/tutorials/advancedfitting/multi_contrast.ipynb (92%) rename docs/{src => docs}/tutorials/advancedfitting/multiple.ort (100%) create mode 100644 docs/docs/tutorials/basic/assemblies_library.md create mode 100644 docs/docs/tutorials/basic/layer_library.md create mode 100644 docs/docs/tutorials/basic/material_library.md create mode 100644 docs/docs/tutorials/basic/model.md rename docs/{src => docs}/tutorials/fitting/d70d2o.ort (100%) rename docs/{src => docs}/tutorials/fitting/dspc.png (100%) rename docs/{src => docs}/tutorials/fitting/eq_monolayer.svg (100%) rename docs/{src => docs}/tutorials/fitting/eq_solvated.svg (100%) rename docs/{src => docs}/tutorials/fitting/example.ort (100%) rename docs/{src => docs}/tutorials/fitting/material_solvated.ipynb (93%) rename docs/{src => docs}/tutorials/fitting/monolayer.ipynb (94%) rename docs/{src => docs}/tutorials/fitting/monolayer.png (100%) rename docs/{src => docs}/tutorials/fitting/monolayer.svg (100%) rename docs/{src => docs}/tutorials/fitting/polymer_film.png (100%) rename docs/{src => docs}/tutorials/fitting/polymer_film.svg (100%) rename docs/{src => docs}/tutorials/fitting/repeating.ipynb (94%) rename docs/{src => docs}/tutorials/fitting/repeating.png (100%) rename docs/{src => docs}/tutorials/fitting/repeating.svg (100%) rename docs/{src => docs}/tutorials/fitting/repeating_layers.ort (100%) rename docs/{src => docs}/tutorials/fitting/simple_fitting.ipynb (96%) create mode 100644 docs/docs/tutorials/index.md rename docs/{src => docs}/tutorials/simulation/bilayer.ipynb (95%) rename docs/{src => docs}/tutorials/simulation/magnetism.ipynb (87%) rename docs/{src => docs}/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort (100%) rename docs/{src => docs}/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort (100%) rename docs/{src => docs}/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort (100%) rename docs/{src => docs}/tutorials/simulation/resolution_functions.ipynb (90%) rename docs/{src => docs}/tutorials/simulation/two_layers.png (100%) rename docs/{src => docs}/tutorials/simulation/two_layers.svg (100%) create mode 100644 docs/docs/user-guide/index.md create mode 100644 docs/includes/abbreviations.md create mode 100644 docs/mkdocs.yml create mode 100644 docs/overrides/.icons/app.svg create mode 100644 docs/overrides/.icons/easyreflectometry.svg create mode 100644 docs/overrides/.icons/easyscience.svg create mode 100644 docs/overrides/.icons/google-colab.svg create mode 100644 docs/overrides/main.html create mode 100644 docs/overrides/partials/logo.html delete mode 100644 docs/src/tutorials/advancedfitting/advancedfitting.rst delete mode 100644 docs/src/tutorials/basic/assemblies_library.rst delete mode 100644 docs/src/tutorials/basic/basic.rst delete mode 100644 docs/src/tutorials/basic/layer_library.rst delete mode 100644 docs/src/tutorials/basic/material_library.rst delete mode 100644 docs/src/tutorials/basic/model.rst delete mode 100644 docs/src/tutorials/extra/extra.rst delete mode 100644 docs/src/tutorials/fitting/fitting.rst delete mode 100644 docs/src/tutorials/simulation/simulation.rst delete mode 100644 docs/src/tutorials/tutorials.rst create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pixi.lock create mode 100644 pixi.toml create mode 100644 prettierrc.toml delete mode 100644 src/easyreflectometry/__version__.py create mode 100644 src/easyreflectometry/sample/assemblies/__init__.py create mode 100644 src/easyreflectometry/sample/collections/__init__.py create mode 100644 src/easyreflectometry/sample/elements/__init__.py create mode 100644 src/easyreflectometry/sample/elements/layers/__init__.py create mode 100644 src/easyreflectometry/sample/elements/materials/__init__.py create mode 100644 tests/functional/test_dummy.py create mode 100644 tests/integration/fitting/test_dummy.py create mode 100644 tests/integration/scipp-analysis/test_dummy.py create mode 100644 tests/unit/test_dummy.py create mode 100644 tools/license_headers.py create mode 100644 tools/update_docs_assets.py create mode 100644 tools/update_github_labels.py diff --git a/.badgery.yaml b/.badgery.yaml new file mode 100644 index 00000000..99bcdc1c --- /dev/null +++ b/.badgery.yaml @@ -0,0 +1,72 @@ +default_branch: master +develop_branch: develop + +cards: + - group: Tests + type: gh_action + title: Code/package tests (GitHub) + file: test.yml + enabled: true + - group: Tests + type: gh_action + title: Tutorial tests (GitHub) + file: tutorial-tests.yml + enabled: true + + - group: Tests + type: gh_action + title: Package tests (PyPI) + file: pypi-test.yml + enabled: true + + - group: Code Quality + type: codefactor + title: Code quality (CodeFactor) + enabled: true + + - group: Code Quality + type: radon_mi + title: Maintainability index (radon) + report: reports/{branch}/maintainability-index.json + enabled: true + + - group: Code Quality + type: radon_cc + title: Cyclomatic complexity (radon) + report: reports/{branch}/cyclomatic-complexity.json + enabled: true + + - group: Size + type: radon_loc + title: Source/Logical lines of code (radon) + report: reports/{branch}/raw-metrics.json + enabled: true + + - group: Size + type: radon_ff + title: Functions/Files count (radon) + report: reports/{branch}/cyclomatic-complexity.json + enabled: true + + - group: Coverage + type: codecov + title: Unit test coverage (Codecov) + flag: unittests + enabled: true + + - group: Coverage + type: interrogate + title: Docstring coverage (interrogate) + report: reports/{branch}/coverage-docstring.txt + enabled: true + - group: Build & Release + type: gh_action + title: Publishing (PyPI) + workflow: pypi-publish.yml + enabled: true + + - group: Build & Release + type: gh_action + title: Docs build/deployment + workflow: docs.yml + enabled: true diff --git a/.codecov.yml b/.codecov.yml index 9c9abf0b..e17376e8 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,8 +1,8 @@ github_checks: - annotations: true + annotations: true comment: - layout: "reach, diff, flags, files" + layout: 'reach, diff, flags, files' behavior: default - require_changes: false # if true: only post the comment if coverage changes - require_base: no # [yes :: must have a base report to post] - require_head: yes # [yes :: must have a head report to post] + require_changes: false # if true: only post the comment if coverage changes + require_base: no # [yes :: must have a base report to post] + require_head: yes # [yes :: must have a head report to post] diff --git a/.copier-answers.yml b/.copier-answers.yml index 867a00e8..f8fbdb26 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,11 +1,19 @@ -# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY -_commit: 8bdcedc -_src_path: gh:/EasyScience/EasyProjectTemplate -description: A reflectometry python package built on the EasyScience framework. -max_python: '3.13' -min_python: '3.9' -orgname: EasyScience -packagename: easyreflectometry -prettyname: Easy Reflectometry Library -projectname: easyreflectometry -year: 2024 +# WARNING: Do not edit this file manually. +# Any changes will be overwritten by Copier. +_commit: v0.11.2-3-ge3f42a1 +_src_path: gh:easyscience/templates +lib_docs_url: https://easyscience.github.io/reflectometry-lib +lib_doi: 10.5281/zenodo.18163581 +lib_package_name: easyreflectometry +lib_python_max: '3.13' +lib_python_min: '3.11' +lib_repo_name: reflectometry-lib +project_contact_email: support@easyreflectometry.org +project_copyright_years: 2021-2026 +project_extended_description: A software for performing reflectometry calculations + based on a layer model and refining its parameters against reflectometry data +project_name: EasyReflectometry +project_short_description: Reflectometry data analysis +project_shortcut: ER +project_type: lib +template_type: lib diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..997504b4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# SCM syntax highlighting & preventing 3-way merges +pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff diff --git a/.github/actions/download-artifact/action.yml b/.github/actions/download-artifact/action.yml new file mode 100644 index 00000000..d1fff1a0 --- /dev/null +++ b/.github/actions/download-artifact/action.yml @@ -0,0 +1,50 @@ +name: 'Download artifact' +description: 'Wrapper for actions/download-artifact' +inputs: + name: + description: 'Name of the artifact to download' + required: true + + path: + description: 'Destination path' + required: false + default: '.' + + pattern: + description: 'Glob pattern to match artifact names (optional)' + required: false + default: '' + + merge-multiple: + description: 'Merge multiple artifacts into the same directory' + required: false + default: 'false' + + github-token: + description: 'GitHub token for cross-repo download (optional)' + required: false + default: '' + + repository: + description: 'owner/repo for cross-repo download (optional)' + required: false + default: '' + + run-id: + description: 'Workflow run ID for cross-run download (optional)' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Download artifact + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.name }} + path: ${{ inputs.path }} + pattern: ${{ inputs.pattern }} + merge-multiple: ${{ inputs.merge-multiple }} + github-token: ${{ inputs.github-token }} + repository: ${{ inputs.repository }} + run-id: ${{ inputs.run-id }} diff --git a/.github/actions/github-script/action.yml b/.github/actions/github-script/action.yml new file mode 100644 index 00000000..50de89b7 --- /dev/null +++ b/.github/actions/github-script/action.yml @@ -0,0 +1,19 @@ +name: 'GitHub Script' +description: 'Wrapper for actions/github-script' +inputs: + script: + description: 'JavaScript to run' + required: true + + github-token: + description: 'GitHub token (defaults to github.token)' + required: false + default: ${{ github.token }} + +runs: + using: 'composite' + steps: + - uses: actions/github-script@v9 + with: + script: ${{ inputs.script }} + github-token: ${{ inputs.github-token }} diff --git a/.github/actions/setup-easyscience-bot/action.yml b/.github/actions/setup-easyscience-bot/action.yml new file mode 100644 index 00000000..e51eb01a --- /dev/null +++ b/.github/actions/setup-easyscience-bot/action.yml @@ -0,0 +1,40 @@ +name: 'Setup EasyScience bot for pushing' +description: 'Create GitHub App token and configure git identity + origin remote' +inputs: + app-id: + description: 'GitHub App ID' + required: true + private-key: + description: 'GitHub App private key (PEM)' + required: true + repositories: + description: 'Additional repositories to grant access to (newline-separated)' + required: false + default: '' + +outputs: + token: + description: 'Installation access token' + value: ${{ steps.app-token.outputs.token }} + +runs: + using: 'composite' + steps: + - name: Create GitHub App installation token + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ inputs.app-id }} + private-key: ${{ inputs.private-key }} + repositories: ${{ inputs.repositories }} + + - name: Configure git for pushing + shell: bash + run: | + git config user.name "easyscience[bot]" + git config user.email "${{ inputs.app-id }}+easyscience[bot]@users.noreply.github.com" + + - name: Configure origin remote to use the bot token + shell: bash + run: | + git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}.git diff --git a/.github/actions/setup-pixi/action.yml b/.github/actions/setup-pixi/action.yml new file mode 100644 index 00000000..ec7d7ba7 --- /dev/null +++ b/.github/actions/setup-pixi/action.yml @@ -0,0 +1,44 @@ +name: 'Setup Pixi Environment' +description: 'Wrapper for prefix-dev/setup-pixi' +inputs: + environments: + description: 'Pixi environments to setup' + required: false + default: 'default' + activate-environment: + description: 'Environment to activate' + required: false + default: 'default' + run-install: + description: 'Whether to run pixi install' + required: false + default: 'true' + locked: + description: 'Whether to run pixi install --locked' + required: false + default: 'false' + frozen: + description: 'Whether to run pixi install --frozen' + required: false + default: 'true' + cache: + description: 'Whether to use cache' + required: false + default: 'false' + post-cleanup: + description: 'Whether to run post cleanup' + required: false + default: 'false' + +runs: + using: 'composite' + steps: + - uses: prefix-dev/setup-pixi@v0.9.4 + with: + environments: ${{ inputs.environments }} + activate-environment: ${{ inputs.activate-environment }} + run-install: ${{ inputs.run-install }} + locked: ${{ inputs.locked }} + frozen: ${{ inputs.frozen }} + cache: ${{ inputs.cache }} + post-cleanup: ${{ inputs.post-cleanup }} diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml new file mode 100644 index 00000000..fe8e4680 --- /dev/null +++ b/.github/actions/upload-artifact/action.yml @@ -0,0 +1,49 @@ +name: 'Upload artifact' +description: 'Wrapper for actions/upload-artifact' +inputs: + name: + description: 'Artifact name' + required: true + + path: + description: 'File(s)/dir(s)/glob(s) to upload (newline-separated)' + required: true + + include-hidden-files: + description: 'Include hidden files' + required: false + default: 'true' + + if-no-files-found: + description: 'warn | error | ignore' + required: false + default: 'error' + + compression-level: + description: '0-9 (0 = no compression)' + required: false + default: '0' + + retention-days: + description: 'Retention in days (optional)' + required: false + default: '' + + overwrite: + description: 'Overwrite an existing artifact with the same name' + required: false + default: 'false' + +runs: + using: 'composite' + steps: + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: ${{ inputs.name }} + path: ${{ inputs.path }} + include-hidden-files: ${{ inputs.include-hidden-files }} + if-no-files-found: ${{ inputs.if-no-files-found }} + compression-level: ${{ inputs.compression-level }} + retention-days: ${{ inputs.retention-days }} + overwrite: ${{ inputs.overwrite }} diff --git a/.github/actions/upload-codecov/action.yml b/.github/actions/upload-codecov/action.yml new file mode 100644 index 00000000..0cb15d1f --- /dev/null +++ b/.github/actions/upload-codecov/action.yml @@ -0,0 +1,42 @@ +name: 'Upload coverage to Codecov' +description: 'Wrapper for codecov/codecov-action' + +inputs: + name: + description: 'Codecov upload name' + required: true + + flags: + description: 'Codecov flags' + required: false + default: '' + + files: + description: 'Coverage report files' + required: true + + fail_ci_if_error: + description: 'Fail CI if upload fails' + required: false + default: 'true' + + verbose: + description: 'Enable verbose output' + required: false + default: 'true' + + token: + description: 'Codecov token' + required: true + +runs: + using: composite + steps: + - uses: codecov/codecov-action@v6 + with: + name: ${{ inputs.name }} + flags: ${{ inputs.flags }} + files: ${{ inputs.files }} + fail_ci_if_error: ${{ inputs.fail_ci_if_error }} + verbose: ${{ inputs.verbose }} + token: ${{ inputs.token }} diff --git a/.github/configs/pages-deployment.json b/.github/configs/pages-deployment.json new file mode 100644 index 00000000..c0d3fbee --- /dev/null +++ b/.github/configs/pages-deployment.json @@ -0,0 +1,6 @@ +{ + "source": { + "branch": "gh-pages", + "path": "/" + } +} diff --git a/.github/configs/rulesets-develop.json b/.github/configs/rulesets-develop.json new file mode 100644 index 00000000..04489e52 --- /dev/null +++ b/.github/configs/rulesets-develop.json @@ -0,0 +1,37 @@ +{ + "name": "develop branch", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["refs/heads/develop"], + "exclude": [] + } + }, + "bypass_actors": [ + { + "actor_id": 2476259, + "actor_type": "Integration", + "bypass_mode": "always" + } + ], + "rules": [ + { + "type": "non_fast_forward" + }, + { + "type": "deletion" + }, + { + "type": "pull_request", + "parameters": { + "allowed_merge_methods": ["squash"], + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_approving_review_count": 0, + "required_review_thread_resolution": false + } + } + ] +} diff --git a/.github/configs/rulesets-gh-pages.json b/.github/configs/rulesets-gh-pages.json new file mode 100644 index 00000000..ebf38928 --- /dev/null +++ b/.github/configs/rulesets-gh-pages.json @@ -0,0 +1,19 @@ +{ + "name": "gh-pages branch", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["refs/heads/gh-pages"], + "exclude": [] + } + }, + "rules": [ + { + "type": "non_fast_forward" + }, + { + "type": "deletion" + } + ] +} diff --git a/.github/configs/rulesets-master.json b/.github/configs/rulesets-master.json new file mode 100644 index 00000000..f658a5c6 --- /dev/null +++ b/.github/configs/rulesets-master.json @@ -0,0 +1,30 @@ +{ + "name": "master branch", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "rules": [ + { + "type": "non_fast_forward" + }, + { + "type": "deletion" + }, + { + "type": "pull_request", + "parameters": { + "allowed_merge_methods": ["merge"], + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_approving_review_count": 0, + "required_review_thread_resolution": false + } + } + ] +} diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 881b2c53..533cae79 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,32 +2,40 @@ ## Project Overview -EasyReflectometryLib is a reflectometry Python package built on the EasyScience framework. It provides tools for reflectometry analysis and modeling. +EasyReflectometryLib is a reflectometry Python package built on the +EasyScience framework. It provides tools for reflectometry analysis and +modeling. ## Development Environment - **Python Versions**: 3.11, 3.12 -- **Supported Platforms**: Linux (ubuntu-latest), macOS (macos-latest), Windows (windows-latest) +- **Supported Platforms**: Linux (ubuntu-latest), macOS (macos-latest), + Windows (windows-latest) - **Package Manager**: pip - **Build System**: hatchling with setuptools-git-versioning ## Code Style and Formatting ### Ruff Configuration -- Use **Ruff** for linting and formatting (configured in `pyproject.toml`) + +- Use **Ruff** for linting and formatting (configured in + `pyproject.toml`) - Maximum line length: 127 characters - Quote style: single quotes for strings - Import style: force single-line imports - To fix issues automatically: `python -m ruff . --fix` ### Code Quality Standards + - Follow PEP 8 guidelines - Use type hints where appropriate - Write clear, self-documenting code with meaningful variable names - Maintain consistency with existing code patterns in the repository ### Linting Rules + The project uses Ruff with the following rule sets: + - `E9`, `F63`, `F7`, `F82`: Critical flake8 rules - `E`: pycodestyle errors - `F`: Pyflakes @@ -35,6 +43,7 @@ The project uses Ruff with the following rule sets: - `S`: flake8-bandit (security checks) Special notes: + - Asserts are allowed in test files (`*test_*.py`) - Init module imports are ignored - Exclude `docs` directory from linting @@ -42,12 +51,14 @@ Special notes: ## Testing ### Test Framework + - Use **pytest** for all tests - Test coverage should be tracked with **pytest-cov** - Aim for comprehensive test coverage - Tests are located in the `tests/` directory ### Running Tests + ```bash # Install dev dependencies pip install -e '.[dev]' @@ -61,6 +72,7 @@ tox ``` ### Test Guidelines + - Write unit tests for all new functionality - Include tests when fixing bugs to prevent regression - Test files should match the pattern `test_*.py` @@ -77,11 +89,14 @@ tox ## Documentation ### Docstring Style + - Include docstrings for all public modules, classes, and functions -- Use **Sphinx/reStructuredText style** docstrings (`:param`, `:type`, `:return`, `:rtype`) +- Use **Sphinx/reStructuredText style** docstrings (`:param`, `:type`, + `:return`, `:rtype`) - Use clear, concise descriptions - Document parameters, return values, and exceptions - Example format: + ```python """ Brief description of the function. @@ -94,6 +109,7 @@ tox ``` ### Documentation Build + - Documentation is built using Sphinx (version 8.1.3) - Source files are in the `docs/` directory - Use `myst_parser` (MyST parser) for Markdown support @@ -102,6 +118,7 @@ tox ## Dependencies ### Core Dependencies + - easyscience (EasyScience framework) - scipp (Scientific computing) - refnx, refl1d (Reflectometry calculations) @@ -109,6 +126,7 @@ tox - bumps (Optimization) ### Adding New Dependencies + - Only add dependencies when absolutely necessary - Add to appropriate section in `pyproject.toml`: - `dependencies` for core runtime dependencies @@ -119,13 +137,16 @@ tox ## Git and Version Control ### Commit Messages + - Write clear, descriptive commit messages - Use present tense ("Add feature" not "Added feature") - Reference issue numbers when applicable ### Branch Workflow + - Create feature branches from the main branch -- Use descriptive branch names (e.g., `feature/add-new-calculator`, `bugfix/fix-reflection-calculation`) +- Use descriptive branch names (e.g., `feature/add-new-calculator`, + `bugfix/fix-reflection-calculation`) - Keep changes focused and atomic ## Pull Request Guidelines @@ -157,17 +178,24 @@ docs/ # Documentation source ## Best Practices -1. **Minimal Changes**: Make the smallest possible changes to accomplish the task -2. **Don't Break Existing Code**: Maintain backward compatibility unless explicitly required -3. **Test Before Committing**: Always run tests and linting before pushing -4. **Follow Existing Patterns**: Look at similar code in the repository for guidance -5. **Ask When Uncertain**: If unsure about an approach, ask for clarification +1. **Minimal Changes**: Make the smallest possible changes to accomplish + the task +2. **Don't Break Existing Code**: Maintain backward compatibility unless + explicitly required +3. **Test Before Committing**: Always run tests and linting before + pushing +4. **Follow Existing Patterns**: Look at similar code in the repository + for guidance +5. **Ask When Uncertain**: If unsure about an approach, ask for + clarification ## CI/CD Pipeline The project uses GitHub Actions for continuous integration: + - **Code Consistency**: Runs Ruff linting on all pushes and PRs -- **Code Testing**: Runs pytest across multiple Python versions and platforms +- **Code Testing**: Runs pytest across multiple Python versions and + platforms - **Package Testing**: Validates package building and installation - **Coverage**: Uploads test coverage to Codecov @@ -177,5 +205,6 @@ All CI checks must pass before merging PRs. - The project is part of the EasyScience ecosystem - Built on top of established reflectometry libraries (refnx, refl1d) -- Focuses on providing a user-friendly interface for reflectometry analysis +- Focuses on providing a user-friendly interface for reflectometry + analysis - Maintains compatibility with multiple calculator backends diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index cc95caf6..4f0d1964 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -4,30 +4,30 @@ name-template: v$NEXT_PATCH_VERSION 🌈 tag-template: v$NEXT_PATCH_VERSION categories: -- title: 🚀 Features - labels: - - feature - - enhancement -- title: 🐛 Bug Fixes - labels: - - fix - - bugfix - - bug -- title: 🧰 Maintenance - labels: - - chore - - documentation + - title: 🚀 Features + labels: + - feature + - enhancement + - title: 🐛 Bug Fixes + labels: + - fix + - bugfix + - bug + - title: 🧰 Maintenance + labels: + - chore + - documentation change-template: '- $TITLE @$AUTHOR (#$NUMBER)' version-resolver: major: labels: - - major + - major minor: labels: - - minor + - minor patch: labels: - - patch + - patch default: patch template: | ## Changes diff --git a/.github/scripts/backmerge-conflict-issue.js b/.github/scripts/backmerge-conflict-issue.js new file mode 100644 index 00000000..f6bd98b5 --- /dev/null +++ b/.github/scripts/backmerge-conflict-issue.js @@ -0,0 +1,69 @@ +module.exports = async ({ github, context, core }) => { + // Repo context + const owner = context.repo.owner + const repo = context.repo.repo + + // Link to the exact workflow run that detected the conflict + const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}` + + // We use a *stable title* so we can find/reuse the same "conflict tracker" issue + // instead of creating a new issue on every failed run. + const title = 'Backmerge conflict: master → develop' + + // Comment/issue body includes the run URL so maintainers can jump straight to logs. + const body = [ + 'Automatic backmerge failed due to merge conflicts.', + '', + `Workflow run: ${runUrl}`, + '', + 'Manual resolution required.', + ].join('\n') + + // Label applied to the tracker issue (assumed to already exist in the repo). + const label = '[bot] backmerge' + + // Search issues by title across *open and closed* issues. + // Why: if the conflict was resolved previously and the issue was closed, + // we prefer to reopen it and append a new comment instead of creating duplicates. + const q = `repo:${owner}/${repo} is:issue in:title "${title}"` + const search = await github.rest.search.issuesAndPullRequests({ + q, + per_page: 10, + }) + + // Pick the first exact-title match (search can return partial matches). + const existing = search.data.items.find((i) => i.title === title) + + if (existing) { + // If a tracker issue exists, reuse it: + // - reopen it if needed + // - add a comment with the new run URL + if (existing.state === 'closed') { + await github.rest.issues.update({ + owner, + repo, + issue_number: existing.number, + state: 'open', + }) + } + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: existing.number, + body, + }) + + core.notice(`Conflict issue updated: #${existing.number}`) + return + } + + // No tracker issue exists yet -> create the first one. + await github.rest.issues.create({ + owner, + repo, + title, + body, + labels: [label], + }) +} diff --git a/.github/scripts/publish-dashboard.sh b/.github/scripts/publish-dashboard.sh new file mode 100644 index 00000000..dcbd0b32 --- /dev/null +++ b/.github/scripts/publish-dashboard.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +set -euo pipefail + +remote_repository="${DASHBOARD_REMOTE_REPOSITORY:?}" +publish_branch="${DASHBOARD_PUBLISH_BRANCH:-master}" +source_dir="${DASHBOARD_SOURCE_DIR:?}" +token="${DASHBOARD_TOKEN:?}" +git_user_name="${DASHBOARD_GIT_USER_NAME:-easyscience[bot]}" +git_user_email="${DASHBOARD_GIT_USER_EMAIL:?}" +commit_message="${DASHBOARD_COMMIT_MESSAGE:?}" +max_attempts="${DASHBOARD_PUSH_ATTEMPTS:-3}" +delay_seconds="${DASHBOARD_PUSH_DELAY_SECONDS:-15}" + +workspace_dir="$(mktemp -d)" +repo_dir="${workspace_dir}/dashboard" +remote_url="https://x-access-token:${token}@github.com/${remote_repository}.git" + +cleanup() { + rm -rf "${workspace_dir}" +} + +prepare_worktree() { + if [[ ! -d "${repo_dir}/.git" ]]; then + git clone --branch "${publish_branch}" --depth 1 "${remote_url}" "${repo_dir}" + else + git -C "${repo_dir}" fetch origin "${publish_branch}" + git -C "${repo_dir}" checkout "${publish_branch}" + git -C "${repo_dir}" reset --hard "origin/${publish_branch}" + git -C "${repo_dir}" clean -fd + fi + + git -C "${repo_dir}" config user.name "${git_user_name}" + git -C "${repo_dir}" config user.email "${git_user_email}" +} + +sync_publish_dir() { + cp -R "${source_dir}/." "${repo_dir}/" + git -C "${repo_dir}" add . + + if git -C "${repo_dir}" diff --cached --quiet; then + return 1 + fi + + git -C "${repo_dir}" commit -m "${commit_message}" +} + +trap cleanup EXIT + +prepare_worktree + +if ! sync_publish_dir; then + echo "No dashboard changes to publish." + exit 0 +fi + +for ((attempt = 1; attempt <= max_attempts; attempt += 1)); do + if git -C "${repo_dir}" push origin "HEAD:${publish_branch}"; then + echo "Dashboard published on attempt ${attempt}." + exit 0 + fi + + if ((attempt == max_attempts)); then + echo "Dashboard publish failed after ${max_attempts} attempts." >&2 + exit 1 + fi + + echo "Dashboard push attempt ${attempt} failed. Retrying in ${delay_seconds}s." >&2 + sleep "${delay_seconds}" + + prepare_worktree + + if ! sync_publish_dir; then + echo "Dashboard changes already exist in the target repository." + exit 0 + fi +done \ No newline at end of file diff --git a/.github/workflows/backmerge.yml b/.github/workflows/backmerge.yml new file mode 100644 index 00000000..36ce6f54 --- /dev/null +++ b/.github/workflows/backmerge.yml @@ -0,0 +1,109 @@ +# This workflow automatically merges `master` into `develop` whenever a +# new version release with a tag is published. It can also be triggered +# manually via workflow_dispatch for cases where an automatic backmerge +# is needed outside of the standard release process. +# If a merge conflict occurs, the workflow creates an issue to notify +# maintainers for manual resolution. + +name: Backmerge (master → develop) + +on: + release: + types: [published, prereleased] + workflow_dispatch: + +permissions: + contents: write + issues: write + +concurrency: + group: backmerge-master-into-develop + cancel-in-progress: false + +jobs: + backmerge: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout repository (for local actions) + uses: actions/checkout@v6 + + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + repositories: ${{ github.event.repository.name }} + + - name: Checkout repository (with bot token) + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ steps.bot.outputs.token }} + + - name: Configure git identity + run: | + git config user.name "easyscience[bot]" + git config user.email "${{ vars.EASYSCIENCE_APP_ID }}+easyscience[bot]@users.noreply.github.com" + + - name: Set merge message + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + MESSAGE="Backmerge: master into develop (manual) [skip ci]" + else + TAG="${{ github.event.release.tag_name }}" + MESSAGE="Backmerge: master (${TAG}) into develop [skip ci]" + fi + + echo "MESSAGE=$MESSAGE" >> "$GITHUB_ENV" + echo "message=$MESSAGE" >> "$GITHUB_OUTPUT" + echo "📝 Merge message: $MESSAGE" | tee -a "$GITHUB_STEP_SUMMARY" + + - name: Prepare branches + run: | + git fetch origin master develop + git checkout -B develop origin/develop + + - name: Check if develop is already up-to-date + id: up_to_date + run: | + if git merge-base --is-ancestor origin/master develop; then + echo "value=true" >> "$GITHUB_OUTPUT" + echo "ℹ️ Develop is already up-to-date with master" | tee -a "$GITHUB_STEP_SUMMARY" + else + echo "value=false" >> "$GITHUB_OUTPUT" + fi + + - name: Try merge master into develop + id: merge + if: steps.up_to_date.outputs.value == 'false' + continue-on-error: true + run: | + if ! git merge origin/master --no-ff -m "${MESSAGE}"; then + echo "conflict=true" >> "$GITHUB_OUTPUT" + echo "❌ Backmerge conflict detected." | tee -a "$GITHUB_STEP_SUMMARY" + git status --porcelain || true + exit 0 + fi + + echo "conflict=false" >> "$GITHUB_OUTPUT" + echo "✅ Merge commit created." | tee -a "$GITHUB_STEP_SUMMARY" + + - name: Push to develop (if merge succeeded) + if: + steps.up_to_date.outputs.value == 'false' && steps.merge.outputs.conflict == + 'false' + run: | + git push origin develop + echo "🚀 Backmerge successful: master → develop" | tee -a "$GITHUB_STEP_SUMMARY" + + - name: Create issue (if merge failed with conflicts) + if: steps.merge.outputs.conflict == 'true' + uses: ./.github/actions/github-script + with: + github-token: ${{ steps.bot.outputs.token }} + script: | + const run = require('./.github/scripts/backmerge-conflict-issue.js') + await run({ github, context, core }) diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 00000000..21c72b38 --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,84 @@ +# This workflow will delete old workflow runs based on the input +# parameters. +# https://github.com/Mattraks/delete-workflow-runs + +name: Old workflow runs cleanup + +on: + # Run monthly, at 00:00 on the 1st day of month. + schedule: + - cron: '0 0 1 * *' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + inputs: + days: + description: 'Number of days.' + required: true + default: '30' + minimum_runs: + description: 'The minimum runs to keep for each workflow.' + required: true + default: '6' + delete_workflow_pattern: + description: + 'The name or filename of the workflow. if not set then it will target all + workflows.' + required: false + delete_workflow_by_state_pattern: + description: + 'Remove workflow by state: active, deleted, disabled_fork, + disabled_inactivity, disabled_manually' + required: true + default: 'All' + type: choice + options: + - 'All' + - active + - deleted + - disabled_inactivity + - disabled_manually + delete_run_by_conclusion_pattern: + description: + 'Remove workflow by conclusion: action_required, cancelled, failure, skipped, + success' + required: true + default: 'All' + type: choice + options: + - 'All' + - action_required + - cancelled + - failure + - skipped + - success + dry_run: + description: 'Only log actions, do not perform any delete operations (dry run).' + required: false + default: 'false' + type: choice + options: + - 'false' + - 'true' + +jobs: + del-runs: + runs-on: ubuntu-latest + + permissions: + actions: write + + steps: + - name: Delete workflow runs + uses: Mattraks/delete-workflow-runs@v2 + with: + token: ${{ github.token }} + repository: ${{ github.repository }} + retain_days: ${{ github.event.inputs.days }} + keep_minimum_runs: ${{ github.event.inputs.minimum_runs }} + delete_workflow_pattern: ${{ github.event.inputs.delete_workflow_pattern }} + delete_workflow_by_state_pattern: + ${{ github.event.inputs.delete_workflow_by_state_pattern }} + delete_run_by_conclusion_pattern: + ${{ github.event.inputs.delete_run_by_conclusion_pattern }} + dry_run: ${{ github.event.inputs.dry_run }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b388f884..238dc354 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,11 +1,11 @@ -name: "CodeQL" +name: 'CodeQL' on: push: - branches: [ master, pre-release, develop ] + branches: [master, pre-release, develop] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [master] schedule: - cron: '0 16 * * 5' @@ -24,43 +24,43 @@ jobs: # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 + - name: Checkout repository + uses: actions/checkout@v4 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@master + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@master - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 \ No newline at end of file + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..d96e5b87 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,94 @@ +name: Coverage checks + +on: + # Trigger the workflow on push to develop + push: + branches: + - develop + # Trigger the workflow on pull request + pull_request: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Need permissions to trigger the dashboard build workflow +permissions: + actions: write + contents: read + +# Allow only one concurrent workflow per PR or branch ref. +# Cancel in-progress runs only for pull requests, but let branch push runs finish. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + +jobs: + # Job 1: Run docstring coverage + docstring-coverage: + runs-on: ubuntu-latest + + steps: + - name: Check-out repository + uses: actions/checkout@v6 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + - name: Run docstring coverage + run: pixi run docstring-coverage + + # Job 2: Run unit tests with coverage and upload to Codecov + unit-tests-coverage: + runs-on: ubuntu-latest + + steps: + - name: Check-out repository + uses: actions/checkout@v6 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + - name: Run unit tests with coverage + run: pixi run unit-tests-coverage --cov-report=xml:coverage-unit.xml + + - name: Upload unit tests coverage to Codecov + if: ${{ !cancelled() }} + uses: ./.github/actions/upload-codecov + with: + name: unit-tests-job + flags: unittests + files: ./coverage-unit.xml + token: ${{ secrets.CODECOV_TOKEN }} + + # Job 2: Run integration tests with coverage and upload to Codecov + integration-tests-coverage: + runs-on: ubuntu-latest + + steps: + - name: Check-out repository + uses: actions/checkout@v6 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + - name: Run integration tests with coverage + run: + pixi run integration-tests-coverage --cov-report=xml:coverage-integration.xml + + - name: Upload integration tests coverage to Codecov + if: ${{ !cancelled() }} + uses: ./.github/actions/upload-codecov + with: + name: integration-tests-job + flags: integration + files: ./coverage-integration.xml + token: ${{ secrets.CODECOV_TOKEN }} + + # Job 4: Build and publish dashboard (reusable workflow) + run-reusable-workflows: + needs: [docstring-coverage, unit-tests-coverage, integration-tests-coverage] # depend on the previous jobs + uses: ./.github/workflows/dashboard.yml + secrets: inherit diff --git a/.github/workflows/dashboard.yml b/.github/workflows/dashboard.yml new file mode 100644 index 00000000..9d1f2b0b --- /dev/null +++ b/.github/workflows/dashboard.yml @@ -0,0 +1,111 @@ +name: Dashboard build and publish + +on: + workflow_dispatch: + workflow_call: + +permissions: + contents: read + +concurrency: + group: dashboard-publish-${{ github.repository }} + cancel-in-progress: false + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + DEVELOP_BRANCH: develop + REPO_OWNER: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + +jobs: + dashboard: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + - name: Install badgery + shell: bash + run: pixi add --pypi --git https://github.com/enhantica/badgery badgery + + - name: Run docstring coverage and code complexity/maintainability checks + run: | + for BRANCH in $DEFAULT_BRANCH $DEVELOP_BRANCH $CI_BRANCH; do + echo + echo "🔹🔸🔹🔸🔹 Processing branch $BRANCH 🔹🔸🔹🔸🔹" + if [ -d "../$BRANCH" ]; then + echo "Branch $BRANCH already processed, skipping" + continue + fi + + git worktree add ../$BRANCH origin/$BRANCH + mkdir -p reports/$BRANCH + + echo "Docstring coverage for branch $BRANCH" + pixi run interrogate -c pyproject.toml --fail-under=0 ../$BRANCH/src > reports/$BRANCH/coverage-docstring.txt + + echo "Cyclomatic complexity for branch $BRANCH" + pixi run radon cc -s -j ../$BRANCH/src > reports/$BRANCH/cyclomatic-complexity.json + + echo "Maintainability index for branch $BRANCH" + pixi run radon mi -j ../$BRANCH/src > reports/$BRANCH/maintainability-index.json + + echo "Raw metrics for branch $BRANCH" + pixi run radon raw -s -j ../$BRANCH/src > reports/$BRANCH/raw-metrics.json + done + + - name: Generate dashboard HTML + run: > + pixi run python -m badgery --config .badgery.yaml --repo ${{ github.repository + }} --branch ${{ env.CI_BRANCH }} --output index.html + + - name: Prepare publish directory + run: | + mkdir -p _dashboard_publish/${{ env.REPO_NAME }}/${{ env.CI_BRANCH }} + cp index.html _dashboard_publish/${{ env.REPO_NAME }}/${{ env.CI_BRANCH }} + + # Create GitHub App token for pushing to external dashboard repo. + # The 'repositories' parameter is required to grant access to repos + # other than the one where the workflow is running. + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + repositories: | + ${{ github.event.repository.name }} + dashboard + + # Push to external dashboard repository with retry logic. + # Retry is needed to handle transient GitHub API/authentication issues + # that occasionally cause 403 errors when multiple workflows push concurrently. + # Uses personal_token (not github_token) as GITHUB_TOKEN cannot access external repos. + - name: + Push to ${{ env.REPO_OWNER }}/dashboard/${{ env.REPO_NAME }}/${{ env.CI_BRANCH + }} + shell: bash + env: + DASHBOARD_COMMIT_MESSAGE: ${{ env.CI_BRANCH }} + DASHBOARD_GIT_USER_EMAIL: + ${{ vars.EASYSCIENCE_APP_ID }}+easyscience[bot]@users.noreply.github.com + DASHBOARD_PUSH_ATTEMPTS: '3' + DASHBOARD_PUSH_DELAY_SECONDS: '15' + DASHBOARD_PUBLISH_BRANCH: master + DASHBOARD_REMOTE_REPOSITORY: ${{ env.REPO_OWNER }}/dashboard + DASHBOARD_SOURCE_DIR: ./_dashboard_publish + DASHBOARD_TOKEN: ${{ steps.bot.outputs.token }} + run: bash ./.github/scripts/publish-dashboard.sh + + - name: Add dashboard link to summary + run: | + URL="https://${{ env.REPO_OWNER }}.github.io/dashboard/${{ env.REPO_NAME }}/${{ env.CI_BRANCH }}" + echo "Dashboard link: [$URL]($URL)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..d8622c29 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,188 @@ +# This workflow builds and deploys documentation for the project. +# +# Overview: +# - Converts tutorial Python scripts to Jupyter notebooks and executes them. +# - Builds the documentation site using MkDocs with the Material theme. +# - Uploads the built site as an artifact for local inspection. +# - Deploys versioned documentation to the gh-pages branch using Mike: +# - For release tags (v*): deploys to a versioned folder (e.g., /0.9.1/) and updates /latest/. +# - For branches: deploys to /dev/. +# +# The action summary page will contain a link to the built artifact for downloading +# and inspecting, as well as a link to the deployed documentation site. + +name: Docs build and deployment + +on: + # Trigger the workflow on push + push: + # Selected branches + branches: [develop] # master and main are already verified in PR + # Runs on creating a new tag starting with 'v', e.g. 'v1.0.3' + tags: ['v*'] + # Trigger the workflow on pull request + pull_request: + # Selected branches + branches: [master, main, develop] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# - Non-tagged pushes and pull requests all use `docs-dev` group, so +# they cancel each other. +# - Tagged pushes use their own group like docs-v1.2.3, so they do not +# cancel non-tagged runs, and non-tagged runs do not cancel them. +concurrency: + group: >- + ${{ startsWith(github.ref, 'refs/tags/v') + && format('docs-{0}', github.ref_name) + || 'docs-dev' }} + cancel-in-progress: true + +# Set the environment variables to be used in all jobs defined in this workflow +env: + # CI_BRANCH - the branch name (used in mkdocs.yml) + # For PRs: github.head_ref is the source branch + # For pushes: github.ref_name is the branch + # For tags: github.ref_name is the tag name + # GITHUB_REPOSITORY - the repository name (used in mkdocs.yml) + # NOTEBOOKS_DIR - the directory containing the Jupyter notebooks (used in mkdocs.yml) + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + DEVELOP_BRANCH: develop + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + IS_RELEASE_TAG: ${{ startsWith(github.ref, 'refs/tags/v') }} + GITHUB_REPOSITORY: ${{ github.repository }} + NOTEBOOKS_DIR: tutorials + +jobs: + # Single job that builds and deploys documentation. + # Uses macOS runner for consistent Plotly chart rendering. + build-deploy-docs: + runs-on: ubuntu-latest # macos-latest + + permissions: + contents: write # Required for pushing to the gh-pages branch + + steps: + # Setting DOCS_VERSION to be used in mkdocs.yml, and then in the + # main.html template. It defines the versioned docs subfolder name + # in the gh-pages branch. If it's a release tag, use the version + # number without the 'v' prefix, otherwise use 'dev'. + # Setting RELEASE_VERSION to be used in mkdocs.yml to show + # the latest release version in the index.md file. If it's a + # release tag, use the tag name, otherwise use the branch name + # for development builds. + - name: Set extra env variables + shell: bash + run: | + if [[ "${IS_RELEASE_TAG}" == "true" ]]; then + RELEASE_VERSION="${GITHUB_REF_NAME}" + DOCS_VERSION="${RELEASE_VERSION#v}" + else + RELEASE_VERSION="${CI_BRANCH}" + DOCS_VERSION="dev" + fi + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_ENV" + echo "DOCS_VERSION=${DOCS_VERSION}" >> "$GITHUB_ENV" + + # Check out the repository source code. + # Note: The gh-pages branch is fetched separately later for mike deployment. + - name: Checkout repository + uses: actions/checkout@v6 + + # Activate dark mode to create documentation with Plotly charts in dark mode + # Need a better solution to automatically switch the chart colour theme based on the mkdocs material switcher + # Something similar to mkdocs_plotly_plugin https://haoda-li.github.io/mkdocs-plotly-plugin/, + # but for generating documentation from notepads + #- name: Activate dark mode + # run: | + # brew install dark-mode + # dark-mode status + # dark-mode on + # dark-mode status + + # Set up the pixi package manager and install dependencies from pixi.toml. + # Uses frozen lockfile to ensure reproducible builds. + - name: Set up pixi + uses: ./.github/actions/setup-pixi + # Pre-import the main package to exclude info messages from the docs + # E.g., Matplotlib may print messages to stdout/stderr when first + # imported. This step allows to avoid "Matplotlib is building the font + # cache" messages during notebook execution. + - name: Pre-build site step + run: pixi run python -c "import easyreflectometry" + + # Prepare the Jupyter notebooks for documentation (strip output, etc.). + - name: Prepare notebooks + run: pixi run notebook-prepare + + # Execute all Jupyter notebooks to generate output cells (plots, tables, etc.). + # Uses multiple cores for parallel execution to speed up the process. + - name: Run notebooks + # if: false # Temporarily disabled to speed up the docs build + run: pixi run notebook-exec + + # Build the static files for the documentation site for local inspection + # Input: docs/ directory containing the Markdown files + # Output: site/ directory containing the generated HTML files + - name: Build site for local check + run: pixi run docs-build-local + + # Upload the static files from the site/ directory to be used for + # local check + - name: Upload built site as artifact + uses: ./.github/actions/upload-artifact + with: + name: site-local_easyreflectometry-lib-${{ env.RELEASE_VERSION }} + path: docs/site/ + + # Create GitHub App token for pushing to gh-pages as easyscience[bot]. + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + + # Configure git identity and remote URL so mike pushes as easyscience[bot]. + - name: Configure git for pushing + run: | + set -euo pipefail + git config user.name "easyscience[bot]" + git config user.email "${{ vars.EASYSCIENCE_APP_ID }}+easyscience[bot]@users.noreply.github.com" + git remote set-url origin "https://x-access-token:${{ steps.bot.outputs.token }}@github.com/${{ github.repository }}.git" + + # Fetch the gh-pages branch to ensure mike has the latest remote state. + # This is required because the checkout step only fetches the source branch, + # not the gh-pages branch that mike needs to update. + - name: Fetch gh-pages branch + run: | + git fetch origin gh-pages:gh-pages 2>/dev/null || true + + # Deploy versioned documentation using mike (MkDocs plugin for versioning). + # - For release tags (v*): deploys to versioned folder (e.g., /0.9.1/) and aliases to /latest/. + # - For branches: deploys to /dev/. + # The "${RELEASE_VERSION#v}" syntax strips the 'v' prefix (v0.9.1 -> 0.9.1). + # Also sets 'latest' as the default version for the version selector. + - name: Rebuild and deploy docs with mike + run: | + # Exit on error (-e), undefined vars (-u), and pipeline failures (pipefail) + set -euo pipefail + + REPO_NAME="${{ github.event.repository.name }}" + BASE_URL="https://easyscience.github.io/${REPO_NAME}" + + # Deploy the release version and update the "latest" alias + if [[ "${IS_RELEASE_TAG}" == "true" ]]; then + pixi run docs-deploy-pre "${RELEASE_VERSION#v}" latest + pixi run docs-set-default-pre latest + DEPLOYMENT_URL="${BASE_URL}/latest" + + # Deploy/update the "dev" alias (or whatever your convention is) + else + pixi run docs-deploy-pre dev + DEPLOYMENT_URL="${BASE_URL}/dev" + + fi + + # Add links to the action summary page for easy access + echo "🔗 deployment url [${DEPLOYMENT_URL}](${DEPLOYMENT_URL})" >> "${GITHUB_STEP_SUMMARY}" diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index f447b491..b3477bb6 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -29,40 +29,40 @@ jobs: # Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: - contents: read # to clone the repository - pages: write # to deploy to Pages - id-token: write # to verify the deployment originates from an appropriate source + contents: read # to clone the repository + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source steps: - - name: Checkout - uses: actions/checkout@master - with: - fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - - name: Upgrade pip - run: | - python -m pip install --upgrade pip - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.12 - - name: Install Pandoc, repo and dependencies - run: | - sudo apt install pandoc - sudo apt install libcairo2-dev - pip install sphinx==8.1.3 - pip install . '.[dev,docs]' + - name: Checkout + uses: actions/checkout@master + with: + fetch-depth: 0 # otherwise, you will failed to push refs to dest repo + - name: Upgrade pip + run: | + python -m pip install --upgrade pip + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + - name: Install Pandoc, repo and dependencies + run: | + sudo apt install pandoc + sudo apt install libcairo2-dev + pip install sphinx==8.1.3 + pip install . '.[dev,docs]' - - name: Install Jupyter kernel - run: | - python -m ipykernel install --user --name=python3 + - name: Install Jupyter kernel + run: | + python -m ipykernel install --user --name=python3 - - name: Build and Commit - uses: sphinx-notes/pages@v3 - with: - sphinx_version: 8.1.3 - documentation_path: docs/src - - name: Push changes - uses: ad-m/github-push-action@master - continue-on-error: true - with: - branch: gh-pages \ No newline at end of file + - name: Build and Commit + uses: sphinx-notes/pages@v3 + with: + sphinx_version: 8.1.3 + documentation_path: docs/src + - name: Push changes + uses: ad-m/github-push-action@master + continue-on-error: true + with: + branch: gh-pages diff --git a/.github/workflows/issues-labels.yml b/.github/workflows/issues-labels.yml new file mode 100644 index 00000000..56ab5b19 --- /dev/null +++ b/.github/workflows/issues-labels.yml @@ -0,0 +1,149 @@ +# Verifies if the current issue has at least one real `[scope]` label and one +# real `[priority]` label. If either is missing, the workflow adds a reminder +# label with a warning emoji. + +name: Issue labels check + +on: + issues: + types: [opened, labeled, unlabeled] + +permissions: + issues: write + +jobs: + check-labels: + runs-on: ubuntu-latest + + concurrency: + group: issue-labels-${{ github.event.issue.number }} + cancel-in-progress: true + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Sync missing-label reminders + uses: ./.github/actions/github-script + with: + script: | + const fs = require('fs'); + + const issueNumber = context.issue.number; + const action = context.payload.action; + const changedLabel = context.payload.label?.name ?? null; + const labels = context.payload.issue.labels.map(({ name }) => name); + const requirements = [ + { + prefix: '[scope] ', + reminder: '[scope] ⚠️ label needed', + }, + { + prefix: '[priority] ', + reminder: '[priority] ⚠️ label needed', + }, + ]; + + const labelsToAdd = []; + const labelsToRemove = []; + const evaluations = []; + + console.log(`::group::Issue label check for #${issueNumber}`); + console.log(`Event action: ${action}`); + if (changedLabel) { + console.log(`Event label: ${changedLabel}`); + } + console.log( + `Current labels: ${labels.length > 0 ? labels.join(', ') : '(none)'}`, + ); + + for (const { prefix, reminder } of requirements) { + const matchingRealLabels = labels.filter( + (name) => name.startsWith(prefix) && name !== reminder, + ); + const hasRealLabel = matchingRealLabels.length > 0; + const hasReminderLabel = labels.includes(reminder); + + evaluations.push({ + prefix, + reminder, + matchingRealLabels, + hasReminderLabel, + }); + + if (hasRealLabel && hasReminderLabel) { + labelsToRemove.push(reminder); + } else if (!hasRealLabel && !hasReminderLabel) { + labelsToAdd.push(reminder); + } + } + + for (const evaluation of evaluations) { + if (evaluation.matchingRealLabels.length > 0) { + console.log( + `Found required ${evaluation.prefix}label(s): ${evaluation.matchingRealLabels.join(', ')}`, + ); + } else { + console.log(`Missing required ${evaluation.prefix}label.`); + } + + if (evaluation.hasReminderLabel) { + console.log(`Reminder label already present: ${evaluation.reminder}`); + } + } + + if (labelsToAdd.length > 0) { + console.log(`Adding reminder labels: ${labelsToAdd.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labelsToAdd, + }); + } + + for (const name of labelsToRemove) { + console.log(`Removing reminder label: ${name}`); + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name, + }); + } + + if (labelsToAdd.length === 0 && labelsToRemove.length === 0) { + console.log('No label changes required.'); + } + + console.log('::endgroup::'); + + if (process.env.GITHUB_STEP_SUMMARY) { + const summaryLines = [ + '### Issue Label Check', + `- Issue: #${issueNumber}`, + `- Event: ${action}`, + `- Trigger label: ${changedLabel ?? '(none)'}`, + `- Current labels: ${labels.length > 0 ? labels.join(', ') : '(none)'}`, + '', + '#### Requirement status', + ...evaluations.map((evaluation) => { + const status = + evaluation.matchingRealLabels.length > 0 + ? `found ${evaluation.matchingRealLabels.join(', ')}` + : 'missing'; + const reminder = evaluation.hasReminderLabel + ? `reminder present: ${evaluation.reminder}` + : `reminder absent: ${evaluation.reminder}`; + return `- ${evaluation.prefix}: ${status}; ${reminder}`; + }), + '', + `- Labels to add: ${labelsToAdd.length > 0 ? labelsToAdd.join(', ') : '(none)'}`, + `- Labels to remove: ${labelsToRemove.length > 0 ? labelsToRemove.join(', ') : '(none)'}`, + ]; + + fs.appendFileSync( + process.env.GITHUB_STEP_SUMMARY, + `${summaryLines.join('\n')}\n`, + ); + } diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml new file mode 100644 index 00000000..16dd95c9 --- /dev/null +++ b/.github/workflows/lint-format.yml @@ -0,0 +1,124 @@ +# The workflow checks +# - the validity of pyproject.toml, +# - the presence and correctness of SPDX license headers, +# - linting and formatting of Python code, +# - linting and formatting of docstrings in Python code, +# - formatting of non-Python files (like markdown and toml). +# - linting of Python code in Jupyter notebooks (for library template). +# +# A summary of the checks is added to the GitHub Actions summary. + +name: Lint and format checks + +on: + # Trigger the workflow on push + push: + branches-ignore: [master, main] # Already verified in PR + # Do not run this workflow on creating a new tag starting with + # 'v', e.g. 'v1.0.3' (see publish-pypi.yml) + tags-ignore: ['v*'] + # Trigger the workflow on pull request + pull_request: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Allow only one concurrent workflow, skipping runs queued between the run +# in-progress and latest queued. And cancel in-progress runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + +jobs: + lint-format: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + - name: Run post-install developer steps + run: pixi run post-install + + - name: Check validity of pyproject.toml + id: pyproject + continue-on-error: true + shell: bash + run: pixi run pyproject-check + + - name: Check SPDX license headers + id: license_headers + continue-on-error: true + shell: bash + run: pixi run license-check + + - name: Check linting of Python code + id: py_lint + continue-on-error: true + shell: bash + run: pixi run py-lint-check + + - name: Check formatting of Python code + id: py_format + continue-on-error: true + shell: bash + run: pixi run py-format-check + + - name: Check linting of docstrings in Python code + id: docstring_lint + continue-on-error: true + shell: bash + run: pixi run docstring-lint-check + + - name: Check formatting of non-Python files (md, toml, etc.) + id: nonpy_format + continue-on-error: true + shell: bash + run: pixi run nonpy-format-check + + - name: Check linting of Python code in Jupyter notebooks (ipynb) + id: notebook_lint + continue-on-error: true + shell: bash + run: pixi run notebook-lint-check + + # Add summary + - name: Add quality checks summary + if: always() + shell: bash + run: | + { + echo "## 🧪 Checks Summary" + echo "" + echo "| Check | Status |" + echo "|-------|--------|" + echo "| pyproject.toml | ${{ steps.pyproject.outcome == 'success' && '✅' || '❌' }} |" + echo "| license headers | ${{ steps.license_headers.outcome == 'success' && '✅' || '❌' }} |" + echo "| py lint | ${{ steps.py_lint.outcome == 'success' && '✅' || '❌' }} |" + echo "| py format | ${{ steps.py_format.outcome == 'success' && '✅' || '❌' }} |" + echo "| docstring lint | ${{ steps.docstring_lint.outcome == 'success' && '✅' || '❌' }} |" + echo "| nonpy format | ${{ steps.nonpy_format.outcome == 'success' && '✅' || '❌' }} |" + echo "| notebooks lint | ${{ steps.notebook_lint.outcome == 'success' && '✅' || '❌' }} |" + } >> "$GITHUB_STEP_SUMMARY" + + # Fail job if any check failed + - name: Fail job if any check failed + if: | + steps.pyproject.outcome == 'failure' + || steps.license_headers.outcome == 'failure' + || steps.py_lint.outcome == 'failure' + || steps.py_format.outcome == 'failure' + || steps.docstring_lint.outcome == 'failure' + || steps.nonpy_format.outcome == 'failure' + || steps.notebook_lint.outcome == 'failure' + shell: bash + run: exit 1 diff --git a/.github/workflows/ossar-analysis.yml b/.github/workflows/ossar-analysis.yml index a2c77306..94859565 100644 --- a/.github/workflows/ossar-analysis.yml +++ b/.github/workflows/ossar-analysis.yml @@ -3,7 +3,6 @@ # # For more information see: https://github.com/github/ossar-action - name: OSSAR on: @@ -17,35 +16,34 @@ jobs: steps: # Checkout your code repository to scan - - name: Checkout repository - uses: actions/checkout@v4 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} - - # Ensure a compatible version of dotnet is installed. - # The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201. - # A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action. - # Remote agents already have a compatible version of dotnet installed and this step may be skipped. - # For local agents, ensure dotnet version 3.1.201 or later is installed by including this action: - # - name: Install .NET - # uses: actions/setup-dotnet@v1 - # with: - # dotnet-version: '3.1.x' - + - name: Checkout repository + uses: actions/checkout@v4 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Ensure a compatible version of dotnet is installed. + # The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201. + # A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action. + # Remote agents already have a compatible version of dotnet installed and this step may be skipped. + # For local agents, ensure dotnet version 3.1.201 or later is installed by including this action: + # - name: Install .NET + # uses: actions/setup-dotnet@v1 + # with: + # dotnet-version: '3.1.x' # Run open source static analysis tools - - name: Run OSSAR - uses: github/ossar-action@v2.0.0 - id: ossar - - # Upload results to the Security tab - - name: Upload OSSAR results - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ${{ steps.ossar.outputs.sarifFile }} \ No newline at end of file + - name: Run OSSAR + uses: github/ossar-action@v2.0.0 + id: ossar + + # Upload results to the Security tab + - name: Upload OSSAR results + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ${{ steps.ossar.outputs.sarifFile }} diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml new file mode 100644 index 00000000..25710633 --- /dev/null +++ b/.github/workflows/pr-labels.yml @@ -0,0 +1,63 @@ +# Verifies if a pull request has at least one label from a set of valid +# labels before it can be merged. +# +# NOTE: +# This workflow may be triggered twice in quick succession when a PR is +# created: +# 1) `opened` — when the pull request is initially created +# 2) `labeled` — if labels are added immediately after creation +# (e.g. by manual labeling, another workflow, or GitHub App). +# +# These are separate GitHub events, so two workflow runs can be started. +# The `concurrency` configuration below ensures that only the latest run +# for the same PR remains active, canceling any previous in-progress +# run. + +name: PR labels check + +on: + pull_request_target: + types: [opened, labeled, unlabeled, synchronize] + +concurrency: + group: pr-labels-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + pull-requests: read + +jobs: + check-labels: + runs-on: ubuntu-latest + + steps: + - name: Check for valid labels + run: | + PR_LABELS=$(echo '${{ toJson(github.event.pull_request.labels.*.name) }}' | jq -r '.[]') + + echo "Current PR labels: $PR_LABELS" + VALID_LABELS=( + "[bot] release" + "[scope] bug" + "[scope] documentation" + "[scope] enhancement" + "[scope] maintenance" + "[scope] significant" + ) + + found=false + for label in "${VALID_LABELS[@]}"; do + if echo "$PR_LABELS" | grep -Fxq "$label"; then + echo "✅ Found valid label: $label" + found=true + break + fi + done + + if [ "$found" = false ]; then + echo "ERROR: PR must have at least one of the following labels:" + for label in "${VALID_LABELS[@]}"; do + echo " - $label" + done + exit 1 + fi diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml new file mode 100644 index 00000000..0b431717 --- /dev/null +++ b/.github/workflows/pypi-publish.yml @@ -0,0 +1,46 @@ +# Builds a Python package and publish it to PyPI when a new tag is +# created. + +name: PyPI publishing + +on: + # Runs on creating a new tag starting with 'v', e.g. 'v1.0.3' + push: + tags: ['v*'] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + pypi-publish: + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + + steps: + - name: Check-out repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 # full history with tags to get the version number by versioningit + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + # Build the Python package (to dist/ folder) + - name: Create Python package + run: pixi run default-build + + # Publish the package to PyPI (from dist/ folder) + # Instead of publishing with personal access token, we use + # GitHub Actions OIDC to get a short-lived token from PyPI. + # New publisher must be previously configured in PyPI at + # https://pypi.org/manage/project/easyreflectometry/settings/publishing/ + # Use the following data: + # Owner: easyscience + # Repository name: reflectometry-lib + # Workflow name: pypi-publish.yml + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: 'dist' diff --git a/.github/workflows/pypi-test.yml b/.github/workflows/pypi-test.yml new file mode 100644 index 00000000..2f4f58ac --- /dev/null +++ b/.github/workflows/pypi-test.yml @@ -0,0 +1,80 @@ +name: PyPI package tests + +on: + # Run daily, at 00:00. + schedule: + - cron: '0 0 * * *' + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Allow only one concurrent workflow, skipping runs queued between the run +# in-progress and latest queued. And cancel in-progress runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + +jobs: + # Job 1: Test installation from PyPI on multiple OS + pypi-package-tests: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + with: + environments: '' + activate-environment: '' + run-install: false + frozen: false + + - name: Init pixi project + run: pixi init easyreflectometry + + - name: Set the minimum system requirements + working-directory: easyreflectometry + run: pixi project system-requirements add macos 14.0 + + - name: Add Python 3.13 from Conda + working-directory: easyreflectometry + run: pixi add "python=3.13" + + - name: Add other Conda dependencies + working-directory: easyreflectometry + run: pixi add gsl + + - name: Add easyreflectometry (with dev dependencies) from PyPI + working-directory: easyreflectometry + run: pixi add --pypi "easyreflectometry[dev]" + + - name: Run unit tests to verify the installation + working-directory: easyreflectometry + run: pixi run python -m pytest ../tests/unit/ --color=yes -v + + - name: Run functional tests to verify the installation + working-directory: easyreflectometry + run: pixi run python -m pytest ../tests/functional/ --color=yes -v + + - name: Run integration tests to verify the installation + working-directory: easyreflectometry + run: pixi run python -m pytest ../tests/integration/ --color=yes -n auto + + # Job 2: Build and publish dashboard (reusable workflow) + run-reusable-workflows: + needs: pypi-package-tests # depend on previous job + uses: ./.github/workflows/dashboard.yml + secrets: inherit diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 1d33313f..ea40ee70 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -1,4 +1,4 @@ -# This workflow will for a variety of Python versions +# This workflow will for a variety of Python versions # - install the code base # - lint the code base # - test the code base @@ -21,8 +21,8 @@ jobs: - name: Suggestion to fix issues if: ${{ failure() }} run: | - echo "::notice::In project root run 'python.exe -m ruff . --fix' and commit changes to fix issues." - exit 1 + echo "::notice::In project root run 'python.exe -m ruff . --fix' and commit changes to fix issues." + exit 1 Code_Testing: strategy: @@ -35,56 +35,54 @@ jobs: if: "!contains(github.event.head_commit.message, '[ci skip]')" steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Upgrade pip - run: | - python -m pip install --upgrade pip + - uses: actions/checkout@v4 - - name: Install dependencies - run: pip install -e '.[dev]' + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} - - name: Test with pytest and coverage - run: | - pip install pytest pytest-cov - pytest --cov=src/easyreflectometry tests --cov-branch --cov-report=xml:coverage-unit.xml + - name: Upgrade pip + run: | + python -m pip install --upgrade pip - - name: Upload coverage reports to Codecov - # only on ubuntu to avoid multiple uploads - if: runner.os == 'Linux' - uses: codecov/codecov-action@v5 - with: - name: unit-tests-job - flags: unittests - files: ./coverage-unit.xml - fail_ci_if_error: false - verbose: true - token: ${{ secrets.CODECOV_TOKEN }} - slug: EasyScience/EasyReflectometryLib + - name: Install dependencies + run: pip install -e '.[dev]' + - name: Test with pytest and coverage + run: | + pip install pytest pytest-cov + pytest --cov=src/easyreflectometry tests --cov-branch --cov-report=xml:coverage-unit.xml + + - name: Upload coverage reports to Codecov + # only on ubuntu to avoid multiple uploads + if: runner.os == 'Linux' + uses: codecov/codecov-action@v5 + with: + name: unit-tests-job + flags: unittests + files: ./coverage-unit.xml + fail_ci_if_error: false + verbose: true + token: ${{ secrets.CODECOV_TOKEN }} + slug: EasyScience/EasyReflectometryLib Package_Testing: - runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '[ci skip]')" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: 3.11 + - uses: actions/setup-python@v5 + with: + python-version: 3.11 - - name: Install dependencies and build - run: | - pip install -e '.[dev]' - python -m build + - name: Install dependencies and build + run: | + pip install -e '.[dev]' + python -m build - - name: Check Build - run: | - cd ./dist - pytest ../ + - name: Check Build + run: | + cd ./dist + pytest ../ diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 3a4d1036..c0717847 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -9,34 +9,34 @@ name: Create Python Package on: push: - branches: [ master, pre-release ] + branches: [master, pre-release] pull_request: - branches: [ master, pre-release ] + branches: [master, pre-release] jobs: build: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.11','3.12','3.13'] + python-version: ['3.11', '3.12', '3.13'] if: "!contains(github.event.head_commit.message, '[ci skip]')" steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies and build - run: | - pip install -e '.[dev]' - python -m build - - name: Test with pytest - run: | - cd ./dist - pytest ../ - - uses: actions/upload-artifact@v4 - with: - name: EasyReflectometrys - Python ${{ matrix.python-version }} - path: ${{ github.workspace }}/dist/* - overwrite: true \ No newline at end of file + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies and build + run: | + pip install -e '.[dev]' + python -m build + - name: Test with pytest + run: | + cd ./dist + pytest ../ + - uses: actions/upload-artifact@v4 + with: + name: EasyReflectometrys - Python ${{ matrix.python-version }} + path: ${{ github.workspace }}/dist/* + overwrite: true diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index dce167b2..38133fd1 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -4,7 +4,6 @@ # # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - name: Publish Python Package # Controls when the workflow will run @@ -19,24 +18,23 @@ on: jobs: deploy: - runs-on: ubuntu-latest permissions: id-token: write - + steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: 3.12 + - uses: actions/setup-python@v5 + with: + python-version: 3.12 - - name: Install dependencies and build - run: | - pip install -e '.[dev]' - python -m build + - name: Install dependencies and build + run: | + pip install -e '.[dev]' + python -m build - - name: Publish distribution 📦 to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - #with: - # password: ${{ secrets.PYPI_PASSWORD }} + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + #with: + # password: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/release-drafter-verify-pr-labels.yml b/.github/workflows/release-drafter-verify-pr-labels.yml index b024955b..3a8dbb4b 100644 --- a/.github/workflows/release-drafter-verify-pr-labels.yml +++ b/.github/workflows/release-drafter-verify-pr-labels.yml @@ -5,7 +5,6 @@ # # For more information see: https://github.com/marketplace/actions/release-drafter - name: Verify PR labels on: pull_request_target: @@ -16,11 +15,12 @@ jobs: runs-on: ubuntu-latest name: Verify that the PR has a valid label steps: - - name: Verify PR label action - uses: jesusvasquez333/verify-pr-label-action@v1.4.0 - id: verify-pr-label - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - valid-labels: chore, fix, bugfix, bug, enhancement, feature, dependencies, documentation - pull-request-number: ${{ github.event.pull_request.number }} - disable-reviews: false + - name: Verify PR label action + uses: jesusvasquez333/verify-pr-label-action@v1.4.0 + id: verify-pr-label + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + valid-labels: + chore, fix, bugfix, bug, enhancement, feature, dependencies, documentation + pull-request-number: ${{ github.event.pull_request.number }} + disable-reviews: false diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 31308447..51cd0d22 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -4,7 +4,6 @@ # Uses the release-drafter.yml configuration file in the .github directory. # https://github.com/marketplace/actions/release-drafter - name: Release Drafter on: diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml new file mode 100644 index 00000000..b6d8205c --- /dev/null +++ b/.github/workflows/release-notes.yml @@ -0,0 +1,71 @@ +# Drafts your next Release notes as pull requests are merged into +# default branch + +name: Release draft update + +on: + # Runs on pushes targeting the default branch (updates the real draft release) + push: + branches: [master, main] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + draft-release-notes: + permissions: + # write permission is required to create a github release + contents: write + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 # full history with tags to get the version number + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + + - name: Drafts the next release notes + id: draft + uses: enhantica/drafterino@v2 + with: + config: | + title: 'easyreflectometry $COMPUTED_VERSION' + tag: 'v$COMPUTED_VERSION' + note-template: '- $PR_TITLE (#$PR_NUMBER)' + + default-bump: post + + major-bump-labels: ['[scope] significant'] + minor-bump-labels: ['[scope] enhancement'] + patch-bump-labels: ['[scope] bug', '[scope] maintenance'] + post-bump-labels: ['[scope] documentation'] + + release-notes: + - title: 'Added' + labels: ['[scope] significant', '[scope] enhancement'] + - title: 'Fixed' + labels: ['[scope] bug'] + - title: 'Changed' + labels: ['[scope] maintenance', '[scope] documentation'] + env: + GITHUB_TOKEN: ${{ steps.bot.outputs.token }} + + - name: Create GitHub draft release + uses: softprops/action-gh-release@v3 + with: + draft: true + tag_name: ${{ steps.draft.outputs.tag_name }} + name: ${{ steps.draft.outputs.release_name }} + body: ${{ steps.draft.outputs.release_notes }} + env: + GITHUB_TOKEN: ${{ steps.bot.outputs.token }} diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml new file mode 100644 index 00000000..3fa073b9 --- /dev/null +++ b/.github/workflows/release-pr.yml @@ -0,0 +1,55 @@ +# This workflow creates an automated release PR from a source branch into the default branch. +# +# Usage: +# - Triggered manually via workflow_dispatch. +# - Creates a PR titled "Release: merge into ". +# - Adds the label "[bot] release" so it is excluded from changelogs. +# - The PR body makes clear that this is automation only (no review needed). + +name: 'Release PR (develop → master)' + +on: + workflow_dispatch: + inputs: + source_branch: + description: 'Source branch to create PR from' + required: false + default: 'develop' + type: string + +permissions: + contents: read + pull-requests: write + +env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + SOURCE_BRANCH: ${{ inputs.source_branch || 'develop' }} + +jobs: + create-pull-request: + runs-on: ubuntu-latest + steps: + - name: Checkout ${{ env.SOURCE_BRANCH }} branch + uses: actions/checkout@v6 + with: + ref: ${{ env.SOURCE_BRANCH }} + + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + + - name: Create PR from ${{ env.SOURCE_BRANCH }} to ${{ env.DEFAULT_BRANCH }} + env: + GH_TOKEN: ${{ steps.bot.outputs.token }} + run: | + gh pr create \ + --base ${{ env.DEFAULT_BRANCH }} \ + --head ${{ env.SOURCE_BRANCH }} \ + --title "🎉 Release: merge ${{ env.SOURCE_BRANCH }} into ${{ env.DEFAULT_BRANCH }}" \ + --label "[bot] release" \ + --body "This PR is created automatically to trigger the release pipeline. It merges the accumulated changes from \`${{ env.SOURCE_BRANCH }}\` into \`${{ env.DEFAULT_BRANCH }}\`. + + ⚠️ It is labeled \`[bot] release\` and is excluded from release notes and version bump logic." diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000..0f43db35 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,93 @@ +# Code scanning (CodeQL) for vulnerabilities and insecure coding patterns. +# +# What this workflow does +# - Runs GitHub CodeQL analysis and uploads results to your repository's Security tab. +# - Triggers on PRs (so findings appear as PR checks) and on pushes to `develop`. +# - Runs on a weekly schedule. +# +# Where to find results on GitHub +# - Repository → Security → Code scanning alerts +# (You can filter by tool = CodeQL and by branch.) +# +# Where to configure on GitHub +# - Repository → Settings → Advanced Security +# Enable "GitHub Advanced Security" (if available) and configure CodeQL there. +# - Repository → Security → Code scanning alerts +# This page shows findings produced by this workflow. +# +# Notes about the scheduled run +# - Scheduled workflows are triggered from the repository's *default branch*. +# If your default branch is `master` but you want the scheduled scan to analyze +# `develop`, this workflow checks out `develop` explicitly for scheduled runs. +# +# References +# - CodeQL Action: https://github.com/github/codeql-action +# - Advanced setup docs: https://docs.github.com/en/code-security/code-scanning + +name: Security scans with CodeQL + +on: + # Run on pull requests so results show up as PR checks and code + # scanning alerts. + pull_request: + branches: [master, main, develop] + + # Run on pushes (e.g., after merging PRs). + push: + branches: [master, main, develop] + + # Run weekly. (Cron is in UTC.) + schedule: + - cron: '0 3 * * 1' + +permissions: + contents: read + security-events: write + +jobs: + codeql: + name: Code scanning + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Keep this list tight to avoid noise and speed up runs. + language: [python, actions] + + steps: + # Scheduled workflows run from the default branch. + # We explicitly analyze `develop` on the schedule to keep the scan + # focused on the active dev branch. + - name: Checkout repository (scheduled → develop) + if: ${{ github.event_name == 'schedule' }} + uses: actions/checkout@v6 + with: + ref: develop + + - name: Checkout repository + if: ${{ github.event_name != 'schedule' }} + uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + + print-link: + name: Print results link + runs-on: ubuntu-latest + + needs: codeql + permissions: {} # no special perms needed just to print links + + steps: + - name: Add Code Scanning link to job summary + run: | + echo "## 🔎 CodeQL Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "View Code Scanning alerts here:" >> $GITHUB_STEP_SUMMARY + echo "${{ github.server_url }}/${{ github.repository }}/security/code-scanning" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/test-trigger.yml b/.github/workflows/test-trigger.yml new file mode 100644 index 00000000..440bae3c --- /dev/null +++ b/.github/workflows/test-trigger.yml @@ -0,0 +1,40 @@ +name: Scheduled code tests trigger + +on: + # Run daily, at 00:00. + schedule: + - cron: '0 0 * * *' + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +permissions: + contents: read + +jobs: + code-tests-trigger: + runs-on: ubuntu-latest + + steps: + - name: Checkout develop branch + uses: actions/checkout@v6 + with: + ref: develop + + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + + - name: Dispatch code tests workflow + uses: ./.github/actions/github-script + with: + github-token: ${{ steps.bot.outputs.token }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: "test.yml", + ref: "develop" + }); diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..6bf99c45 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,306 @@ +# This is the main workflow for testing the code before and after +# packaging it. +# The workflow is divided into three jobs: +# 1. env-prepare: +# - Prepare the environment for testing +# 2. source-test: +# - Test the code base against the latest code in the repository +# - Create the Python package +# - Upload the Python package for the next job +# 3. package-test: +# - Download the Python package (including extra files) from the previous job +# - Install the downloaded Python package +# - Test the code base against the installed package +# 4. dashboard-build-trigger: +# - Trigger the dashboard build workflow to update the code quality +# metrics on the dashboard + +name: Code and package tests + +on: + # Trigger the workflow on push + push: + branches-ignore: [master, main] # Already verified in PR + # But do not run this workflow on creating a new tag starting with + # 'v', e.g. 'v1.0.3' (see publish-pypi.yml) + tags-ignore: ['v*'] + # Trigger the workflow on pull request + pull_request: + branches: ['**'] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Need permissions to trigger the dashboard build workflow +permissions: + actions: write + contents: read + +# Allow only one concurrent workflow, skipping runs queued between the run +# in-progress and latest queued. And cancel in-progress runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + PY_VERSIONS: '3.11 3.13' + PIXI_ENVS: 'py-311-env py-313-env' + +jobs: + # Job 1: Set up environment variables + env-prepare: + runs-on: [ubuntu-latest] + + outputs: + pytest-marks: ${{ steps.set-mark.outputs.pytest_marks }} + + steps: + # Determine if integration tests should be run fully or only the fast ones + # (to save time on branches other than master and develop) + - name: Set mark for integration tests + id: set-mark + run: | + if [[ "${{ env.CI_BRANCH }}" == "master" || "${{ env.CI_BRANCH }}" == "develop" ]]; then + echo "pytest_marks=" >> $GITHUB_OUTPUT + else + echo "pytest_marks=-m fast" >> $GITHUB_OUTPUT + fi + + # Job 2: Test code + source-test: + needs: env-prepare # depend on previous job + + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, macos-15, windows-2022] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + with: + environments: ${{ env.PIXI_ENVS }} + + - name: Run unit tests + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.XX -> py-3XX-env + + echo "Running tests in environment: $env" + pixi run --environment $env unit-tests + done + + - name: Run functional tests + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.XX -> py-3XX-env + + echo "Running tests in environment: $env" + pixi run --environment $env functional-tests + done + + - name: Run integration tests ${{ needs.env-prepare.outputs.pytest-marks }} + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.XX -> py-3XX-env + + echo "Running tests in environment: $env" + pixi run --environment $env integration-tests ${{ needs.env-prepare.outputs.pytest-marks }} + done + + # Delete all local tags when not on a tagged commit to force versioningit + # to fall back to the configured default-tag, which is '999.0.0' in our case. + # This is needed for testing the package in the next job, as its version + # must be higher than the PyPI version for pip to prefer the local version. + - name: Force using versioningit default tag (non tagged release) + if: startsWith(github.ref , 'refs/tags/v') != true + run: git tag --delete $(git tag) + + - name: Build package wheels for all Python versions + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.11 -> py-311-env + + echo "Building wheel in environment: $env" + pixi run --environment $env dist-build + + echo "Moving built wheel to dist/py$py_ver/" + pixi run mkdir -p dist/py$py_ver + pixi run mv dist/*.whl dist/py$py_ver/ + done + + - name: Remove Python cache files before uploading + shell: bash + run: pixi run clean-pycache + + # More than one file/dir need to be specified in 'path', to preserve the + # structure of the dist/ directory, not only its contents. + - name: Upload package (incl. extras) for next job + uses: ./.github/actions/upload-artifact + with: + name: easyreflectometry_${{ matrix.os }}_${{ runner.arch }} + path: dist/ + + # Job 3: Test the package + package-test: + needs: source-test # depend on previous job + + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, macos-15, windows-2022] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Download package (incl. extras) from previous job + uses: ./.github/actions/download-artifact + with: + # name and path should be taken from the upload step of the previous job + name: easyreflectometry_${{ matrix.os }}_${{ runner.arch }} + path: dist/ + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + with: + environments: '' + activate-environment: '' + run-install: false + frozen: false + + - name: Install easyreflectometry from the built wheel + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + echo "Initializing pixi project" + pixi init easyreflectometry_py$py_ver + cd easyreflectometry_py$py_ver + + echo "Adding Python $py_ver" + pixi add "python=$py_ver" + + echo "Setting macOS 14.0 as minimum required" + pixi project system-requirements add macos 14.0 + + echo "Looking for wheel in ../dist/py$py_ver/" + ls -l "../dist/py$py_ver/" + + whl_path=(../dist/"py$py_ver"/*.whl) + if [[ ! -f "${whl_path[0]}" ]]; then + echo "❌ No wheel found in ../dist/py$py_ver/" + exit 1 + fi + + # whl_url="file://$(python -c 'import os,sys; print(os.path.abspath(sys.argv[1]))' "${whl_path[0]}")" + # echo "Adding easyreflectometry from: $whl_url" + # pixi add --pypi "easyreflectometry[dev] @ ${whl_url}" + + whl_abs_path="$(python -c 'import os,sys; print(os.path.abspath(sys.argv[1]))' "${whl_path[0]}")" + + echo "Adding easyreflectometry from: $whl_abs_path" + pixi add --pypi "easyreflectometry[dev] @ ${whl_abs_path}" + + echo "Exiting pixi project directory" + cd .. + done + + - name: Run unit tests + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + echo "Entering pixi project directory easyreflectometry_py$py_ver" + cd easyreflectometry_py$py_ver + + echo "Running tests" + pixi run python -m pytest ../tests/unit/ --color=yes -v + + echo "Exiting pixi project directory" + cd .. + done + + - name: Run functional tests + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + echo "Entering pixi project directory easyreflectometry_py$py_ver" + cd easyreflectometry_py$py_ver + + echo "Running tests" + pixi run python -m pytest ../tests/functional/ --color=yes -v + + echo "Exiting pixi project directory" + cd .. + done + + - name: Run integration tests ${{ needs.env-prepare.outputs.pytest-marks }} + shell: bash + run: | + set -euo pipefail + + for py_ver in $PY_VERSIONS; do + echo + echo "🔹🔸🔹🔸🔹 Python: $py_ver 🔹🔸🔹🔸🔹" + + echo "Entering pixi project directory easyreflectometry_py$py_ver" + cd easyreflectometry_py$py_ver + + echo "Running tests" + pixi run python -m pytest ../tests/integration/ --color=yes -n auto -v ${{ needs.env-prepare.outputs.pytest-marks }} + + echo "Exiting pixi project directory" + cd .. + done + + # Job 4: Build and publish dashboard (reusable workflow) + run-reusable-workflows: + needs: package-test # depend on previous job + uses: ./.github/workflows/dashboard.yml + secrets: inherit diff --git a/.github/workflows/tutorial-tests-trigger.yml b/.github/workflows/tutorial-tests-trigger.yml new file mode 100644 index 00000000..4b160d87 --- /dev/null +++ b/.github/workflows/tutorial-tests-trigger.yml @@ -0,0 +1,40 @@ +name: Scheduled tutorial tests trigger + +on: + # Run daily, at 00:00. + schedule: + - cron: '0 0 * * *' + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +permissions: + contents: read + +jobs: + tutorial-tests-trigger: + runs-on: ubuntu-latest + + steps: + - name: Checkout develop branch + uses: actions/checkout@v6 + with: + ref: develop + + - name: Setup easyscience[bot] + id: bot + uses: ./.github/actions/setup-easyscience-bot + with: + app-id: ${{ vars.EASYSCIENCE_APP_ID }} + private-key: ${{ secrets.EASYSCIENCE_APP_KEY }} + + - name: Dispatch tutorial tests workflow + uses: ./.github/actions/github-script + with: + github-token: ${{ steps.bot.outputs.token }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: "tutorial-tests.yml", + ref: "develop" + }); diff --git a/.github/workflows/tutorial-tests.yml b/.github/workflows/tutorial-tests.yml new file mode 100644 index 00000000..a4fc940c --- /dev/null +++ b/.github/workflows/tutorial-tests.yml @@ -0,0 +1,60 @@ +name: Tutorial tests + +on: + # Trigger the workflow on push + push: + # Selected branches + branches: [develop] # master and main are already verified in PR + # Trigger the workflow on pull request + pull_request: + branches: ['**'] + # Trigger the workflow on workflow_call (to be called from other workflows) + # Needed, as standard schedule triggers the master branch only, but we want + # to run this workflow on develop branch. + workflow_call: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +permissions: + contents: read + +# Allow only one concurrent workflow, skipping runs queued between the run +# in-progress and latest queued. And cancel in-progress runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +# Set the environment variables to be used in all jobs defined in this workflow +env: + CI_BRANCH: ${{ github.head_ref || github.ref_name }} + +jobs: + # Job 1: Test tutorials as scripts and notebooks on multiple OS + tutorial-tests: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up pixi + uses: ./.github/actions/setup-pixi + + - name: Prepare notebooks + shell: bash + run: pixi run notebook-prepare + + - name: Test tutorials as notebooks + shell: bash + run: pixi run notebook-tests + + # Job 2: Build and publish dashboard (reusable workflow) + run-reusable-workflows: + needs: tutorial-tests # depend on previous job + uses: ./.github/workflows/dashboard.yml + secrets: inherit diff --git a/.gitignore b/.gitignore index ff5a50d4..6dc595c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,46 @@ -# QtCreator -*.autosave - -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* - -# QtCreator Python -*.pyproject.user -*.pyproject.user.* - -# QtCreator CMake -CMakeLists.txt.user* - # Python -__pycache__ -.venv +__pycache__/ +.venv/ .coverage .pyc -# Poetry -dist -poetry.lock -*.egg-info +# Pixi +.pixi/ # PyInstaller -build +dist/ +build/ *.spec -# Jupyter +# MkDocs +docs/site/ + +# Jupyter Notebooks .ipynb_checkpoints +# Node +node_modules/ + +# QtCreator +*.autosave +*.qmlproject.user +*.qmlproject.user.* +*.pyproject.user +*.pyproject.user.* +CMakeLists.txt.user* + +# PyCharm +.idea/ + +# VS Code +.vscode/ + # macOS .DS_Store *.app *.dmg -# Docs -docs/_build - -# VSCode -.vscode - # Misc -..* +.cache/ *.log *.zip -.ci/ -.idea/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..9a3855f4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,61 @@ +repos: + - repo: local + hooks: + # ------------- + # Manual checks + # ------------- + - id: pixi-pyproject-check + name: pixi run pyproject-check + entry: pixi run pyproject-check + language: system + pass_filenames: false + stages: [manual] + + - id: license-headers-check + name: pixi run license-check + entry: pixi run license-check + language: system + pass_filenames: false + stages: [manual] + + - id: pixi-py-lint-check + name: pixi run py-lint-check + entry: pixi run py-lint-check + language: system + pass_filenames: false + stages: [manual] + + - id: pixi-py-format-check + name: pixi run py-format-check + entry: pixi run py-format-check + language: system + pass_filenames: false + stages: [manual] + + - id: pixi-docstring-lint-check + name: pixi run docstring-lint-check + entry: pixi run docstring-lint-check + language: system + pass_filenames: false + stages: [manual] + + - id: pixi-nonpy-format-check + name: pixi run nonpy-format-check + entry: pixi run nonpy-format-check + language: system + pass_filenames: false + stages: [manual] + + - id: pixi-notebook-lint-check + name: pixi run notebook-lint-check + entry: pixi run notebook-lint-check + language: system + pass_filenames: false + stages: [manual] + + - id: pixi-unit-tests + name: pixi run unit-tests + entry: pixi run unit-tests + language: system + pass_filenames: false + stages: [manual] diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..a08c3c48 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,32 @@ +# Git +.git/ + +# Copier +.copier-answers*.yml + +# Pixi +.pixi +pixi.lock + +# MkDocs +docs/overrides/ +docs/site/ +docs/docs/assets/ + +# Python +.pytest_cache/ + +# MyPy +.mypy_cache/ + +# Ruff +.ruff_cache/ + +# Node +node_modules + +# Misc +.benchmarks +.cache +deps/ +tmp/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 69077f1c..bb888bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,14 @@ # Version 1.6.0 (1 May 2026) -Add Mighell-based handling of non-positive-variance points in fitting (issue #256). -Non-positive-variance data points are no longer forcibly discarded; instead, a -hybrid objective applies a Mighell substitution for non-positive-variance points -while using standard weighted least squares for the rest. The previous masking -behavior is available via `objective='legacy_mask'`. New `objective` parameter on -`MultiFitter`, `fit()`, and `fit_single_data_set_1d()`. +Add Mighell-based handling of non-positive-variance points in fitting +(issue #256). Non-positive-variance data points are no longer forcibly +discarded; instead, a hybrid objective applies a Mighell substitution +for non-positive-variance points while using standard weighted least +squares for the rest. The previous masking behavior is available via +`objective='legacy_mask'`. New `objective` parameter on `MultiFitter`, +`fit()`, and `fit_single_data_set_1d()`. # Version 1.3.3 (17 June 2025) -Added Chi^2 and fit status to fitting results. -Added explicit dependency on bumps version. +Added Chi^2 and fit status to fitting results. Added explicit dependency +on bumps version. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a4ac1fbc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,446 @@ +# Contributing to EasyReflectometry + +Thank you for your interest in contributing to **EasyReflectometry**! + +This guide explains how you can: + +- Report issues +- Contribute code +- Improve documentation +- Suggest enhancements +- Interact with the EasyScience community + +Whether you are an experienced developer or contributing for the first +time, this document walks you through the entire process step by step. + +Please make sure you follow the EasyScience organization-wide +[Code of Conduct](https://github.com/easyscience/.github/blob/master/CODE_OF_CONDUCT.md). + +--- + +## Table of Contents + +- [How to Interact With This Project](#how-to-interact-with-this-project) +- [1. Understanding the Development Model](#1-understanding-the-development-model) +- [2. Getting the Code](#2-getting-the-code) +- [3. Setting Up the Development Environment](#3-setting-up-the-development-environment) +- [4. Creating a Branch](#4-creating-a-branch) +- [5. Implementing Your Changes](#5-implementing-your-changes) +- [6. Code Quality Checks](#6-code-quality-checks) +- [7. Opening a Pull Request](#7-opening-a-pull-request) +- [8. Continuous Integration (CI)](#8-continuous-integration-ci) +- [9. Code Review](#9-code-review) +- [10. Documentation Contributions](#10-documentation-contributions) +- [11. Reporting Issues](#11-reporting-issues) +- [12. Security Issues](#12-security-issues) +- [13. Releases](#13-releases) + +--- + +## How to Interact With This Project + +If you are not planning to contribute code, you may want to: + +- 🐞 Report a bug — see [Reporting Issues](#11-reporting-issues) +- 🛡 Report a security issue — see + [Security Issues](#12-security-issues) +- 💬 Ask a question or start a discussion at + [Project Discussions](https://github.com/easyscience/reflectometry-lib/discussions) + +If you plan to contribute code or documentation, continue below. + +--- + +## 1. Understanding the Development Model + +Before you start coding, it is important to understand how development +works in this project. + +### Branching Strategy + +We use the following branches: + +- `master` — stable releases only +- `develop` — active development branch +- Short-lived branches — feature or fix branches created for a single + contribution and deleted after merge + +> [!IMPORTANT] +> +> All normal contributions must target the `develop` branch. +> +> - Do **not** open Pull Requests against `master` +> - Always create your branch from `develop` +> - Always target `develop` when opening a Pull Request + +See ADR easyscience/.github#12 for more details on the branching +strategy. + +--- + +## 2. Getting the Code + +### 2.1. If You Are an External Contributor + +If you are not a core maintainer of this repository, follow these steps. + +1. Open the repository page: + `https://github.com/easyscience/reflectometry-lib` + +2. Click the **Fork** button (top-right corner). This creates your own + copy of the repository. + +3. Clone your fork locally: + + ```bash + git clone https://github.com//reflectometry-lib.git + cd reflectometry-lib + ``` + +4. Add the original repository as `upstream`: + + ```bash + git remote add upstream https://github.com/easyscience/reflectometry-lib.git + ``` + +5. Switch to the `develop` branch and update it: + + ```bash + git fetch upstream + git checkout develop + git pull upstream develop + ``` + +If you have contributed before, make sure your local `develop` branch is +up to date before starting new work. You can update it with: + +```bash +git fetch upstream +git pull upstream develop +``` + +This ensures you are working on the latest version of the project. + +### 2.2. If You Are a Core Team Member + +Core team members can create branches directly in this repository and +therefore do not need to fork it, but the rest of the workflow remains +the same. + +--- + +## 3. Setting Up the Development Environment + +You need: + +- Git +- Pixi + +EasyScience projects use **Pixi** to manage the development environment. + +To install Pixi, follow the official instructions: +https://pixi.prefix.dev/latest/installation/ + +You do **not** need to manually install Python. Pixi automatically: + +- Creates the correct Python environment +- Installs all required dependencies +- Installs development tools (linters, formatters, test tools) + +Set up the environment: + +```bash +pixi install +pixi run post-install # Install additional tooling +``` + +After this step, your development environment is ready. + +See ADR easyscience/.github#63 for more details about using Pixi for +development. + +--- + +## 4. Creating a Branch + +Never work directly on `develop`. + +Create a new branch: + +```bash +git checkout -b my-change develop +``` + +> [!IMPORTANT] +> +> Use a clear and descriptive name, for example: +> +> - `improve-solver-speed` +> - `fix-boundary-condition` +> - `add-tutorial-example` + +Clear branch names make reviews and history easier to understand. + +--- + +## 5. Implementing Your Changes + +While developing, make small, logical commits with clear messages. + +Example: + +```bash +git add . +git commit -m "Improve performance of time integrator for large systems" +``` + +--- + +## 6. Code Quality Checks + +> [!IMPORTANT] +> +> When adding new functionality or making changes, make sure to add or +> update the following as needed: +> +> - 📘 docstrings +> - 🧪 unit tests + +Before opening a Pull Request, always run: + +```bash +pixi run check +``` + +This command: + +- Validates the pyproject.toml file +- Checks for licence headers in code files +- Identifies linting and formatting issues in Python code +- Checks docstring linting and formatting issues in Python code +- Detects formatting issues in non-Python files (MD, YAML, TOML etc.) +- Checks linting issues in Jupyter notebooks (if applicable) +- Runs unit tests + +A successful run should look like this: + +```bash +pixi run pyproject-check.......................Passed +pixi run license-check.........................Passed +pixi run py-lint-check.........................Passed +pixi run py-format-check.......................Passed +pixi run docstring-lint-check..................Passed +pixi run nonpy-format-check....................Passed +pixi run notebook-lint-check...................Passed +pixi run unit-tests............................Passed +``` + +If something fails, read the error message carefully and fix the issue. + +You can run individual checks, for example, to run only unit tests: + +```bash +pixi run unit-tests +``` + +or to run only Python linting checks: + +```bash +pixi run py-lint-check +``` + +Some formatting issues can be fixed automatically: + +```bash +pixi run fix +``` + +If everything is correctly formatted, you will see: + +```text +✅ All auto-formatting steps completed successfully! +``` + +This indicates that the auto-formatting pipeline completed successfully. +If you do not see this message and no error messages appear, try running +the command again. + +If errors are reported, resolve them and re-run: + +```bash +pixi run check +``` + +> [!IMPORTANT] +> +> All checks must pass before your Pull Request can be merged. + +If you are unsure how to fix an issue, ask for help in your Pull Request +discussion. + +--- + +## 7. Opening a Pull Request + +Push your branch: + +```bash +git push origin my-change +``` + +On GitHub: + +- Click **Compare & Pull Request** +- Ensure the base branch is `develop` +- Write a clear and concise title +- Add a description explaining what changed and why +- Add the required `[scope]` label + +### Pull Request Title + +> [!IMPORTANT] +> +> The PR title appears in release notes and changelogs. It should be +> concise and informative. + +Good examples: + +- Improve performance of time integrator for large systems +- Fix incorrect boundary condition handling in solver +- Add adaptive step-size control to ODE solver +- Add tutorial for custom model configuration +- Refactor solver API for improved readability + +### Required `[scope]` Label + +> [!IMPORTANT] +> +> Each Pull Request must include a `[scope]` label, which is used for +> automatically suggesting version bumps when preparing a new release. + +The available scopes are: + +| Label | Description | +| ----------------------- | ----------------------------------------------------------------------- | +| `[scope] bug` | Bug report or fix (major.minor.**PATCH**) | +| `[scope] documentation` | Documentation-only changes (major.minor.patch.**POST**) | +| `[scope] enhancement` | Adds or improves features (major.**MINOR**.patch) | +| `[scope] maintenance` | Code/tooling cleanup without feature or bug fix (major.minor.**PATCH**) | +| `[scope] significant` | Breaking or major changes (**MAJOR**.minor.patch) | + +See ADR easyscience/.github#33 for more details on the standardized +labeling scheme. + +--- + +## 8. Continuous Integration (CI) + +After opening a Pull Request: + +- Automated checks run automatically +- You will see green checkmarks or red crosses + +If checks fail: + +1. Open the failing check +2. Read the logs +3. Fix the issue locally +4. Run `pixi run check` +5. Push your changes + +The Pull Request updates automatically. + +--- + +## 9. Code Review + +All Pull Requests are reviewed by at least one core team member. + +Code review is collaborative and aims to improve quality. + +Do not take comments personally — they are meant to help. + +To update your PR: + +```bash +git add . +git commit -m "Address review comments" +git push +``` + +--- + +## 10. Documentation Contributions + +> [!IMPORTANT] +> +> If your change affects user-facing functionality, update the project +> documentation accordingly — specifically the `nav:` (navigation) +> structure in `mkdocs.yml` and the relevant documentation Markdown +> files in `docs/docs/`. +> +> ```text +> 📁 docs +> ├── 📁 docs - Markdown files for documentation +> │ └── ... +> └── 📄 mkdocs.yml - Configuration file (navigation, theme, etc.) +> ``` + +This may include: + +- API documentation +- Examples +- Tutorials +- Jupyter notebooks + +Preview documentation locally: + +```bash +pixi run docs-serve +``` + +Open the URL shown in the terminal to review your changes. + +--- + +## 11. Reporting Issues + +If you find a bug but cannot work on a fix, please consider opening an +issue. + +When reporting an issue, it helps to: + +- Search existing issues first. +- Provide clear reproduction steps. +- Include logs, screenshots, and environment details. + +Clear and detailed reports help maintainers investigate and resolve +issues more effectively. + +--- + +## 12. Security Issues + +> [!IMPORTANT] +> +> Please do **not** report security vulnerabilities publicly. + +If you discover a potential vulnerability, please contact the +maintainers privately so the issue can be investigated and addressed +responsibly. + +--- + +## 13. Releases + +Once your contribution is merged into `develop`, it will eventually be +included in the next stable release. + +When enough changes have accumulated in `develop`, core team members +merge `develop` into `master` to prepare a new release. The release is +then tagged and published on GitHub and PyPI. + +--- + +Thank you for contributing to EasyReflectometry and the EasyScience +ecosystem! diff --git a/LICENSE b/LICENSE index c1ee0cf3..c4e3e48e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ BSD 3-Clause License -Copyright (c) 2024, Easyscience contributors (https://github.com/EasyScience) -All rights reserved. +Copyright (c) 2021-2026 EasyScience contributors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index 1e53cdf4..01da1ff1 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,48 @@ -![Logo](https://github.com/easyScience/EasyReflectometryLib/raw/master/docs/src/_static/logo.png) -[![CI badge](https://github.com/easyScience/EasyReflectometryLib/actions/workflows/python-ci.yml/badge.svg)](https://github.com/easyScience/easyReflectometryLib/actions/workflows/python-ci.yml) -[![PyPI badge](https://img.shields.io/pypi/v/easyreflectometry.svg)](https://pypi.python.org/pypi/easyreflectometry) -[![Quality badge](https://www.codefactor.io/repository/github/easyscience/easyreflectometrylib/badge)](https://www.codefactor.io/repository/github/easyscience/easyreflectometrylib) -[![Docs badge](https://img.shields.io/badge/docs-built-blue)](http://docs.easyreflectometry.org) +

+ + + + + + + EasyReflectometry + +

-# About +**EasyReflectometry** is a software for performing reflectometry +calculations based on a layer model and refining its parameters against +reflectometry data. -A reflectometry python package and an application. + -This repo and documentation is for the `easyreflectometry` Python package that is built on the `easyscience` [framework](https://easyscience.software). -To get more information about the application visit [`easyreflectometry.org`](https://easyreflectometry.org) +**EasyReflectometry** is developed as a Python library. -# Installation +License: +[BSD 3-Clause](https://github.com/easyscience/reflectometry-lib/blob/master/LICENSE) -```sh -python -m pip install easyreflectometry -``` +## Useful Links + +### For Users + +- 📖 + [Documentation](https://easyscience.github.io/reflectometry-lib/latest) +- 🚀 + [Getting Started](https://easyscience.github.io/reflectometry-lib/latest/introduction) +- 🧪 + [Tutorials](https://easyscience.github.io/reflectometry-lib/latest/tutorials) +- 💬 + [Get in Touch](https://easyscience.github.io/reflectometry-lib/latest/introduction/#get-in-touch) +- 🧾 + [Citation](https://easyscience.github.io/reflectometry-lib/latest/introduction/#citation) + +### For Contributors + +- 🧑‍💻 [Source Code](https://github.com/easyscience/reflectometry-lib) +- 🐞 + [Issue Tracker](https://github.com/easyscience/reflectometry-lib/issues) +- 💡 + [Discussions](https://github.com/easyscience/reflectometry-lib/discussions) +- 🤝 + [Contributing Guide](https://github.com/easyscience/reflectometry-lib/blob/master/CONTRIBUTING.md) +- 🛡 + [Code of Conduct](https://github.com/easyscience/.github/blob/master/CODE_OF_CONDUCT.md) diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..f62b13ab --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +# Codecov configuration +# https://docs.codecov.com/docs/codecovyml-reference + +coverage: + status: + project: + default: + # Make project coverage informational (won't block PR) + informational: true + patch: + default: + # Require patch coverage but with threshold + threshold: 1% diff --git a/docs/docs/api-reference/assemblies/gradient_layer.md b/docs/docs/api-reference/assemblies/gradient_layer.md new file mode 100644 index 00000000..b5ee7d48 --- /dev/null +++ b/docs/docs/api-reference/assemblies/gradient_layer.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.assemblies.gradient_layer diff --git a/docs/docs/api-reference/assemblies/multilayer.md b/docs/docs/api-reference/assemblies/multilayer.md new file mode 100644 index 00000000..f693c205 --- /dev/null +++ b/docs/docs/api-reference/assemblies/multilayer.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.assemblies.multilayer diff --git a/docs/docs/api-reference/assemblies/repeating_multilayer.md b/docs/docs/api-reference/assemblies/repeating_multilayer.md new file mode 100644 index 00000000..d2215e46 --- /dev/null +++ b/docs/docs/api-reference/assemblies/repeating_multilayer.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.assemblies.repeating_multilayer diff --git a/docs/docs/api-reference/assemblies/surfactant_layer.md b/docs/docs/api-reference/assemblies/surfactant_layer.md new file mode 100644 index 00000000..19f1f56d --- /dev/null +++ b/docs/docs/api-reference/assemblies/surfactant_layer.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.assemblies.surfactant_layer diff --git a/docs/docs/api-reference/data.md b/docs/docs/api-reference/data.md new file mode 100644 index 00000000..700ec762 --- /dev/null +++ b/docs/docs/api-reference/data.md @@ -0,0 +1 @@ +::: easyreflectometry.data.measurement diff --git a/docs/docs/api-reference/elements/layer.md b/docs/docs/api-reference/elements/layer.md new file mode 100644 index 00000000..c37cd7b4 --- /dev/null +++ b/docs/docs/api-reference/elements/layer.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.elements.layers.layer diff --git a/docs/docs/api-reference/elements/layer_area_per_molecule.md b/docs/docs/api-reference/elements/layer_area_per_molecule.md new file mode 100644 index 00000000..d7245c69 --- /dev/null +++ b/docs/docs/api-reference/elements/layer_area_per_molecule.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.elements.layers.layer_area_per_molecule diff --git a/docs/docs/api-reference/elements/material.md b/docs/docs/api-reference/elements/material.md new file mode 100644 index 00000000..7fba8cf4 --- /dev/null +++ b/docs/docs/api-reference/elements/material.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.elements.materials.material diff --git a/docs/docs/api-reference/elements/material_density.md b/docs/docs/api-reference/elements/material_density.md new file mode 100644 index 00000000..24c4c425 --- /dev/null +++ b/docs/docs/api-reference/elements/material_density.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.elements.materials.material_density diff --git a/docs/docs/api-reference/elements/material_mixture.md b/docs/docs/api-reference/elements/material_mixture.md new file mode 100644 index 00000000..899b28e8 --- /dev/null +++ b/docs/docs/api-reference/elements/material_mixture.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.elements.materials.material_mixture diff --git a/docs/docs/api-reference/elements/material_solvated.md b/docs/docs/api-reference/elements/material_solvated.md new file mode 100644 index 00000000..d89901e6 --- /dev/null +++ b/docs/docs/api-reference/elements/material_solvated.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.elements.materials.material_solvated diff --git a/docs/docs/api-reference/fitting.md b/docs/docs/api-reference/fitting.md new file mode 100644 index 00000000..88b465c9 --- /dev/null +++ b/docs/docs/api-reference/fitting.md @@ -0,0 +1 @@ +::: easyreflectometry.fitting diff --git a/docs/docs/api-reference/index.md b/docs/docs/api-reference/index.md new file mode 100644 index 00000000..85b9e5c7 --- /dev/null +++ b/docs/docs/api-reference/index.md @@ -0,0 +1,72 @@ +--- +icon: material/code-braces-box +--- + +# :material-code-braces-box: API Reference + +This section contains the auto-generated reference detailing the +functions and modules available in EasyReflectometry. + +## Model + +Model is a sample, a background and a resolution. + +- [Model](model.md) + +## Sample + +Sample is built from assemblies. + +- [Sample](sample.md) + +## Project + +Project provides a higher-level interface for managing models, +experiments, and ORSO import. + +- [Project](project.md) + +## Fitting + +Fitting helpers and objective functions. + +- [Fitting](fitting.md) + +## Assemblies + +Assemblies are collections of layers that are used to represent a +specific physical setup. + +- [Multilayer](assemblies/multilayer.md) +- [Repeating Multilayer](assemblies/repeating_multilayer.md) +- [Surfactant Layer](assemblies/surfactant_layer.md) +- [Gradient Layer](assemblies/gradient_layer.md) + +## Elements + +Elements are the building blocks that are required to construct a +sample. + +### Layers + +Layers are basic elements and used to represent a single layer of +material with a thickness and a roughness. + +- [Layer](elements/layer.md) +- [Layer Area Per Molecule](elements/layer_area_per_molecule.md) + +### Materials + +Materials are the most basic elements and are used to represent a +material with given physical properties. + +- [Material](elements/material.md) +- [Material Density](elements/material_density.md) +- [Material Mixture](elements/material_mixture.md) +- [Material Solvated](elements/material_solvated.md) + +## Data + +Collection of helper functions. + +- [Data](data.md) diff --git a/docs/docs/api-reference/model.md b/docs/docs/api-reference/model.md new file mode 100644 index 00000000..cbb1bbf9 --- /dev/null +++ b/docs/docs/api-reference/model.md @@ -0,0 +1 @@ +::: easyreflectometry.model.model diff --git a/docs/docs/api-reference/project.md b/docs/docs/api-reference/project.md new file mode 100644 index 00000000..d778788c --- /dev/null +++ b/docs/docs/api-reference/project.md @@ -0,0 +1 @@ +::: easyreflectometry.project diff --git a/docs/docs/api-reference/sample.md b/docs/docs/api-reference/sample.md new file mode 100644 index 00000000..387cd62c --- /dev/null +++ b/docs/docs/api-reference/sample.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.collections.sample diff --git a/docs/docs/assets/images/favicon.png b/docs/docs/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..ec9e329e7d7b1637990ae7fb79a5449f81652c92 GIT binary patch literal 14318 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 J^Nko0{|5(1fTsWe literal 0 HcmV?d00001 diff --git a/docs/docs/assets/images/logo_dark.svg b/docs/docs/assets/images/logo_dark.svg new file mode 100644 index 00000000..d59c52d2 --- /dev/null +++ b/docs/docs/assets/images/logo_dark.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + Logo + + + + + + + + Text + + + + + \ No newline at end of file diff --git a/docs/docs/assets/images/logo_light.svg b/docs/docs/assets/images/logo_light.svg new file mode 100644 index 00000000..f02ec4a1 --- /dev/null +++ b/docs/docs/assets/images/logo_light.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + Logo + + + + + + + + Text + + + + + \ No newline at end of file diff --git a/docs/docs/assets/javascripts/extra.js b/docs/docs/assets/javascripts/extra.js new file mode 100644 index 00000000..9596ee07 --- /dev/null +++ b/docs/docs/assets/javascripts/extra.js @@ -0,0 +1,27 @@ +;(function () { + 'use strict' + + // Variables + const header = document.getElementsByTagName('header')[0] + + console.log(window.pageYOffset) + + // Hide-show header shadow + function toggleHeaderShadow() { + if (window.pageYOffset <= 0) { + header.classList.remove('md-header--shadow') + } else { + header.classList.add('md-header--shadow') + } + } + + // Onload + window.onload = function () { + toggleHeaderShadow() + } + + // Onscroll + window.onscroll = function () { + toggleHeaderShadow() + } +})() diff --git a/docs/docs/assets/javascripts/mathjax.js b/docs/docs/assets/javascripts/mathjax.js new file mode 100644 index 00000000..333524ec --- /dev/null +++ b/docs/docs/assets/javascripts/mathjax.js @@ -0,0 +1,33 @@ +window.MathJax = { + tex: { + //inlineMath: [['\\(', '\\)']], + //displayMath: [['\\[', '\\]']], + // Add support for $...$ and \(...\) delimiters + inlineMath: [ + ['$', '$'], + ['\\(', '\\)'], + ], + // Add support for $$...$$ and \[...]\ delimiters + displayMath: [ + ['$$', '$$'], + ['\\[', '\\]'], + ], + processEscapes: true, + processEnvironments: true, + }, + options: { + //ignoreHtmlClass: ".*|", + //processHtmlClass: "arithmatex" + // Skip code blocks only + skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'], + // Only ignore explicit opt-out + ignoreHtmlClass: 'no-mathjax|tex2jax_ignore', + }, +} + +document$.subscribe(() => { + MathJax.startup.output.clearCache() + MathJax.typesetClear() + MathJax.texReset() + MathJax.typesetPromise() +}) diff --git a/docs/docs/assets/stylesheets/extra.css b/docs/docs/assets/stylesheets/extra.css new file mode 100644 index 00000000..a625be80 --- /dev/null +++ b/docs/docs/assets/stylesheets/extra.css @@ -0,0 +1,359 @@ +/*************/ +/* Variables */ +/*************/ + +:root { + --sz-link-color--lightmode: #0184c7; + --sz-hovered-link-color--lightmode: #37bdf9; + --sz-body-background-color--lightmode: #fafafa; + --sz-body-text-color--lightmode: #525252; + --sz-body-heading-color--lightmode: #434343; + --sz-code-background-color--lightmode: #ececec; + + --sz-link-color--darkmode: #37bdf9; + --sz-hovered-link-color--darkmode: #2890c0; + --sz-body-background-color--darkmode: #262626; + --sz-body-text-color--darkmode: #a3a3a3; + --sz-body-heading-color--darkmode: #e5e5e5; + --sz-code-background-color--darkmode: #212121; +} + +/****************/ +/* Color styles */ +/****************/ + +/* Default styles https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/main/_typeset.scss */ + +/* Light mode */ + +/* Default light mode https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/main/_colors.scss */ + +[data-md-color-scheme="default"] { + + /* Primary color shades */ + --md-primary-fg-color: var(--sz-body-background-color--lightmode); /* Navigation background */ + --md-primary-bg-color: var(--sz-body-text-color--lightmode); /* E.g., Header title and icons */ + --md-primary-bg-color--light: var(--sz-body-text-color--lightmode); /* E.g., Header search */ + + /* Accent color shades */ + --md-accent-fg-color: var(--sz-hovered-link-color--lightmode); /* E.g., Hovered `a` elements & copy icon in code */ + + /* Default color shades */ + --md-default-fg-color: var(--sz-body-text-color--lightmode); + --md-default-fg-color--light: var(--sz-body-heading-color--lightmode); /* E.g., `h1` color & TOC viewed items & `$` in code */ + --md-default-fg-color--lighter: var(--sz-body-text-color--lightmode); /* E.g., `¶` sign near `h1-h8` */ + --md-default-fg-color--lightest: var(--sz-body-text-color--lightmode); /* E.g., Copy icon in code */ + --md-default-bg-color: var(--sz-body-background-color--lightmode); + + /* Code color shades */ + --md-code-bg-color: var(--sz-code-background-color--lightmode); + + /* Typeset color shades */ + --md-typeset-color: var(--sz-body-text-color--lightmode); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--sz-link-color--lightmode); + + /* Footer color shades */ + --md-footer-fg-color: var(--sz-body-text-color--lightmode); /* E.g., Next -> */ + --md-footer-fg-color--light: var(--sz-body-text-color--lightmode); /* E.g., © 2022 EasyDiffraction, Material for MkDocs */ + --md-footer-fg-color--lighter: var(--sz-body-text-color--lightmode); /* E.g. Made with */ + --md-footer-bg-color: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., Next -> */ + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., © 2022 EasyDiffraction */ + + /* Custom colors */ + --sz-red-color: #F44336; + --sz-blue-color: #03A9F4; + --sz-green-color: #4CAF50; + --sz-orange-color: #FF9800; + + /* Logo display */ + --md-footer-logo-dark-mode: none; + --md-footer-logo-light-mode: block; +} + +/* Dark mode */ + +/* Default dark mode: https://github.com/squidfunk/mkdocs-material/blob/master/src/assets/stylesheets/palette/_scheme.scss */ + +[data-md-color-scheme="slate"] { + + /* Primary color shades */ + --md-primary-fg-color: var(--sz-body-background-color--darkmode); /* Navigation background */ + --md-primary-bg-color: var(--sz-body-text-color--darkmode); /* E.g., Header title and icons */ + --md-primary-bg-color--light: var(--sz-body-text-color--darkmode); /* E.g., Header search */ + + /* Accent color shades */ + --md-accent-fg-color: var(--sz-hovered-link-color--darkmode); /* E.g., Hovered `a` elements & copy icon in code */ + + /* Default color shades */ + --md-default-fg-color: var(--sz-body-text-color--darkmode); + --md-default-fg-color--light: var(--sz-body-heading-color--darkmode); /* E.g., `h1` color & TOC viewed items & `$` in code */ + --md-default-fg-color--lighter: var(--sz-body-text-color--darkmode); /* E.g., `¶` sign near `h1-h8` */ + --md-default-fg-color--lightest: var(--sz-body-text-color--darkmode); /* E.g., Copy icon in code */ + --md-default-bg-color: var(--sz-body-background-color--darkmode); + + /* Code color shades */ + --md-code-bg-color: var(--sz-code-background-color--darkmode); + + /* Typeset color shades */ + --md-typeset-color: var(--sz-body-text-color--darkmode); + + /* Typeset `a` color shades */ + --md-typeset-a-color: var(--sz-link-color--darkmode); + + /* Footer color shades */ + --md-footer-fg-color: var(--sz-body-text-color--darkmode); /* E.g., Next -> */ + --md-footer-fg-color--light: var(--sz-body-text-color--darkmode); /* E.g., © 2022 EasyDiffraction, Material for MkDocs */ + --md-footer-fg-color--lighter: var(--sz-body-text-color--darkmode); /* E.g. Made with */ + --md-footer-bg-color: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., Next -> */ + --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.0); /* Space with, e.g., © 2022 EasyDiffraction */ + + /* Custom colors */ + --sz-red-color: #EF9A9A; + --sz-blue-color: #81D4FA; + --sz-green-color: #A5D6A7; + --sz-orange-color: #FFCC80; + + /* Logo display */ + --md-footer-logo-dark-mode: block; + --md-footer-logo-light-mode: none; +} + +/*****************/ +/* Custom styles */ +/*****************/ + +/* Logo */ + +#logo_light_mode { + display: var(--md-footer-logo-light-mode); +} + +#logo_dark_mode { + display: var(--md-footer-logo-dark-mode); +} + +/* Customize default styles of MkDocs Material */ + +/* Hide navigation title */ +label.md-nav__title[for="__drawer"] { + height: 0; +} + +/* Hide site title (first topic) while keeping page title and version selector */ +.md-header__topic:first-child .md-ellipsis { + display: none; +} + +/* Increase logo size */ +.md-logo :is(img, svg) { + height: 1.8rem !important; +} + +/* Hide GH repo with counts (top right page corner) */ +.md-header__source { + display: none; +} + +/* Hide GH repo with counts (navigation bar in mobile view) */ +.md-nav__source { + display: none; +} + +/* Ensure all horizontal lines in the navigation list are removed or hidden */ +.md-nav__item { + /* Removes any border starting from the second level */ + border: none !important; + /* Modifies the background color to hide the first horizontal line */ + background-color: var(--md-default-bg-color); +} + +/* Increase TOC (on the right) width */ +.md-nav--secondary { + margin-left: -10px; + margin-right: -4px; +} + +/* */ +.md-nav__item > .md-nav__link { + padding-left: 0.5em; /* Default */ +} + +/* Change line height of the tabel cells */ +.md-typeset td, +.md-typeset th { + line-height: 1.25 !important; +} + +/* Change vertical alignment of the icon inside the tabel cells */ +.md-typeset td .twemoji { + vertical-align: sub !important; +} + +/* Change the width of the primary sidebar */ +/* +.md-sidebar--primary { + width: 240px; +} +*/ + +/* Change the overall width of the page */ +.md-grid { + max-width: 1280px; +} + +/* Needed for mkdocs-jupyter to show download and other buttons on top of the notebook */ +.md-content__button { + position: relative !important; +} + +/* Background color of the search input field */ +.md-search__input { + background-color: var(--md-code-bg-color) !important; +} + +/* Customize default style of mkdocs-jupyter plugin */ + +/* Set the width of the notebook to fill 100% and not reduce by the width of .md-content__button's +Adjust the margins and paddings to fit the defaults in MkDocs Material and do not crop the label in the header +*/ +.jupyter-wrapper { + width: 100% !important; + display: flex !important; +} + +.jp-Notebook { + padding: 0 !important; + margin-top: -3em !important; + + /* Ensure notebook content stretches across the page */ + width: 100% !important; + max-width: 100% !important; + + /* mkdocs-material + some notebook HTML end up as flex */ + align-items: stretch !important; +} + +.jp-Notebook .jp-Cell { + /* Key: flex children often need min-width: 0 to prevent weird shrink */ + width: 100% !important; + max-width: 100% !important; + min-width: 0 !important; + + /* Removes jupyter cell paddings */ + padding-left: 0 !important; +} + +/* Removes jupyter cell prefixes, like In[123]: */ +.prompt, +.jp-InputPrompt, +.jp-OutputPrompt { + display: none !important; +} + +/* Removes jupyter output cell padding to align with input cell text */ +.jp-RenderedText { + padding-left: 0.85em !important; +} + +/* Extra styling the panda dataframes, on top of the style included in the code */ +table.dataframe { + float: left; + margin-left: 0.75em !important; + margin-bottom: 0.5em !important; + font-size: var(--jp-code-font-size) !important; + color: var(--md-primary-bg-color) !important; + /* Allow table cell wrapping in MkDocs-Jupyter outputs */ + /* + table-layout: auto !important; + width: auto !important; + */ +} + +/* Allow wrap for the last column */ +/* +table.dataframe td:last-child, +table.dataframe th:last-child { + white-space: normal !important; + word-break: break-word !important; +} +*/ + +/* Custom styles for the CIF files */ + +.cif { + padding-left: 1em; + padding-right: 1em; + padding-top: 1px; + padding-bottom: 1px; + background-color: var(--md-code-bg-color); + font-size: small; +} +.red { + color: var(--sz-red-color); +} +.green { + color: var(--sz-green-color); +} +.blue { + color: var(--sz-blue-color); +} +.orange { + color: var(--sz-orange-color); +} +.grey { + color: grey; +} + +/**********/ +/* Labels */ +/**********/ + +.label-cif { + padding-top: 0.5ex; + padding-bottom: 0.5ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 1ex; + color: var(--md-default-fg-color) !important; + background-color: var(--md-code-bg-color); +} + +p .label-cif, li .label-cif { + vertical-align: 5%; + font-size: 12px; +} + +.label-cif:hover { + color: white !important; +} + +.label-experiment { + padding-top: 0.25ex; + padding-bottom: 0.6ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 1ex; + color: var(--md-default-fg-color) !important; + background-color: rgba(55, 189, 249, 0.1); +} + +p .label-experiment, li .label-experiment { + vertical-align: 5%; + font-size: 12px; +} + +h1 .label-experiment { + padding-top: 0.05ex; + padding-bottom: 0.4ex; + padding-left: 0.9ex; + padding-right: 0.9ex; + border-radius: 0.75ex; + color: var(--md-default-fg-color) !important; + background-color: rgba(55, 189, 249, 0.1); +} + +.label-experiment:hover { + color: white !important; +} diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 00000000..f11ef87f --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,21 @@ +![](assets/images/logo_dark.svg#gh-dark-mode-only)![](assets/images/logo_light.svg#gh-light-mode-only) + +# Reflectometry data analysis + +Here is a brief overview of the main documentation sections: + +- [:material-information-slab-circle: Introduction](introduction/index.md) + – Provides a description of EasyReflectometry, including its purpose, + licensing, latest release details, and contact information. +- [:material-cog-box: Installation & Setup](installation-and-setup/index.md) + – Guides users through system requirements, environment configuration, + and the installation process. +- [:material-book-open-variant: User Guide](user-guide/index.md) – + Covers core concepts, key terminology, workflow steps, and essential + parameters for effective use of EasyReflectometry. +- [:material-school: Tutorials](tutorials/index.md) – Offers practical, + step-by-step examples demonstrating common workflows and data analysis + tasks. +- [:material-code-braces-box: API Reference](api-reference/index.md) – + An auto-generated reference detailing the available functions and + modules in EasyReflectometry. diff --git a/docs/docs/installation-and-setup/index.md b/docs/docs/installation-and-setup/index.md new file mode 100644 index 00000000..4baef822 --- /dev/null +++ b/docs/docs/installation-and-setup/index.md @@ -0,0 +1,282 @@ +--- +icon: material/cog-box +--- + +# :material-cog-box: Installation & Setup + +**EasyReflectometry** is a cross-platform Python library compatible with +**Python 3.11** through **3.13**. + +To install and set up EasyReflectometry, we recommend using +[**Pixi**](https://pixi.prefix.dev), a modern package manager for +Windows, macOS, and Linux. + +??? note "Main benefits of using Pixi" + + - **Ease of use**: Pixi simplifies the installation process, making it + accessible even for users with limited experience in package management. + - **Python version control**: Pixi allows specifying and managing different + Python versions for each project, ensuring compatibility. + - **Isolated environments**: Pixi creates isolated environments for each + project, preventing conflicts between different package versions. + - **PyPI and Conda support**: Pixi can install packages from both PyPI and + Conda repositories, providing access to a wide range of libraries. + +An alternative installation method using the traditional **pip** package +manager is also provided. + +## Installing with Pixi recommended { #installing-with-pixi data-toc-label="Installing with Pixi" } + +This section describes the simplest way to set up EasyReflectometry +using **Pixi**. + +#### Installing Pixi + +- Install Pixi by following the instructions on the + [official Pixi Installation Guide](https://pixi.prefix.dev/latest/installation). + +#### Setting up EasyReflectometry with Pixi + + + +- Choose a project location (local drive recommended). + + ??? warning ":fontawesome-brands-windows: Windows + OneDrive" + + We **do not recommend creating a Pixi project inside OneDrive or other + synced folders**. + + By default, Pixi creates the virtual environment inside the project + directory (in `.pixi/`). On Windows, synced folders such as OneDrive + may cause file‑system issues (e.g., path-length limitations or + restricted link operations), which can lead to unexpected install + errors or environments being recreated. + + Instead, create your project in a **local directory on your drive** + where you have full write permissions. + + + +- Initialize a new Pixi project and navigate into it: + ```txt + pixi init easyreflectometry + cd easyreflectometry + ``` +- Set the Python version for the Pixi environment (e.g., 3.13): + ```txt + pixi add python=3.13 + ``` +- Add EasyReflectometry to the Pixi environment from PyPI: + ```txt + pixi add --pypi easyreflectometry + ``` +- Add a Pixi task to run EasyReflectometry commands easily: + ```txt + pixi task add easyreflectometry "python -m easyreflectometry" + ``` + +#### Updating Pixi and EasyReflectometry + +- To update all packages in the Pixi environment, including + EasyReflectometry: + ```txt + pixi update + ``` +- To update Pixi itself to the latest version: + ```txt + pixi self-update + ``` + +#### Uninstalling Pixi + +- Follow the + [official Pixi Guide](https://pixi.prefix.dev/latest/installation/#uninstall). + +## Classical Installation + +This section describes how to install EasyReflectometry using the +traditional method with **pip**. It is assumed that you are familiar +with Python package management and virtual environments. + +### Environment Setup optional { #environment-setup data-toc-label="Environment Setup" } + +We recommend using a **virtual environment** to isolate dependencies and +avoid conflicts with system-wide packages. If any issues arise, you can +simply delete and recreate the environment. + +#### Creating and Activating a Virtual Environment: + + + +- Create a new virtual environment: + ```txt + python3 -m venv venv + ``` +- Activate the environment: + + === ":material-apple: macOS" + ```txt + . venv/bin/activate + ``` + === ":material-linux: Linux" + ```txt + . venv/bin/activate + ``` + === ":fontawesome-brands-windows: Windows" + ```txt + . venv/Scripts/activate # Windows with Unix-like shells + .\venv\Scripts\activate.bat # Windows with CMD + .\venv\Scripts\activate.ps1 # Windows with PowerShell + ``` + +- The terminal should now show `(venv)`, indicating that the virtual environment + is active. + + + +#### Deactivating and Removing the Virtual Environment: + + + +- Exit the environment: + ```txt + deactivate + ``` +- If this environment is no longer needed, delete it: + + === ":material-apple: macOS" + ```txt + rm -rf venv + ``` + === ":material-linux: Linux" + ```txt + rm -rf venv + ``` + === ":fontawesome-brands-windows: Windows" + ```txt + rmdir /s /q venv + ``` + + + +### Installing from PyPI { #from-pypi } + +EasyReflectometry is available on **PyPI (Python Package Index)** and +can be installed using `pip`. To do so, use the following command: + +```txt +pip install easyreflectometry +``` + +To install a specific version of EasyReflectometry, e.g., 1.0.3: + +```txt +pip install 'easyreflectometry==1.0.3' +``` + +To upgrade to the latest version: + +```txt +pip install --upgrade easyreflectometry +``` + +To upgrade to the latest version and force reinstallation of all +dependencies (useful if files are corrupted): + +```txt +pip install --upgrade --force-reinstall easyreflectometry +``` + +To check the installed version: + +```txt +pip show easyreflectometry +``` + +### Installing from GitHub alternative { #from-github data-toc-label="Installing from GitHub" } + +Installing unreleased versions is generally not recommended but may be +useful for testing. + +To install EasyReflectometry from the `develop` branch of GitHub, for +example: + +```txt +pip install git+https://github.com/easyscience/reflectometry-lib@develop +``` + +To include extra dependencies (e.g., dev): + +```txt +pip install 'easyreflectometry[dev] @ git+https://github.com/easyscience/reflectometry-lib@develop' +``` + +## How to Run Tutorials + +EasyReflectometry includes a collection of **Jupyter Notebook examples** +that demonstrate key functionality. These tutorials serve as +**step-by-step guides** to help users understand the data analysis +workflow. They are available as **static HTML pages** in the +[:material-school: Tutorials](../tutorials/index.md) section. + +In the next sections, we explain how to set up Jupyter and run the +tutorials interactively in two different ways: locally or online via +Google Colab. + +If you decide to run the tutorials locally, you need to download them +first. This can be done individually via the :material-download: +**Download Notebook** button available on each tutorial page, or all at +once using the command line, as shown below. + +### Run Tutorials Locally with Pixi recommended { #running-with-pixi data-toc-label="Run Tutorials Locally with Pixi" } + +- Navigate to your existing Pixi project, created as described in the + [Installing with Pixi](#installing-with-pixi) section. +- Add JupyterLab, Interactive Python shell and the Pixi kernel for + Jupyter: + ```txt + pixi add --pypi jupyterlab ipython pixi-kernel + ``` +- Download all the EasyReflectometry tutorials to the `tutorials/` + directory. +- Start the JupyterLab server in the `tutorials/` directory to access + the notebooks: + ```txt + pixi run jupyter lab tutorials/ + ``` +- Your web browser should open automatically. Click on one of the + `*.ipynb` files and select the `Python (Pixi)` kernel to get started. + +### Classical Run Tutorials Locally + +- Install Jupyter Notebook, Interactive Python shell and the IPython + kernel: + ```txt + pip install notebook ipython ipykernel + ``` +- Add the virtual environment as a Jupyter kernel: + ```txt + python -m ipykernel install --user --name=venv --display-name "EasyReflectometry Python kernel" + ``` +- Download all the EasyReflectometry tutorials to the `tutorials/` + directory. +- Start the Jupyter Notebook server in the `tutorials/` directory to + access the notebooks: + ```txt + jupyter notebook tutorials/ + ``` +- Your web browser should open automatically. Click on one of the + `*.ipynb` files and select the `EasyReflectometry Python kernel` to + get started. + +### Run Tutorials via Google Colab + +**Google Colab** lets you run Jupyter Notebooks in the cloud without any +local installation. This is the fastest way to start experimenting with +EasyReflectometry. + +- Ensure you have a **Google account**. +- Go to the **[:material-school: Tutorials](../tutorials/index.md)** + section. +- Click the :google-colab: **Open in Google Colab** button on any + tutorial. diff --git a/docs/docs/introduction/index.md b/docs/docs/introduction/index.md new file mode 100644 index 00000000..4335dfdf --- /dev/null +++ b/docs/docs/introduction/index.md @@ -0,0 +1,68 @@ +--- +icon: material/information-slab-circle +--- + +# :material-information-slab-circle: Introduction + +## Description + +**EasyReflectometry** is a software for performing reflectometry +calculations based on a layer model and refining its parameters against +reflectometry data. + +**EasyReflectometry** is developed as a Python library. + + + +## License + +**EasyReflectometry** library is released under the +[BSD 3-Clause License](https://raw.githubusercontent.com/easyscience/reflectometry-lib/master/LICENSE). + +## Releases + +The latest version of the **EasyReflectometry** library is +[{{ vars.release_version }}](https://github.com/easyscience/reflectometry-lib/releases/latest). + +For a complete list of new features, bug fixes, and improvements, see +the +[GitHub Releases page](https://github.com/easyscience/reflectometry-lib/releases). + +## Citation + +If you use **EasyReflectometry** library in your work, please cite the +specific version you used. + +All official releases of the **EasyReflectometry** library are archived +on Zenodo, each with a version-specific Digital Object Identifier (DOI). + +Citation details in various styles (e.g., APA, MLA) and formats (e.g., +BibTeX, JSON) are available on the +[Zenodo archive page](https://doi.org/10.5281/zenodo.18163581). + +## Contributing + +We welcome contributions of any kind! + +**EasyReflectometry** is intended to be a community-driven, open-source +project supported by a diverse group of contributors. + +The project is maintained by the +[European Spallation Source (ESS)](https://ess.eu). + +If you would like to report a bug or request a new feature, please use +the +[GitHub Issue Tracker](https://github.com/easyscience/reflectometry-lib/issues) +(A free GitHub account is required.) + +To contribute code, documentation, or tests, please see our +[:material-account-plus: Contributing Guidelines](https://github.com/easyscience/reflectometry-lib/blob/master/CONTRIBUTING.md) +for detailed development instructions. + +## Get in Touch + +For general questions or feedback, please contact us at +[support@easyreflectometry.org](mailto:support@easyreflectometry.org). diff --git a/docs/src/tutorials/advancedfitting/multi_contrast.ipynb b/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb similarity index 92% rename from docs/src/tutorials/advancedfitting/multi_contrast.ipynb rename to docs/docs/tutorials/advancedfitting/multi_contrast.ipynb index 51e4e225..02f675ba 100644 --- a/docs/src/tutorials/advancedfitting/multi_contrast.ipynb +++ b/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb @@ -20,6 +20,16 @@ "First configure matplotlib to place figures in notebook and import needed modules. Note that the plot function needs installation of `plopp` seperately or installation of `easyreflectometry[dev]`" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "54684688", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": null, @@ -27,29 +37,21 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", - "import refnx\n", "import pooch\n", + "from easyscience.fitting import AvailableMinimizers\n", "\n", - "import easyreflectometry\n", - "\n", + "from easyreflectometry.calculators import CalculatorFactory\n", "from easyreflectometry.data import load\n", + "from easyreflectometry.fitting import MultiFitter\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import PercentageFwhm\n", "from easyreflectometry.plot import plot\n", - "from easyreflectometry.sample import Material\n", - "from easyreflectometry.sample import SurfactantLayer\n", "from easyreflectometry.sample import Layer\n", - "from easyreflectometry.sample import Multilayer\n", "from easyreflectometry.sample import LayerAreaPerMolecule\n", + "from easyreflectometry.sample import Material\n", + "from easyreflectometry.sample import Multilayer\n", "from easyreflectometry.sample import Sample\n", - "from easyreflectometry.model import Model\n", - "from easyreflectometry.model import PercentageFwhm\n", - "from easyreflectometry.calculators import CalculatorFactory\n", - "from easyreflectometry.fitting import MultiFitter\n", - "from easyscience.fitting import AvailableMinimizers\n", - "\n", - "print(f'easyreflectometry: {easyreflectometry.__version__}')\n", - "print(f'refnx: {refnx.__version__}')" + "from easyreflectometry.sample import SurfactantLayer" ] }, { @@ -73,8 +75,8 @@ "source": [ "file_path = pooch.retrieve(\n", " # URL to one of Pooch's test files\n", - " url=\"https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/advancedfitting/multiple.ort\",\n", - " known_hash=\"241bcb819cdae47fbbb310a99c2456c7332312719496b936a153dc7dee83e62c\",\n", + " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/advancedfitting/multiple.ort',\n", + " known_hash='241bcb819cdae47fbbb310a99c2456c7332312719496b936a153dc7dee83e62c',\n", ")\n", "data = load(file_path)" ] @@ -130,8 +132,7 @@ "metadata": {}, "outputs": [], "source": [ - "dspc = {'d-head': 'C10D18NO8P', 'd-tail': 'C34D70',\n", - " 'h-head': 'C10H18NO8P', 'h-tail': 'C34H70'}" + "dspc = {'d-head': 'C10D18NO8P', 'd-tail': 'C34D70', 'h-head': 'C10H18NO8P', 'h-tail': 'C34H70'}" ] }, { @@ -235,7 +236,7 @@ " solvent=air,\n", " solvent_fraction=tail_solvent_fraction,\n", " area_per_molecule=area_per_molecule,\n", - " roughness=roughness\n", + " roughness=roughness,\n", ")\n", "head_layer_d13d2o = LayerAreaPerMolecule(\n", " molecular_formula=dspc['d-head'],\n", @@ -243,12 +244,9 @@ " solvent=d2o,\n", " solvent_fraction=head_solvent_fraction,\n", " area_per_molecule=area_per_molecule,\n", - " roughness=roughness\n", - ")\n", - "d13d2o = SurfactantLayer(\n", - " tail_layer=tail_layer_d13d2o,\n", - " head_layer=head_layer_d13d2o\n", + " roughness=roughness,\n", ")\n", + "d13d2o = SurfactantLayer(tail_layer=tail_layer_d13d2o, head_layer=head_layer_d13d2o)\n", "d13d2o.constrain_area_per_molecule = True\n", "d13d2o.conformal_roughness = True\n", "d13d2o.constrain_solvent_roughness(d2o_layer.roughness)" @@ -275,7 +273,7 @@ " solvent=air,\n", " solvent_fraction=tail_solvent_fraction,\n", " area_per_molecule=area_per_molecule,\n", - " roughness=roughness\n", + " roughness=roughness,\n", ")\n", "head_layer_d70d2o = LayerAreaPerMolecule(\n", " molecular_formula=dspc['h-head'],\n", @@ -283,12 +281,9 @@ " solvent=d2o,\n", " solvent_fraction=head_solvent_fraction,\n", " area_per_molecule=area_per_molecule,\n", - " roughness=roughness\n", - ")\n", - "d70d2o = SurfactantLayer(\n", - " tail_layer=tail_layer_d70d2o,\n", - " head_layer=head_layer_d70d2o\n", + " roughness=roughness,\n", ")\n", + "d70d2o = SurfactantLayer(tail_layer=tail_layer_d70d2o, head_layer=head_layer_d70d2o)\n", "d70d2o.constrain_area_per_molecule = True\n", "d70d2o.conformal_roughness = True\n", "d70d2o.constrain_solvent_roughness(d2o_layer.roughness)" @@ -315,7 +310,7 @@ " solvent=air,\n", " solvent_fraction=tail_solvent_fraction,\n", " area_per_molecule=area_per_molecule,\n", - " roughness=roughness\n", + " roughness=roughness,\n", ")\n", "head_layer_d83acmw = LayerAreaPerMolecule(\n", " molecular_formula=dspc['d-head'],\n", @@ -323,12 +318,9 @@ " solvent=acmw,\n", " solvent_fraction=head_solvent_fraction,\n", " area_per_molecule=area_per_molecule,\n", - " roughness=roughness\n", - ")\n", - "d83acmw = SurfactantLayer(\n", - " tail_layer=tail_layer_d83acmw,\n", - " head_layer=head_layer_d83acmw\n", + " roughness=roughness,\n", ")\n", + "d83acmw = SurfactantLayer(tail_layer=tail_layer_d83acmw, head_layer=head_layer_d83acmw)\n", "d83acmw.constrain_area_per_molecule = True\n", "d83acmw.conformal_roughness = True\n", "d83acmw.constrain_solvent_roughness(acmw_layer.roughness)" @@ -420,19 +412,19 @@ " sample=d13d2o_sample,\n", " scale=0.1,\n", " background=data['data']['R_d13DSPC-D2O'].values.min(),\n", - " resolution_function=resolution_function\n", + " resolution_function=resolution_function,\n", ")\n", "d70d2o_model = Model(\n", " sample=d70d2o_sample,\n", " scale=0.1,\n", " background=data['data']['R_d70DSPC-D2O'].values.min(),\n", - " resolution_function=resolution_function\n", + " resolution_function=resolution_function,\n", ")\n", "d83acmw_model = Model(\n", " sample=d83acmw_sample,\n", " scale=0.1,\n", " background=data['data']['R_d83DSPC-ACMW'].values.min(),\n", - " resolution_function=resolution_function\n", + " resolution_function=resolution_function,\n", ")" ] }, diff --git a/docs/src/tutorials/advancedfitting/multiple.ort b/docs/docs/tutorials/advancedfitting/multiple.ort similarity index 100% rename from docs/src/tutorials/advancedfitting/multiple.ort rename to docs/docs/tutorials/advancedfitting/multiple.ort diff --git a/docs/docs/tutorials/basic/assemblies_library.md b/docs/docs/tutorials/basic/assemblies_library.md new file mode 100644 index 00000000..da419f56 --- /dev/null +++ b/docs/docs/tutorials/basic/assemblies_library.md @@ -0,0 +1,71 @@ +# Creating Multilayers and Surfactant Layers + +EasyReflectometry is designed to be used with a broad range of different +assemblies. Assemblies are collective layers behaving as a single +object, for example, a multilayer or a surfactant layer. These +assemblies offer flexibility for the user and enable more powerful +analysis by making chemical and physical constraints available with +limited code. In this page, we will document the assemblies that are +available with simple examples of the constructors that exist. Full API +documentation is also available for the +`easyreflectometry.sample.assemblies` module. + +## Multilayer + +This assembly should be used for a series of layers that should be +thought of as a single object. For example, in the simple fitting +tutorial this assembly type is used to combine the silicon and silicon +dioxide layer that is formed into a single object. All of the separate +layers in these objects will be fitted individually, i.e. there are no +constraints present, however, there is some cognitive benefit to +grouping layers together. + +To create a `Multilayer` object, we use the following construction. + +```python +from easyreflectometry.sample import Layer +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer + +si = Material(sld=2.07, isld=0, name='Si') +sio2 = Material(sld=3.47, isld=0, name='SiO2') +si_layer = Layer(material=si, thickness=0, roughness=0, name='Si layer') +sio2_layer = Layer(material=sio2, thickness=30, roughness=3, name='SiO2 layer') + +subphase = Multilayer(layers=[si_layer, sio2_layer], name='Si/SiO2 subphase') +``` + +This will create a `Multilayer` object named `subphase` which we can use +in some `Structure` for our analysis. + +## RepeatingMultilayer + +The `RepeatingMultilayer` assembly type is an extension of the +`Multilayer` for the analysis of systems with a multilayer that has some +number of repeats. This assembly type imposes some constraints, +specifically that all of the repeats have the exact same structure (i.e. +thicknesses, roughnesses, and scattering length densities), which brings +with it some computational saving as the reflectometry coefficients only +need to be calculated once for this structure and propagated for the +correct number of repeats. There is a tutorial that discusses the +utilisation of this assembly type for a nickel-titanium multilayer +system. + +The creation of a `RepeatingMultilayer` object is very similar to that +for the `Multilayer`, with the addition of a number of repetitions. + +```python +from easyreflectometry.sample import Layer +from easyreflectometry.sample import Material +from easyreflectometry.sample import RepeatingMultilayer + +ti = Material(sld=-1.9493, isld=0, name='Ti') +ni = Material(sld=9.4245, isld=0, name='Ni') +ti_layer = Layer(material=ti, thickness=40, roughness=0, name='Ti Layer') +ni_layer = Layer(material=ni, thickness=70, roughness=0, name='Ni Layer') +ni_ti = RepeatingMultilayer(layers=[ti_layer, ni_layer], repetitions=10, name='Ni/Ti Multilayer') +``` + +The number of repeats is a parameter that can be varied in the +optimisation process, however given this is a value that depends on the +synthesis of the sample this is unlikely to be necessary. diff --git a/docs/docs/tutorials/basic/layer_library.md b/docs/docs/tutorials/basic/layer_library.md new file mode 100644 index 00000000..4a679690 --- /dev/null +++ b/docs/docs/tutorials/basic/layer_library.md @@ -0,0 +1,69 @@ +# Defining Layers + +Similar to a range of different materials, there are a few different +ways that a layer can be defined in EasyReflectometry. + +## Layer + +The `Layer` is the simplest possible type of layer, taking a `Material` +and two floats associated with the thickness and upper (that is closer +to the source of the incident radiation) roughness. So we construct a +`Layer` as follows for a 100 Å thick layer of boron with a roughness of +10 Å. + +```python +from easyreflectometry.sample import Material +from easyreflectometry.sample import Layer + +boron = Material(sld=6.908, isld=-0.278, name='Boron') +boron_layer = Layer(material=boron, thickness=100, roughness=10, name='Boron Layer') +``` + +This type of layer is used extensively in the tutorials. + +To create a semi-infinite layer one needs to set the thickness to 0 and +the roughness to 0. + +```python +from easyreflectometry.sample import Material +from easyreflectometry.sample import Layer + +si = Material(sld=2.07, isld=0, name='Si') +semi_infinite_layer = Layer(material=si, thickness=0, roughness=0, name='Si layer') +``` + +## LayerAreaPerMolecule + +The `LayerAreaPerMolecule` layer type is the foundation of the +`SurfactantLayer` assemblies type (further information on this can be +found in the assemblies library). The purpose of the +`LayerAreaPerMolecule` is to allow a layer to be defined in terms of the +chemical formula of the material and the area per molecule of the layer. +The area per molecule is a common description of surface density in the +surfactant monolayer and bilayer community. + +We can construct a 10 Å thick `LayerAreaPerMolecule` of +phosphatidylcholine, with an area per molecule of 48 Å squared and a +roughness of 3 Å that has 20% solvent surface coverage with D2O using +the following. + +```python +from easyreflectometry.sample import Material +from easyreflectometry.sample import LayerAreaPerMolecule + +d2o = Material(sld=6.36, isld=0, name='D2O') +molecular_formula = 'C10H18NO8P' +pc = LayerAreaPerMolecule( + molecular_formula=molecular_formula, + thickness=10, + solvent=d2o, + solvent_fraction=0.2, + area_per_molecule=48, + roughness=3, + name='PC Layer', +) +``` + +It is expected that the typical user will not interface directly with +the `LayerAreaPerMolecule` assembly type, but instead the +`SurfactantLayer` assemblies library will be used instead. diff --git a/docs/docs/tutorials/basic/material_library.md b/docs/docs/tutorials/basic/material_library.md new file mode 100644 index 00000000..78bdfcf1 --- /dev/null +++ b/docs/docs/tutorials/basic/material_library.md @@ -0,0 +1,78 @@ +# Defining Materials + +In order to support a wide range of applications (and to build complex +assemblies) there are a few different types of material that can be +utilised in EasyReflectometry. These can include constraints or enable +the user to define the material based on chemical or physical +properties. Full API documentation for the +`easyreflectometry.sample.elements.material` module is also available, +but here we will give some simple uses for them. + +## Material + +The simplest type of material that is available is the `Material`. This +allows the user to define a single type of material, with a real and +imaginary component to the scattering length density. The construction +of a `Material` is achieved as shown below. + +```python +from easyreflectometry.sample import Material + +boron = Material(sld=6.908, isld=-0.278, name='Boron') +``` + +The above object will have the properties of `sld` and `isld`, which +will have values of `6.908 1/angstrom^2` and `-0.278 1/angstrom^2` +respectively. As is shown in the tutorials, a material can be used to +construct a `Layer` from which +[slab models](https://www.reflectometry.org/isis_school/3_reflectometry_slab_models/the_slab_model.html) +are created. + +## MaterialDensity + +In addition to defining a material by its scattering length density, it +may be useful to define a material by the mass density and chemical +formula. This is possible with the `MaterialDensity` material type, +which uses the scattering length and atomic mass from the chemical +formula and the density to determine the scattering length density. It +is then possible to vary the density, which defines the scattering +length density in turn. The `MaterialDensity` material can be created as +follows. + +```python +from easyreflectometry.sample import MaterialDensity + +chemical_structure = 'SiO2' +si = MaterialDensity(chemical_structure=chemical_structure, density=2.65, name='SiO2 Material') +``` + +The density should be in units of grams per cubic centimeter and the +scattering length is calculated from `'SiO2'`. + +## MaterialSolvated + +Sometimes it is desirable to have a layer that consists of a material +and a solvent in some ratio. An example of this is shown in the +solvation tutorial, where a polymer film solvated with D2O is modelled. +To produce a material that is described by such a mixture, there is +`MaterialSolvated`. This is constructed from two constituent `Materials` +and the fractional amount of the material in the solvent. So to produce +a `MaterialSolvated` that is 20% D2O in a polymer, the following is +used. + +```python +from easyreflectometry.sample import Material +from easyreflectometry.sample import MaterialSolvated + +polymer = Material(sld=2.0, isld=0.0, name='Polymer') +d2o = Material(sld=6.36, isld=0, name='D2O') + +solvated_polymer = MaterialSolvated(material=polymer, solvent=d2o, solvent_fraction=0.2, name='Solvated Polymer') +``` + +For the `solvated_polymer` object, the `sld` will be +`2.872 1/angstrom^2` (the weighted average of the two scattering length +densities). The `MaterialSolvated` includes a constraint such that if +the value of either constituent scattering length densities (both real +and imaginary components) or the fraction changes, then the resulting +material `sld` and `isld` will change appropriately. diff --git a/docs/docs/tutorials/basic/model.md b/docs/docs/tutorials/basic/model.md new file mode 100644 index 00000000..6b9c76c3 --- /dev/null +++ b/docs/docs/tutorials/basic/model.md @@ -0,0 +1,87 @@ +# Creating a Model + +The main component of an experiment in EasyReflectometry is the `Model`. +This is a description of the `Sample` and the environment in which the +experiment is performed. The `Model` is used to calculate the +reflectivity of the `Sample` at a given set of angles (Q-points). The +resolution functions are used to quantify the experimental uncertainties +in wavelength and angle, allowing the `Model` to accurately describe the +data. + +## Model + +A `Model` instance contains a `Sample` and variables describing +experimental settings. To be able to compute reflectivities it is also +necessary to have a `Calculator` (interface). + +```python +from easyreflectometry.calculators import CalculatorFactory +from easyreflectometry.model import Model +from easyreflectometry.sample import Sample + +default_sample = Sample() +model = Model(sample=default_sample, scale=1.0, background=1e-6) + +interface = CalculatorFactory() +model.interface = interface +``` + +This will create a `Model` instance with the `default_sample` and the +environment variables `scale` factor set to 1.0 and a `background` of +1e-6. Following the `interface` is set to the default calculator that is +`Refnx`. + +## Resolution Functions + +A resolution function enables the EasyReflectometry model to incorporate +the experimental uncertainties in wavelength and incident angle into the +model. In its essence the resolution function controls the smearing to +apply when determining the reflectivity at a given Q-point. For a given +Q-point the smearing to apply is given as a weighted average of the +neighboring Q-point, which weights are by a normal distribution. This +normal distribution is then defined by a Q-point dependent Full Width at +the Half Maximum (FWHM) that is given by the resolution function. + +### PercentageFwhm + +Often we rely on a resolution function that has a simple functional +dependency of the Q-point. By this is understood that the applied +smearing in a Q-point has a FWHM that is simply a percentage of the +value of the Q-point. + +```python +from easyreflectometry.model import Model +from easyreflectometry.model import PercentageFwhm + +resolution_function = PercentageFwhm(1.1) + +m = Model(resolution_function=resolution_function) +``` + +This will create a `Model` instance where the resolution function is +defined as 1.1% of the Q-point value, which again is the FWHM for the +smearing. + +### LinearSpline + +Alternatively the FWHM value might be determined and declared directly +for each measured Q-point. When this is the case the provided Q-points +and the corresponding FWHM values can be used to declare a linear spline +function and thereby enable a determination of the reflectivity at an +arbitrary point within the provided range of discrete Q-points. + +```python +from easyreflectometry.model import Model +from easyreflectometry.model import LinearSpline + +m = Model() + +resolution_function = LinearSpline(q_data_points=[0.01, 0.2, 0.31], fwhm_values=[0.001, 0.043, 0.026]) + +m.resolution_function = resolution_function +``` + +This will create a `Model` instance where the resolution function +defining the FWHM is determined from a linear interpolation. In the +present case the provided data Q-points are (`[0.01, 0.2, 0.31]`) and +the corresponding FWHM function values are (`[0.001, 0.043, 0.026]`). diff --git a/docs/src/tutorials/fitting/d70d2o.ort b/docs/docs/tutorials/fitting/d70d2o.ort similarity index 100% rename from docs/src/tutorials/fitting/d70d2o.ort rename to docs/docs/tutorials/fitting/d70d2o.ort diff --git a/docs/src/tutorials/fitting/dspc.png b/docs/docs/tutorials/fitting/dspc.png similarity index 100% rename from docs/src/tutorials/fitting/dspc.png rename to docs/docs/tutorials/fitting/dspc.png diff --git a/docs/src/tutorials/fitting/eq_monolayer.svg b/docs/docs/tutorials/fitting/eq_monolayer.svg similarity index 100% rename from docs/src/tutorials/fitting/eq_monolayer.svg rename to docs/docs/tutorials/fitting/eq_monolayer.svg diff --git a/docs/src/tutorials/fitting/eq_solvated.svg b/docs/docs/tutorials/fitting/eq_solvated.svg similarity index 100% rename from docs/src/tutorials/fitting/eq_solvated.svg rename to docs/docs/tutorials/fitting/eq_solvated.svg diff --git a/docs/src/tutorials/fitting/example.ort b/docs/docs/tutorials/fitting/example.ort similarity index 100% rename from docs/src/tutorials/fitting/example.ort rename to docs/docs/tutorials/fitting/example.ort diff --git a/docs/src/tutorials/fitting/material_solvated.ipynb b/docs/docs/tutorials/fitting/material_solvated.ipynb similarity index 93% rename from docs/src/tutorials/fitting/material_solvated.ipynb rename to docs/docs/tutorials/fitting/material_solvated.ipynb index 4665e87b..d393eb34 100644 --- a/docs/src/tutorials/fitting/material_solvated.ipynb +++ b/docs/docs/tutorials/fitting/material_solvated.ipynb @@ -23,6 +23,16 @@ "First configure matplotlib to place figures in notebook and import needed modules. Note that the plot function needs installation of `plopp` seperately or installation of `easyreflectometry[dev]`" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "0143544a", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": null, @@ -30,26 +40,23 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", "import numpy as np\n", - "import scipp as sc\n", "import pooch\n", "import refnx\n", + "import scipp as sc\n", "\n", "import easyreflectometry\n", - "\n", + "from easyreflectometry.calculators import CalculatorFactory\n", "from easyreflectometry.data import load\n", + "from easyreflectometry.fitting import MultiFitter\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import PercentageFwhm\n", + "from easyreflectometry.plot import plot\n", "from easyreflectometry.sample import Layer\n", - "from easyreflectometry.sample import Sample\n", "from easyreflectometry.sample import Material\n", "from easyreflectometry.sample import MaterialSolvated\n", "from easyreflectometry.sample import Multilayer\n", - "from easyreflectometry.model import Model\n", - "from easyreflectometry.model import PercentageFwhm\n", - "from easyreflectometry.calculators import CalculatorFactory\n", - "from easyreflectometry.fitting import MultiFitter\n", - "from easyreflectometry.plot import plot" + "from easyreflectometry.sample import Sample" ] }, { @@ -92,8 +99,8 @@ "source": [ "file_path = pooch.retrieve(\n", " # URL to one of Pooch's test files\n", - " url=\"https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/example.ort\",\n", - " known_hash=\"82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab\",\n", + " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/example.ort',\n", + " known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n", ")\n", "data = load(file_path)" ] @@ -137,12 +144,7 @@ "metadata": {}, "outputs": [], "source": [ - "solvated_film_material = MaterialSolvated(\n", - " material=film,\n", - " solvent=d2o,\n", - " solvent_fraction=0.25,\n", - " name='Solvated Film'\n", - ")" + "solvated_film_material = MaterialSolvated(material=film, solvent=d2o, solvent_fraction=0.25, name='Solvated Film')" ] }, { @@ -196,13 +198,7 @@ "\n", "resolution_function = PercentageFwhm(0.02)\n", "sample = Sample(superphase, Multilayer(solvated_film), Multilayer(subphase), name='Film Structure')\n", - "model = Model(\n", - " sample=sample,\n", - " scale=1,\n", - " background=1e-6,\n", - " resolution_function=resolution_function,\n", - " name='Film Model'\n", - ")" + "model = Model(sample=sample, scale=1, background=1e-6, resolution_function=resolution_function, name='Film Model')" ] }, { diff --git a/docs/src/tutorials/fitting/monolayer.ipynb b/docs/docs/tutorials/fitting/monolayer.ipynb similarity index 94% rename from docs/src/tutorials/fitting/monolayer.ipynb rename to docs/docs/tutorials/fitting/monolayer.ipynb index d0ef8aa0..e4a7b06d 100644 --- a/docs/src/tutorials/fitting/monolayer.ipynb +++ b/docs/docs/tutorials/fitting/monolayer.ipynb @@ -20,6 +20,16 @@ "First configure matplotlib to place figures in notebook and import needed modules. Note that the plot function needs installation of `plopp` seperately or installation of `easyreflectometry[dev]`" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "ece9d352", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": null, @@ -27,27 +37,22 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", - "import refnx\n", "import pooch\n", + "import refnx\n", "\n", "import easyreflectometry\n", - "\n", "from easyreflectometry.calculators import CalculatorFactory\n", "from easyreflectometry.data import load\n", + "from easyreflectometry.fitting import MultiFitter\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import PercentageFwhm\n", "from easyreflectometry.plot import plot\n", - "from easyreflectometry.sample import Material\n", - "from easyreflectometry.sample import SurfactantLayer\n", - "from easyreflectometry.sample import LayerAreaPerMolecule\n", "from easyreflectometry.sample import Layer\n", + "from easyreflectometry.sample import LayerAreaPerMolecule\n", + "from easyreflectometry.sample import Material\n", "from easyreflectometry.sample import Multilayer\n", "from easyreflectometry.sample import Sample\n", - "from easyreflectometry.model import Model\n", - "from easyreflectometry.model import PercentageFwhm\n", - "from easyreflectometry.fitting import MultiFitter\n", - "from easyreflectometry.plot import plot\n", - "from easyscience.fitting import AvailableMinimizers\n" + "from easyreflectometry.sample import SurfactantLayer" ] }, { @@ -90,8 +95,8 @@ "source": [ "file_path = pooch.retrieve(\n", " # URL to one of Pooch's test files\n", - " url=\"https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/d70d2o.ort\",\n", - " known_hash=\"3e4750536621be8eec493fa21a287e408d384f29cacb113b71d02690d99f0998\",\n", + " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/d70d2o.ort',\n", + " known_hash='3e4750536621be8eec493fa21a287e408d384f29cacb113b71d02690d99f0998',\n", ")\n", "data = load(file_path)\n", "plot(data)" @@ -254,22 +259,19 @@ " molecular_formula=tail_formula,\n", " thickness=tail_thickness,\n", " solvent=air,\n", - " solvent_fraction=tail_solvent_fraction, \n", + " solvent_fraction=tail_solvent_fraction,\n", " area_per_molecule=area_per_molecule,\n", - " roughness=roughness\n", + " roughness=roughness,\n", ")\n", "head_layer = LayerAreaPerMolecule(\n", " molecular_formula=head_formula,\n", " thickness=head_thickness,\n", " solvent=d2o,\n", - " solvent_fraction=head_solvent_fraction, \n", + " solvent_fraction=head_solvent_fraction,\n", " area_per_molecule=area_per_molecule,\n", - " roughness=roughness\n", - ")\n", - "dspc = SurfactantLayer(\n", - " tail_layer=tail_layer,\n", - " head_layer=head_layer\n", + " roughness=roughness,\n", ")\n", + "dspc = SurfactantLayer(tail_layer=tail_layer, head_layer=head_layer)\n", "dspc.constrain_area_per_molecule = True\n", "dspc.conformal_roughness = True\n", "dspc" @@ -330,12 +332,7 @@ "source": [ "resolution_function = PercentageFwhm(5)\n", "sample = Sample(Multilayer(air_layer), dspc, Multilayer(d2o_layer))\n", - "model = Model(\n", - " sample=sample,\n", - " scale=1,\n", - " background=data['data']['R_0'].values.min(),\n", - " resolution_function=resolution_function\n", - ")" + "model = Model(sample=sample, scale=1, background=data['data']['R_0'].values.min(), resolution_function=resolution_function)" ] }, { diff --git a/docs/src/tutorials/fitting/monolayer.png b/docs/docs/tutorials/fitting/monolayer.png similarity index 100% rename from docs/src/tutorials/fitting/monolayer.png rename to docs/docs/tutorials/fitting/monolayer.png diff --git a/docs/src/tutorials/fitting/monolayer.svg b/docs/docs/tutorials/fitting/monolayer.svg similarity index 100% rename from docs/src/tutorials/fitting/monolayer.svg rename to docs/docs/tutorials/fitting/monolayer.svg diff --git a/docs/src/tutorials/fitting/polymer_film.png b/docs/docs/tutorials/fitting/polymer_film.png similarity index 100% rename from docs/src/tutorials/fitting/polymer_film.png rename to docs/docs/tutorials/fitting/polymer_film.png diff --git a/docs/src/tutorials/fitting/polymer_film.svg b/docs/docs/tutorials/fitting/polymer_film.svg similarity index 100% rename from docs/src/tutorials/fitting/polymer_film.svg rename to docs/docs/tutorials/fitting/polymer_film.svg diff --git a/docs/src/tutorials/fitting/repeating.ipynb b/docs/docs/tutorials/fitting/repeating.ipynb similarity index 94% rename from docs/src/tutorials/fitting/repeating.ipynb rename to docs/docs/tutorials/fitting/repeating.ipynb index 0ebde2e1..e062ac5d 100644 --- a/docs/src/tutorials/fitting/repeating.ipynb +++ b/docs/docs/tutorials/fitting/repeating.ipynb @@ -24,6 +24,16 @@ "First configure matplotlib to place figures in notebook and import needed modules. Note that the plot function needs installation of `plopp` seperately or installation of `easyreflectometry[dev]`" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "419add7b", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": null, @@ -31,27 +41,23 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", "import numpy as np\n", - "import scipp as sc\n", "import pooch\n", "import refl1d\n", + "import scipp as sc\n", "\n", "import easyreflectometry\n", - "\n", + "from easyreflectometry.calculators import CalculatorFactory\n", "from easyreflectometry.data import load\n", - "from easyreflectometry.sample import Layer\n", - "from easyreflectometry.sample import Sample\n", - "from easyreflectometry.sample import Material\n", - "from easyreflectometry.sample import RepeatingMultilayer\n", - "from easyreflectometry.sample import Multilayer\n", + "from easyreflectometry.fitting import MultiFitter\n", "from easyreflectometry.model import Model\n", "from easyreflectometry.model import PercentageFwhm\n", - "from easyreflectometry.calculators import CalculatorFactory\n", - "from easyreflectometry.fitting import MultiFitter\n", "from easyreflectometry.plot import plot\n", - "from easyscience.fitting import AvailableMinimizers" + "from easyreflectometry.sample import Layer\n", + "from easyreflectometry.sample import Material\n", + "from easyreflectometry.sample import Multilayer\n", + "from easyreflectometry.sample import RepeatingMultilayer\n", + "from easyreflectometry.sample import Sample" ] }, { @@ -95,8 +101,8 @@ "source": [ "file_path = pooch.retrieve(\n", " # URL to one of Pooch's test files\n", - " url=\"https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/repeating_layers.ort\",\n", - " known_hash=\"a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90\",\n", + " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/repeating_layers.ort',\n", + " known_hash='a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90',\n", ")\n", "data = load(file_path)" ] @@ -203,13 +209,7 @@ "source": [ "resolution_function = PercentageFwhm(0)\n", "sample = Sample(Multilayer(superphase), rep_multilayer, Multilayer(subphase), name='Multilayer Structure')\n", - "model = Model(\n", - " sample=sample,\n", - " scale=1,\n", - " background=0,\n", - " resolution_function=resolution_function,\n", - " name='Multilayer Model'\n", - ")" + "model = Model(sample=sample, scale=1, background=0, resolution_function=resolution_function, name='Multilayer Model')" ] }, { diff --git a/docs/src/tutorials/fitting/repeating.png b/docs/docs/tutorials/fitting/repeating.png similarity index 100% rename from docs/src/tutorials/fitting/repeating.png rename to docs/docs/tutorials/fitting/repeating.png diff --git a/docs/src/tutorials/fitting/repeating.svg b/docs/docs/tutorials/fitting/repeating.svg similarity index 100% rename from docs/src/tutorials/fitting/repeating.svg rename to docs/docs/tutorials/fitting/repeating.svg diff --git a/docs/src/tutorials/fitting/repeating_layers.ort b/docs/docs/tutorials/fitting/repeating_layers.ort similarity index 100% rename from docs/src/tutorials/fitting/repeating_layers.ort rename to docs/docs/tutorials/fitting/repeating_layers.ort diff --git a/docs/src/tutorials/fitting/simple_fitting.ipynb b/docs/docs/tutorials/fitting/simple_fitting.ipynb similarity index 96% rename from docs/src/tutorials/fitting/simple_fitting.ipynb rename to docs/docs/tutorials/fitting/simple_fitting.ipynb index ea761e75..8eea570a 100644 --- a/docs/src/tutorials/fitting/simple_fitting.ipynb +++ b/docs/docs/tutorials/fitting/simple_fitting.ipynb @@ -21,6 +21,16 @@ "Note that the plot function needs installation of `plopp` seperately or installation of `easyreflectometry[dev]`" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "38b12098", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": null, @@ -28,25 +38,22 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", "import matplotlib.pyplot as plt\n", "import pooch\n", "import refl1d\n", "import refnx\n", "\n", "import easyreflectometry\n", - "\n", + "from easyreflectometry.calculators import CalculatorFactory\n", "from easyreflectometry.data import load\n", + "from easyreflectometry.fitting import MultiFitter\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import PercentageFwhm\n", + "from easyreflectometry.plot import plot\n", "from easyreflectometry.sample import Layer\n", - "from easyreflectometry.sample import Sample\n", "from easyreflectometry.sample import Material\n", "from easyreflectometry.sample import Multilayer\n", - "from easyreflectometry.model import Model\n", - "from easyreflectometry.model import PercentageFwhm\n", - "from easyreflectometry.calculators import CalculatorFactory\n", - "from easyreflectometry.fitting import MultiFitter\n", - "from easyreflectometry.plot import plot" + "from easyreflectometry.sample import Sample" ] }, { @@ -90,8 +97,8 @@ "source": [ "file_path = pooch.retrieve(\n", " # URL to one of Pooch's test files\n", - " url=\"https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/example.ort\",\n", - " known_hash=\"82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab\",\n", + " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/example.ort',\n", + " known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n", ")\n", "data = load(file_path)" ] @@ -311,13 +318,7 @@ "outputs": [], "source": [ "resolution_function = PercentageFwhm(0.02)\n", - "model = Model(\n", - " sample=sample,\n", - " scale=1,\n", - " background=1e-6,\n", - " resolution_function=resolution_function,\n", - " name='Film Model'\n", - ")" + "model = Model(sample=sample, scale=1, background=1e-6, resolution_function=resolution_function, name='Film Model')" ] }, { @@ -552,11 +553,7 @@ "\n", "resolution_function_refl1d = PercentageFwhm(0.02)\n", "model_refl1d = Model(\n", - " sample=sample_refl1d,\n", - " scale=1,\n", - " background=1e-6,\n", - " resolution_function=resolution_function_refl1d,\n", - " name='Film Model (Refl1D)'\n", + " sample=sample_refl1d, scale=1, background=1e-6, resolution_function=resolution_function_refl1d, name='Film Model (Refl1D)'\n", ")\n", "\n", "sio2_layer_refl1d.thickness.bounds = (15, 50)\n", @@ -598,7 +595,7 @@ "\n", "qz = data['coords']['Qz_0'].values\n", "reflectivity = data['data']['R_0'].values\n", - "uncertainty = data['data']['R_0'].variances**0.5\n", + "uncertainty = data['data']['R_0'].variances ** 0.5\n", "\n", "plt.figure(figsize=(8, 5))\n", "plt.errorbar(qz, reflectivity, yerr=uncertainty, fmt='o', markersize=3, color='black', alpha=0.6, label='Data')\n", diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md new file mode 100644 index 00000000..7b59c331 --- /dev/null +++ b/docs/docs/tutorials/index.md @@ -0,0 +1,56 @@ +--- +icon: material/school +--- + +# :material-school: Tutorials + +This section presents a collection of **Jupyter Notebook** tutorials +that demonstrate how to use EasyReflectometry for various tasks. These +tutorials serve as self-contained, step-by-step **guides** to help users +grasp the workflow of data analysis using EasyReflectometry. + +Instructions on how to run the tutorials are provided in the +[:material-cog-box: Installation & Setup](../installation-and-setup/index.md#how-to-run-tutorials) +section of the documentation. + +## Getting Started + +- [Creating a Model](basic/model.md) – Learn how to define a + reflectometry model with sample, scale, background, and resolution + functions. +- [Defining Materials](basic/material_library.md) – Explore different + material types: `Material`, `MaterialDensity`, `MaterialSolvated`, and + `MaterialMixture`. +- [Defining Layers](basic/layer_library.md) – Understand layer types + including `Layer` and `LayerAreaPerMolecule`. +- [Creating Assemblies](basic/assemblies_library.md) – Build complex + structures with `Multilayer`, `RepeatingMultilayer`, and + `SurfactantLayer`. + +## Simulation + +These are basic simulation examples using the EasyReflectometry library. + +- [Bilayer Simulation](simulation/bilayer.ipynb) +- [Magnetism Simulation](simulation/magnetism.ipynb) +- [Resolution Functions](simulation/resolution_functions.ipynb) + +## Fitting + +These are basic fitting examples using the EasyReflectometry library. + +- [Simple Fitting](fitting/simple_fitting.ipynb) +- [Repeating Multilayer Fitting](fitting/repeating.ipynb) +- [Monolayer Fitting](fitting/monolayer.ipynb) +- [Solvated Material Fitting](fitting/material_solvated.ipynb) + +## Advanced Fitting + +These are advanced fitting examples using the EasyReflectometry library. + +- [Multi-Contrast Fitting](advancedfitting/multi_contrast.ipynb) + +## Extra + +Additional examples and supplementary material using the +EasyReflectometry library. diff --git a/docs/src/tutorials/simulation/bilayer.ipynb b/docs/docs/tutorials/simulation/bilayer.ipynb similarity index 95% rename from docs/src/tutorials/simulation/bilayer.ipynb rename to docs/docs/tutorials/simulation/bilayer.ipynb index 3d7faccd..b16b8284 100644 --- a/docs/src/tutorials/simulation/bilayer.ipynb +++ b/docs/docs/tutorials/simulation/bilayer.ipynb @@ -32,6 +32,16 @@ "First, we import the necessary modules and configure matplotlib for inline plotting." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d213b99", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": null, @@ -39,21 +49,18 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", - "import numpy as np\n", "import matplotlib.pyplot as plt\n", + "import numpy as np\n", "\n", - "import easyreflectometry\n", "from easyreflectometry.calculators import CalculatorFactory\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import PercentageFwhm\n", "from easyreflectometry.sample import Bilayer\n", + "from easyreflectometry.sample import Layer\n", "from easyreflectometry.sample import LayerAreaPerMolecule\n", "from easyreflectometry.sample import Material\n", - "from easyreflectometry.sample import Layer\n", "from easyreflectometry.sample import Multilayer\n", - "from easyreflectometry.sample import Sample\n", - "from easyreflectometry.model import Model\n", - "from easyreflectometry.model import PercentageFwhm" + "from easyreflectometry.sample import Sample" ] }, { @@ -105,7 +112,7 @@ " solvent_fraction=0.3, # 30% solvent in head region\n", " area_per_molecule=48.2,\n", " roughness=3.0,\n", - " name='DPPC Head'\n", + " name='DPPC Head',\n", ")\n", "\n", "# Create a tail layer for the bilayer\n", @@ -117,7 +124,7 @@ " solvent_fraction=0.0, # No solvent in the tail region\n", " area_per_molecule=48.2,\n", " roughness=3.0,\n", - " name='DPPC Tail'\n", + " name='DPPC Tail',\n", ")" ] }, @@ -145,9 +152,9 @@ "bilayer = Bilayer(\n", " front_head_layer=head_layer,\n", " front_tail_layer=front_tail_layer,\n", - " constrain_heads=True, # Head layers share thickness and area per molecule\n", + " constrain_heads=True, # Head layers share thickness and area per molecule\n", " conformal_roughness=True, # All layers share the same roughness\n", - " name='DPPC Bilayer'\n", + " name='DPPC Bilayer',\n", ")\n", "\n", "print(bilayer)" @@ -222,7 +229,7 @@ "\n", "# We can set them independently\n", "bilayer.back_head_layer.solvent_fraction = 0.5\n", - "print(f'\\nAfter setting back head solvent fraction to 0.5:')\n", + "print('\\nAfter setting back head solvent fraction to 0.5:')\n", "print(f'Front head solvent fraction: {bilayer.front_head_layer.solvent_fraction:.2f}')\n", "print(f'Back head solvent fraction: {bilayer.back_head_layer.solvent_fraction:.2f}')" ] @@ -290,7 +297,7 @@ " Multilayer(sio2_layer, name='SiO2'),\n", " bilayer,\n", " Multilayer(d2o_subphase, name='D2O Subphase'),\n", - " name='Bilayer on Si/SiO2'\n", + " name='Bilayer on Si/SiO2',\n", ")\n", "\n", "print(sample)" @@ -314,13 +321,7 @@ "outputs": [], "source": [ "# Create the model\n", - "model = Model(\n", - " sample=sample,\n", - " scale=1.0,\n", - " background=1e-7,\n", - " resolution_function=PercentageFwhm(5),\n", - " name='Bilayer Model'\n", - ")\n", + "model = Model(sample=sample, scale=1.0, background=1e-7, resolution_function=PercentageFwhm(5), name='Bilayer Model')\n", "\n", "# Set up the calculator\n", "interface = CalculatorFactory()\n", @@ -451,7 +452,7 @@ "\n", "# Now set asymmetric hydration (common in supported bilayers)\n", "bilayer.front_head_layer.solvent_fraction = 0.1 # Substrate side - less hydrated\n", - "bilayer.back_head_layer.solvent_fraction = 0.4 # Solution side - more hydrated\n", + "bilayer.back_head_layer.solvent_fraction = 0.4 # Solution side - more hydrated\n", "\n", "# Get asymmetric SLD profile\n", "z_asym, sld_asym = model.interface().sld_profile(model.unique_name)\n", @@ -502,7 +503,7 @@ " solvent_fraction=0.3,\n", " area_per_molecule=48.2,\n", " roughness=3.0,\n", - " name='DPPC Head H2O'\n", + " name='DPPC Head H2O',\n", ")\n", "\n", "# Create tail layer for H2O contrast (same deuterated lipid, different solvent)\n", @@ -513,7 +514,7 @@ " solvent_fraction=0.0,\n", " area_per_molecule=48.2,\n", " roughness=3.0,\n", - " name='DPPC Tail H2O'\n", + " name='DPPC Tail H2O',\n", ")\n", "\n", "# Create H2O bilayer\n", @@ -522,7 +523,7 @@ " front_tail_layer=tail_layer_h2o,\n", " constrain_heads=True,\n", " conformal_roughness=True,\n", - " name='DPPC Bilayer H2O'\n", + " name='DPPC Bilayer H2O',\n", ")" ] }, @@ -568,16 +569,12 @@ " Multilayer(sio2_layer, name='SiO2'),\n", " bilayer_h2o,\n", " Multilayer(h2o_subphase, name='H2O Subphase'),\n", - " name='Bilayer on Si/SiO2 in H2O'\n", + " name='Bilayer on Si/SiO2 in H2O',\n", ")\n", "\n", "# Create model for H2O contrast\n", "model_h2o = Model(\n", - " sample=sample_h2o,\n", - " scale=1.0,\n", - " background=1e-7,\n", - " resolution_function=PercentageFwhm(5),\n", - " name='Bilayer Model H2O'\n", + " sample=sample_h2o, scale=1.0, background=1e-7, resolution_function=PercentageFwhm(5), name='Bilayer Model H2O'\n", ")\n", "model_h2o.interface = interface" ] diff --git a/docs/src/tutorials/simulation/magnetism.ipynb b/docs/docs/tutorials/simulation/magnetism.ipynb similarity index 87% rename from docs/src/tutorials/simulation/magnetism.ipynb rename to docs/docs/tutorials/simulation/magnetism.ipynb index 1f35fdf1..8efdb9e4 100644 --- a/docs/src/tutorials/simulation/magnetism.ipynb +++ b/docs/docs/tutorials/simulation/magnetism.ipynb @@ -20,6 +20,16 @@ "First configure matplotlib to place figures in notebook and import needed modules" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "644e53e3", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": null, @@ -27,24 +37,21 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "import scipp as sc\n", "import refl1d\n", "import refl1d.names\n", + "import scipp as sc\n", "\n", "import easyreflectometry\n", - "\n", "from easyreflectometry.calculators import CalculatorFactory\n", + "from easyreflectometry.calculators.refl1d.wrapper import _get_polarized_probe\n", "from easyreflectometry.model import Model\n", "from easyreflectometry.model import PercentageFwhm\n", "from easyreflectometry.sample import Layer\n", "from easyreflectometry.sample import Material\n", "from easyreflectometry.sample import Multilayer\n", - "from easyreflectometry.sample import Sample\n", - "from easyreflectometry.calculators.refl1d.wrapper import _get_polarized_probe" + "from easyreflectometry.sample import Sample" ] }, { @@ -150,18 +157,13 @@ "metadata": {}, "outputs": [], "source": [ - "refl1d_sld_4 = refl1d.names.SLD(name=\"Sld 4\", rho=4.0, irho=0)\n", - "refl1d_sld_8 = refl1d.names.SLD(name=\"Sld 8\", rho=8.0, irho=0)\n", - "refl1d_vacuum = refl1d.names.SLD(name=\"Vacuum\", rho=0, irho=0)\n", - "refl1d_si = refl1d.names.SLD(name=\"Si\", rho=2.047, irho=0)\n", + "refl1d_sld_4 = refl1d.names.SLD(name='Sld 4', rho=4.0, irho=0)\n", + "refl1d_sld_8 = refl1d.names.SLD(name='Sld 8', rho=8.0, irho=0)\n", + "refl1d_vacuum = refl1d.names.SLD(name='Vacuum', rho=0, irho=0)\n", + "refl1d_si = refl1d.names.SLD(name='Si', rho=2.047, irho=0)\n", "\n", "# Refl1d model is inverted as compared to EasyReflectometry, so the order of the layers is reversed\n", - "refl1d_sample = (\n", - " refl1d_si(0, 0) | \n", - " refl1d_sld_8(150, 0) |\n", - " refl1d_sld_4(100, 0) | \n", - " refl1d_vacuum(0, 0)\n", - ") " + "refl1d_sample = refl1d_si(0, 0) | refl1d_sld_8(150, 0) | refl1d_sld_4(100, 0) | refl1d_vacuum(0, 0)" ] }, { @@ -206,6 +208,7 @@ " num=1000,\n", ")\n", "\n", + "\n", "def plot_apply_makeup():\n", " ax = plt.gca()\n", " ax.set_xlim([-0.01, 0.35])\n", @@ -233,11 +236,11 @@ "source": [ "# Refl1d\n", "probe = refl1d.names.QProbe(\n", - " Q=model_coords,\n", - " dQ=np.zeros(len(model_coords)),\n", - " intensity=1,\n", - " background=0,\n", - " )\n", + " Q=model_coords,\n", + " dQ=np.zeros(len(model_coords)),\n", + " intensity=1,\n", + " background=0,\n", + ")\n", "experiment = refl1d.names.Experiment(probe=probe, sample=refl1d_sample)\n", "model_data_no_magnetism_ref1d_raw = experiment.reflectivity()[1]\n", "\n", @@ -253,7 +256,9 @@ " model_coords,\n", " model.unique_name,\n", ")\n", - "plt.plot(model_coords, model_data_no_magnetism_ref1d_easy, 'r-', label=f'EasyReflectometry ({model_interface.name})', linewidth=2)\n", + "plt.plot(\n", + " model_coords, model_data_no_magnetism_ref1d_easy, 'r-', label=f'EasyReflectometry ({model_interface.name})', linewidth=2\n", + ")\n", "\n", "plot_apply_makeup()" ] @@ -290,8 +295,12 @@ "model.interface = interface\n", "model_interface = model.interface()\n", "model_interface.include_magnetism = True\n", - "model_interface._wrapper.update_layer(list(model_interface._wrapper.storage['layer'].keys())[1], magnetism_rhoM=10, magnetism_thetaM=70)\n", - "model_interface._wrapper.update_layer(list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175)\n", + "model_interface._wrapper.update_layer(\n", + " list(model_interface._wrapper.storage['layer'].keys())[1], magnetism_rhoM=10, magnetism_thetaM=70\n", + ")\n", + "model_interface._wrapper.update_layer(\n", + " list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175\n", + ")\n", "model_data_magnetism_layer_1 = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", @@ -327,21 +336,19 @@ "source": [ "# Refl1d model is inverted as compared to EasyReflectometry, so the order of the layers is reversed\n", "refl1d_sample = (\n", - " refl1d_si(0, 0) | \n", - " refl1d_sld_8(150, 0, magnetism=refl1d.names.Magnetism(rhoM=5, thetaM=175)) |\n", - " refl1d_sld_4(100, 0, magnetism=refl1d.names.Magnetism(rhoM=10, thetaM=70)) | \n", - " refl1d_vacuum(0, 0)\n", - ") \n", + " refl1d_si(0, 0)\n", + " | refl1d_sld_8(150, 0, magnetism=refl1d.names.Magnetism(rhoM=5, thetaM=175))\n", + " | refl1d_sld_4(100, 0, magnetism=refl1d.names.Magnetism(rhoM=10, thetaM=70))\n", + " | refl1d_vacuum(0, 0)\n", + ")\n", "model_name = model.unique_name\n", "storage = {'model': {model_name: {}}}\n", "storage['model'][model_name]['scale'] = 10.0\n", "storage['model'][model_name]['bkg'] = 20.0\n", "\n", "polarized_probe = _get_polarized_probe(\n", - " q_array=model_coords,\n", - " dq_array=np.zeros(len(model_coords)),\n", - " model_name=model_name,\n", - " storage=storage)\n", + " q_array=model_coords, dq_array=np.zeros(len(model_coords)), model_name=model_name, storage=storage\n", + ")\n", "\n", "experiment = refl1d.names.Experiment(probe=polarized_probe, sample=refl1d_sample)\n", "model_data_magnetism_ref1d = experiment.reflectivity()[0][1]\n", @@ -352,8 +359,12 @@ "model.interface = interface\n", "model_interface = model.interface()\n", "model_interface.include_magnetism = True\n", - "model_interface._wrapper.update_layer(list(model_interface._wrapper.storage['layer'].keys())[1], magnetism_rhoM=10, magnetism_thetaM=70)\n", - "model_interface._wrapper.update_layer(list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175)\n", + "model_interface._wrapper.update_layer(\n", + " list(model_interface._wrapper.storage['layer'].keys())[1], magnetism_rhoM=10, magnetism_thetaM=70\n", + ")\n", + "model_interface._wrapper.update_layer(\n", + " list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175\n", + ")\n", "model_data_magnetism_easy = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", @@ -406,15 +417,14 @@ "metadata": {}, "outputs": [], "source": [ - "# The magnetism is set to 8. \n", + "# The magnetism is set to 8.\n", "# This would double (pp) and cancel out (mm) the magnitude of the reflectivity oscillations when its angle is set to 90.\n", "# This would give the strongest spin-flipping (pm and mp) when its angle is set to 0.\n", - "# However we set the angle to 45, so the reflectivity oscillations are not doubled or cancelled out, and the spin-flipping is not maximized.\n", + "# However we set the angle to 45, so the reflectivity oscillations are not doubled or cancelled out,\n", + "# and the spin-flipping is not maximized.\n", "refl1d_sample = (\n", - " refl1d_si(0, 0) | \n", - " refl1d_sld_8(150, 0, magnetism=refl1d.names.Magnetism(rhoM=8, thetaM=45)) |\n", - " refl1d_vacuum(0, 0)\n", - ") \n", + " refl1d_si(0, 0) | refl1d_sld_8(150, 0, magnetism=refl1d.names.Magnetism(rhoM=8, thetaM=45)) | refl1d_vacuum(0, 0)\n", + ")\n", "\n", "model_name = model.unique_name\n", "storage = {'model': {model_name: {}}}\n", @@ -422,13 +432,10 @@ "storage['model'][model_name]['bkg'] = 0.0\n", "\n", "polarized_probe = _get_polarized_probe(\n", - " q_array=model_coords,\n", - " dq_array=np.zeros(len(model_coords)),\n", - " model_name=model_name,\n", - " storage=storage,\n", - " all_polarizations=True)\n", + " q_array=model_coords, dq_array=np.zeros(len(model_coords)), model_name=model_name, storage=storage, all_polarizations=True\n", + ")\n", "\n", - "experiment = refl1d.names.Experiment(probe=polarized_probe, sample=refl1d_sample)\n" + "experiment = refl1d.names.Experiment(probe=polarized_probe, sample=refl1d_sample)" ] }, { diff --git a/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort b/docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort similarity index 100% rename from docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort rename to docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort diff --git a/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort b/docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort similarity index 100% rename from docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort rename to docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort diff --git a/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort b/docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort similarity index 100% rename from docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort rename to docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort diff --git a/docs/src/tutorials/simulation/resolution_functions.ipynb b/docs/docs/tutorials/simulation/resolution_functions.ipynb similarity index 90% rename from docs/src/tutorials/simulation/resolution_functions.ipynb rename to docs/docs/tutorials/simulation/resolution_functions.ipynb index 4fc42ad2..48a6893a 100644 --- a/docs/src/tutorials/simulation/resolution_functions.ipynb +++ b/docs/docs/tutorials/simulation/resolution_functions.ipynb @@ -24,6 +24,16 @@ "First configure matplotlib to place figures in notebook and import needed modules. Note that the plot function needs installation of `plopp` seperately or installation of `easyreflectometry[dev]`" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "deb2db0a", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": null, @@ -31,27 +41,24 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "import scipp as sc\n", - "import refnx\n", "import pooch\n", + "import refnx\n", + "import scipp as sc\n", "\n", "import easyreflectometry\n", - "\n", "from easyreflectometry.calculators import CalculatorFactory\n", "from easyreflectometry.data import load\n", - "from easyreflectometry.model import Model\n", "from easyreflectometry.model import LinearSpline\n", + "from easyreflectometry.model import Model\n", "from easyreflectometry.model import PercentageFwhm\n", "from easyreflectometry.model import Pointwise\n", + "from easyreflectometry.plot import plot\n", "from easyreflectometry.sample import Layer\n", "from easyreflectometry.sample import Material\n", "from easyreflectometry.sample import Multilayer\n", - "from easyreflectometry.sample import Sample\n", - "from easyreflectometry.plot import plot" + "from easyreflectometry.sample import Sample" ] }, { @@ -97,18 +104,18 @@ "source": [ "file_path_0 = pooch.retrieve(\n", " # URL to one of Pooch's test files\n", - " url=\"https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort\",\n", - " known_hash=\"f8a3e7007b83f0de4e2c761134e7d1c55027f0099528bd56f746b50349369f50\",\n", + " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort',\n", + " known_hash='f8a3e7007b83f0de4e2c761134e7d1c55027f0099528bd56f746b50349369f50',\n", ")\n", "file_path_1 = pooch.retrieve(\n", " # URL to one of Pooch's test files\n", - " url=\"https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort\",\n", - " known_hash=\"9d81a512cbe45f923806ad307e476b27535614b2e08a2bf0f4559ab608a34f7a\",\n", + " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort',\n", + " known_hash='9d81a512cbe45f923806ad307e476b27535614b2e08a2bf0f4559ab608a34f7a',\n", ")\n", "file_path_10 = pooch.retrieve(\n", " # URL to one of Pooch's test files\n", - " url=\"https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort\",\n", - " known_hash=\"991395c0b6a91bf60c12d234c645143dcac1cab929944fc4e452020d44b787ad\",\n", + " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort',\n", + " known_hash='991395c0b6a91bf60c12d234c645143dcac1cab929944fc4e452020d44b787ad',\n", ")\n", "dict_reference = {}\n", "dict_reference['0'] = load(file_path_0)\n", @@ -328,7 +335,7 @@ " model.unique_name,\n", " )\n", " plt.plot(model_coords, model_data, 'k-', label=f'Resolution: {key}%')\n", - " plt.plot(reference_coords, reference_data, 'rx', label=f'Reference')\n", + " plt.plot(reference_coords, reference_data, 'rx', label='Reference')\n", " ax = plt.gca()\n", " ax.set_xlim([-0.01, 0.45])\n", " ax.set_ylim([1e-10, 2.5])\n", @@ -377,14 +384,14 @@ " model_coords,\n", " model.unique_name,\n", ")\n", - "plt.plot(model_coords, model_data, 'k-', label=f'Variable', linewidth=5)\n", + "plt.plot(model_coords, model_data, 'k-', label='Variable', linewidth=5)\n", "\n", "model.resolution_function = PercentageFwhm(1.0)\n", "model_data = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", - "plt.plot(model_coords, model_data, 'r-', label=f'Percentage')\n", + "plt.plot(model_coords, model_data, 'r-', label='Percentage')\n", "\n", "ax = plt.gca()\n", "ax.set_xlim([-0.01, 0.45])\n", @@ -416,17 +423,17 @@ " model_coords,\n", " model.unique_name,\n", ")\n", - "plt.plot(model_coords, model_data, 'k-', label=f'Variable', linewidth=5)\n", + "plt.plot(model_coords, model_data, 'k-', label='Variable', linewidth=5)\n", "data_points = []\n", - "data_points.append(reference_coords) # Qz\n", - "data_points.append(reference_data) # R\n", - "data_points.append(reference_variances) # sQz\n", + "data_points.append(reference_coords) # Qz\n", + "data_points.append(reference_data) # R\n", + "data_points.append(reference_variances) # sQz\n", "model.resolution_function = Pointwise(q_data_points=data_points)\n", "model_data = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", - "plt.plot(model_coords, model_data, 'r-', label=f'Pointwise')\n", + "plt.plot(model_coords, model_data, 'r-', label='Pointwise')\n", "\n", "ax = plt.gca()\n", "ax.set_xlim([-0.01, 0.45])\n", diff --git a/docs/src/tutorials/simulation/two_layers.png b/docs/docs/tutorials/simulation/two_layers.png similarity index 100% rename from docs/src/tutorials/simulation/two_layers.png rename to docs/docs/tutorials/simulation/two_layers.png diff --git a/docs/src/tutorials/simulation/two_layers.svg b/docs/docs/tutorials/simulation/two_layers.svg similarity index 100% rename from docs/src/tutorials/simulation/two_layers.svg rename to docs/docs/tutorials/simulation/two_layers.svg diff --git a/docs/docs/user-guide/index.md b/docs/docs/user-guide/index.md new file mode 100644 index 00000000..fbdec8b4 --- /dev/null +++ b/docs/docs/user-guide/index.md @@ -0,0 +1,211 @@ +--- +icon: material/book-open-variant +--- + +# :material-book-open-variant: User Guide + +This section provides an overview of the **core concepts**, **key +parameters** and **workflow steps** required for using EasyReflectometry +effectively. + +## Glossary + +The following serves to clarify what we mean by the terms we use in this +project. + +### Sample + +A sample is an ideal representation of the full physical setup. This +includes the layer(s) under investigation, the surrounding superphase, +and the subphase. + +### Calculator + +A calculator is the physics engine which calculates the reflectivity +curve from our inputted sample parameters. We rely on third party +software to provide the necessary calculators. Different calculators +might have different capabilities and limitations. + +Currently, EasyReflectometry can offer two different calculation +engines: + +- [**refnx**](https://refnx.readthedocs.io/) +- [**Refl1D**](https://refl1d.readthedocs.io/en/latest/) + +And we are working to add more, in particular +[**BornAgain**](https://www.bornagainproject.org) and +[**GenX**](https://aglavic.github.io/genx/doc/). + +### Model + +A model combines a sample and calculator. The model is also responsible +for including instrumental effects such as background, scale, and +resolution. + +### Assemblies + +Assemblies are collections of layers that are used to represent a +specific physical setup. Examples include: + +- **Multilayer** – A series of layers grouped as a single object +- **RepeatingMultilayer** – A multilayer with a fixed number of repeats +- **SurfactantLayer** – A layer defined by area per molecule and + chemical formula +- **GradientLayer** – A layer with a graded scattering length density + profile + +### Elements + +Elements are the building blocks that are required to construct a +sample. + +**Layers** are basic elements used to represent a single layer of +material with a thickness and a roughness: + +- `Layer` – Standard layer with material, thickness, and roughness +- `LayerAreaPerMolecule` – Layer defined by area per molecule and + chemical formula + +**Materials** are the most basic elements and are used to represent a +material with given physical properties: + +- `Material` – Simple material defined by SLD (real and imaginary) +- `MaterialDensity` – Material defined by mass density and chemical + formula +- `MaterialSolvated` – Material mixed with a solvent in a given ratio +- `MaterialMixture` – Mixture of two materials + +### Fitting + +Fitting helpers and objective functions. The `MultiFitter` supports +several objective modes for handling reflectometry data during fitting, +especially when measured variances are non-positive. + +## Getting Started + +To use EasyReflectometry in a project: + +```python +import easyreflectometry +from easyreflectometry.sample import Material, Layer +from easyreflectometry.model import Model +from easyreflectometry.fitting import MultiFitter +from easyreflectometry.plot import plot + +# Define your Material +material = Material(...) + +# Create a Layer +layer = Layer(material=material, ...) + +# Make a Sample out of the Layer +sample = Sample(layer, ...) + +# Define a Model of the experiment +model = Model( + sample=sample, + scale=1, + background=1e-6, + ... +) + +# Set parameter bounds for fit +... + +# Perform the fit and plot +fitter = MultiFitter(model) +analysed = fitter.fit(data) + +plot(analysed) +``` + +Details of specific usage of EasyReflectometry can be found in the +[Tutorials](../tutorials/index.md). + +## Objective Functions and Non-Positive Variance Handling + +`MultiFitter` supports several objective modes for handling +reflectometry data during fitting, especially when measured variances +are non-positive. + +The default objective is `hybrid`. This uses ordinary weighted least +squares for points with positive variance and applies a Mighell-style +substitution only to points whose variance is non-positive. The older +`legacy_mask` mode drops non-positive-variance points before fitting. +The `mighell` mode applies the Mighell transform to every point. + +### Mighell Objective + +The full `mighell` objective follows the algebraic form of the +$\chi^2_\gamma$ statistic described by Mighell for Poisson-distributed +count data: + +$$ +\chi^2_\gamma = +\sum_i \frac{[n_i + \min(n_i, 1) - m_i]^2}{n_i + 1} +$$ + +where $n_i$ are observed counts and $m_i$ are model values. + +In EasyReflectometry this is implemented as a weighted least-squares +problem. For each observed value $y_i$ the fitted target is shifted to + +$$ +y_{\mathrm{eff},i} = y_i + \min(y_i, 1) +$$ + +and the effective uncertainty is + +$$ +\sigma_i = \sqrt{y_i + 1} +$$ + +so the minimized objective is + +$$ +\sum_i \left(\frac{y_{\mathrm{eff},i} - f_i}{\sigma_i}\right)^2 = +\sum_i \frac{[y_i + \min(y_i, 1) - f_i]^2}{y_i + 1} +$$ + +### Scope and Interpretation + +Mighell's statistic was derived for Poisson-distributed count data. In +reflectometry workflows, the fitted values are usually normalized +reflectivities or intensities rather than raw counts. They may already +have been processed, scaled, background-corrected, or otherwise +transformed before they reach the fitter. + +This distinction matters when interpreting the result. The full +`mighell` objective is not only a reweighting of residuals; it also +changes the fitted target from $y$ to $y + \min(y, 1)$. For values +between zero and one, this can substantially increase the target value. +A fit can therefore have a good Mighell objective value while looking +poorer against the originally plotted reflectivity curve, or while +having a worse classical chi-square. + +For reflectometry data, `hybrid` is generally the recommended +compromise: it preserves ordinary weighted least-squares behavior where +positive variances are available, while still allowing +non-positive-variance points to contribute through the Mighell-style +substitution. + +### Objective Modes + +- **`hybrid`** (default): Use standard weighted least squares for points + with positive variance and apply the Mighell substitution only where + variance is non-positive. +- **`mighell`**: Apply the Mighell transform to all points. The reported + objective chi-square is evaluated in transformed objective space and + should not be interpreted as a classical chi-square against the + original reflectivity values. +- **`legacy_mask`**: Remove non-positive-variance points before fitting + and use standard weighted least squares for the remaining points. +- **`auto`**: Alias for `hybrid`. + +### Fit Metrics + +The fitter exposes both objective-space and classical fit metrics after +fitting. `objective_chi2` and `objective_reduced_chi` describe the +minimized objective value, while `classical_chi2` and +`classical_reduced_chi` describe the fit quality against the original +reflectivity values (with non-positive-variance points excluded). diff --git a/docs/includes/abbreviations.md b/docs/includes/abbreviations.md new file mode 100644 index 00000000..682f16ff --- /dev/null +++ b/docs/includes/abbreviations.md @@ -0,0 +1,15 @@ + + +*[CIF]: Crystallographic Information File. +*[curl]: Command-line tool for transferring data with URLs. +*[GitHub]: A web-based platform for version control and collaboration. +*[Google Colab]: Cloud service that allows you to run Jupyter Notebooks in the cloud. +*[IUCr]: International Union of Crystallography. +*[Jupyter Notebook]: An open-source web application that allows you to create and share documents that contain live code, equations, visualizations, and narrative text. +*[JupyterLab]: Web-based interactive development environment for notebooks, code, and data. +*[pip]: Package installer for Python. +*[PyPI]: The Python Package Index is a repository of software for the Python programming language. +*[Conda]: Conda is a cross-platform, language-agnostic binary package manager. +*[Pixi]: A modern package manager for Windows, macOS, and Linux. + + diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 00000000..f75e4657 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,217 @@ +# Project information +site_name: EasyReflectometry Library +site_url: https://easyscience.github.io/reflectometry-lib + +# Repository +repo_url: https://github.com/easyscience/reflectometry-lib +edit_uri: edit/develop/docs/ + +# Copyright +copyright: © 2021-2026 EasyReflectometry + +# Sets the theme and theme-specific configuration +theme: + name: material + custom_dir: overrides + features: + #- content.action.edit # Temporary disable edit button (until decided on which branch to use and where to host the notebooks) + #- content.action.view + - content.code.annotate + - content.code.copy # Auto generated button to copy a code block's content + - content.tooltips + - navigation.footer + - navigation.indexes + #- navigation.instant # Instant loading, but it causes issues with rendering equations + #- navigation.sections + - navigation.top # Back-to-top button + - navigation.tracking # Anchor tracking + - search.highlight + - search.share + - search.suggest + - toc.follow + palette: + # Palette toggle for light mode + - media: '(prefers-color-scheme: light)' + scheme: default + primary: custom + toggle: + icon: fontawesome/solid/sun + name: Switch to dark mode + # Palette toggle for dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + primary: custom + toggle: + icon: fontawesome/solid/moon + name: Switch to light mode + font: + text: Mulish + code: Roboto Mono + icon: + edit: material/file-edit-outline + favicon: assets/images/favicon.png + logo_dark_mode: assets/images/logo_dark.svg + logo_light_mode: assets/images/logo_light.svg + +# A set of key-value pairs, where the values can be any valid YAML +# construct, that will be passed to the template +extra: + generator: false # Disable `Made with Material for MkDocs` (bottom left) + social: # Extra icons in the bottom right corner + - icon: easyscience # File: overrides/.icons/easyscience.svg + link: https://easyscience.org + name: EasyScience Framework Webpage + - icon: easyreflectometry # File: overrides/.icons/easyreflectometry.svg + link: https://easyscience.github.io/reflectometry + name: EasyReflectometry Main Webpage + - icon: fontawesome/brands/github # Name as in Font Awesome + link: https://github.com/easyscience/reflectometry-lib + name: EasyReflectometry Library Source Code on GitHub + # Set custom variables to be used in Markdown and HTML files + vars: + ci_branch: !ENV CI_BRANCH + github_repository: !ENV GITHUB_REPOSITORY + release_version: !ENV RELEASE_VERSION + docs_version: !ENV DOCS_VERSION + notebooks_dir: !ENV NOTEBOOKS_DIR + # Renders a version selector in the header + version: + provider: mike + +# Customization to be included by the theme +extra_css: + - assets/stylesheets/extra.css + +extra_javascript: + - assets/javascripts/extra.js + # MathJax for rendering mathematical expressions + - assets/javascripts/mathjax.js # Custom MathJax config to ensure compatibility with mkdocs-jupyter + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js # Official MathJax CDN + +# A list of extensions beyond the ones that MkDocs uses by default (meta, toc, tables, and fenced_code) +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - pymdownx.arithmatex: # rendering of equations and integrates with MathJax or KaTeX + generic: true + - pymdownx.blocks.caption + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + options: + custom_icons: + - docs/overrides/.icons + - pymdownx.highlight: # whether highlighting should be carried out during build time by Pygments + use_pygments: true + pygments_lang_class: true + - pymdownx.snippets: + auto_append: + - docs/includes/abbreviations.md + - pymdownx.superfences: # whether highlighting should be carried out during build time by Pygments + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: # enables content tabs + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - toc: + toc_depth: 3 + +# A list of plugins (with optional configuration settings) to use when building the site +plugins: + - autorefs + - inline-svg + - markdownextradata # Plugin that injects the mkdocs.yml extra variables into the Markdown template + - mike # Plugin that makes it easy to deploy multiple versions of the docs + - mkdocs-jupyter: + include: ['*.ipynb'] # Default: ['*.py', '*.ipynb'] + execute: false # Do not execute notebooks during build. They are expected to be pre-executed. + allow_errors: false + include_source: true + include_requirejs: true # Required for Plotly + #custom_mathjax_url: 'https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js' # See 'extra_javascript' above + ignore_h1_titles: true # Use titles defined in the nav section below + remove_tag_config: + remove_input_tags: + - hide-in-docs + - mkdocstrings: + handlers: + python: + paths: ['src'] # Change 'src' to your actual sources directory + options: + annotations_path: source + docstring_style: numpy + group_by_category: true + members: true + members_order: source + heading_level: 1 + show_inheritance: true + show_root_heading: true + show_root_full_path: false + show_submodules: true + show_source: true + - search + +# Determines additional directories to watch when running mkdocs serve +watch: + - includes + - overrides + - ../src + +# Exclude files and folders from the global navigation +not_in_nav: | + index.md + +# Format and layout of the global navigation for the site +nav: + - Introduction: + - Introduction: introduction/index.md + - Installation & Setup: + - Installation & Setup: installation-and-setup/index.md + - User Guide: + - User Guide: user-guide/index.md + - Tutorials: + - Tutorials: tutorials/index.md + - Getting Started: + - Creating a Model: tutorials/basic/model.md + - Defining Materials: tutorials/basic/material_library.md + - Defining Layers: tutorials/basic/layer_library.md + - Creating Assemblies: tutorials/basic/assemblies_library.md + - Simulation: + - Bilayer Simulation: tutorials/simulation/bilayer.ipynb + - Magnetism Simulation: tutorials/simulation/magnetism.ipynb + - Resolution Functions: tutorials/simulation/resolution_functions.ipynb + - Fitting: + - Simple Fitting: tutorials/fitting/simple_fitting.ipynb + - Repeating Multilayer Fitting: tutorials/fitting/repeating.ipynb + - Monolayer Fitting: tutorials/fitting/monolayer.ipynb + - Solvated Material Fitting: tutorials/fitting/material_solvated.ipynb + - Advanced Fitting: + - Multi-Contrast Fitting: tutorials/advancedfitting/multi_contrast.ipynb + - API Reference: + - API Reference: api-reference/index.md + - Model: api-reference/model.md + - Sample: api-reference/sample.md + - Project: api-reference/project.md + - Fitting: api-reference/fitting.md + - Assemblies: + - Multilayer: api-reference/assemblies/multilayer.md + - Repeating Multilayer: api-reference/assemblies/repeating_multilayer.md + - Surfactant Layer: api-reference/assemblies/surfactant_layer.md + - Gradient Layer: api-reference/assemblies/gradient_layer.md + - Elements: + - Layers: + - Layer: api-reference/elements/layer.md + - Layer Area Per Molecule: api-reference/elements/layer_area_per_molecule.md + - Materials: + - Material: api-reference/elements/material.md + - Material Density: api-reference/elements/material_density.md + - Material Mixture: api-reference/elements/material_mixture.md + - Material Solvated: api-reference/elements/material_solvated.md + - Data: api-reference/data.md diff --git a/docs/overrides/.icons/app.svg b/docs/overrides/.icons/app.svg new file mode 100644 index 00000000..b4fdd4f3 --- /dev/null +++ b/docs/overrides/.icons/app.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/overrides/.icons/easyreflectometry.svg b/docs/overrides/.icons/easyreflectometry.svg new file mode 100644 index 00000000..8a2e3087 --- /dev/null +++ b/docs/overrides/.icons/easyreflectometry.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + Logo + + + + + \ No newline at end of file diff --git a/docs/overrides/.icons/easyscience.svg b/docs/overrides/.icons/easyscience.svg new file mode 100644 index 00000000..fb514912 --- /dev/null +++ b/docs/overrides/.icons/easyscience.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/overrides/.icons/google-colab.svg b/docs/overrides/.icons/google-colab.svg new file mode 100644 index 00000000..9cd9d1b0 --- /dev/null +++ b/docs/overrides/.icons/google-colab.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 00000000..2e146827 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block content %} + +{% if page.nb_url %} + {# Parse notebook path/URL #} + {% set parts = page.nb_url.split('/') %} + {% set tutorial_name = parts[-2] %} + {% set filename = parts[-1] %} + + {# Colab url #} + {% set base_colab_url = "https://colab.research.google.com/github/" %} + {% set colab_url = + base_colab_url ~ config.extra.vars.github_repository ~ + "/blob/gh-pages/" ~ config.extra.vars.docs_version ~ + "/tutorials/" ~ tutorial_name ~ "/" ~ filename + %} + + {# Download link: relative to the current page #} + {% set file_url = filename %} + + {# Open in Colab (absolute GitHub URL; works anywhere) #} + + {% include ".icons/google-colab.svg" %} + + + {# Download: use a RELATIVE link to the file next to this page #} + + {% include ".icons/material/download.svg" %} + +{% endif %} + +{{ super() }} +{% endblock content %} diff --git a/docs/overrides/partials/logo.html b/docs/overrides/partials/logo.html new file mode 100644 index 00000000..78fa69ca --- /dev/null +++ b/docs/overrides/partials/logo.html @@ -0,0 +1,15 @@ +{% if ( config.theme.logo_light_mode and config.theme.logo_dark_mode ) %} +logo +logo +{% elif config.theme.logo %} +logo +{% else %} {% set icon = config.theme.icon.logo or "material/library" %} {% +include ".icons/" ~ icon ~ ".svg" %} {% endif %} diff --git a/docs/src/conf.py b/docs/src/conf.py index 36235949..127779c9 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -20,9 +20,10 @@ import datetime import os import sys -import toml from pathlib import Path +import toml + import easyreflectometry sys.path.insert(0, os.path.abspath('../src')) @@ -54,7 +55,7 @@ 'sphinx_autodoc_typehints', 'sphinx_copybutton', 'nbsphinx', - 'myst_parser' + 'myst_parser', ] # Add any paths that contain templates here, relative to this directory. @@ -71,8 +72,8 @@ # General information about the project. project = 'EasyReflectometry' -copyright = f"{datetime.date.today().year}, EasyReflectometry" -author = "EasyReflectometry" +copyright = f'{datetime.date.today().year}, EasyReflectometry' +author = 'EasyReflectometry' # The version info for the project you're documenting, acts as replacement # for |version| and |release|, also used in various other places throughout @@ -105,7 +106,7 @@ autoclass_content = 'class' autodoc_member_order = 'bysource' autodoc_typehints = 'signature' -autodoc_class_signature = "separated" +autodoc_class_signature = 'separated' # -- Options for HTML output ------------------------------------------- @@ -116,7 +117,7 @@ html_logo = os.path.join('_static', 'logo.png') html_favicon = os.path.join('_static', 'favicon.ico') html_theme_options = { -# 'logo_only': True, + # 'logo_only': True, 'navigation_with_keys': True } html_baseurl = 'https://docs.easyreflectometry.org' @@ -132,7 +133,7 @@ html_static_path = ['_static'] nbsphinx_execute_arguments = [ - "--Session.metadata=scipp_docs_build=True", + '--Session.metadata=scipp_docs_build=True', ] @@ -148,15 +149,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -166,9 +164,7 @@ # (source start file, target name, title, author, documentclass # [howto, manual, or own class]). latex_documents = [ - (master_doc, 'EasyReflectometry.tex', - 'EasyReflectometry Documentation', - 'Andrew R. McCluskey', 'manual'), + (master_doc, 'EasyReflectometry.tex', 'EasyReflectometry Documentation', 'Andrew R. McCluskey', 'manual'), ] @@ -176,11 +172,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'EasyReflectometry', - 'EasyReflectometry Documentation', - [author], 1) -] +man_pages = [(master_doc, 'EasyReflectometry', 'EasyReflectometry Documentation', [author], 1)] # -- Options for Texinfo output ---------------------------------------- @@ -189,11 +181,13 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'EasyReflectometry', - 'EasyReflectometry Documentation', - author, - 'EasyReflectometry', - 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + 'EasyReflectometry', + 'EasyReflectometry Documentation', + author, + 'EasyReflectometry', + 'One line description of project.', + 'Miscellaneous', + ), ] - diff --git a/docs/src/tutorials/advancedfitting/advancedfitting.rst b/docs/src/tutorials/advancedfitting/advancedfitting.rst deleted file mode 100644 index 8c12ba54..00000000 --- a/docs/src/tutorials/advancedfitting/advancedfitting.rst +++ /dev/null @@ -1,9 +0,0 @@ -Advanced Fitting -================ - -These are advanced fitting examples using the :py:mod:`easyreflectometry` library, to get an understanding of the possibilities. - -.. toctree:: - :maxdepth: 1 - - multi_contrast.ipynb \ No newline at end of file diff --git a/docs/src/tutorials/basic/assemblies_library.rst b/docs/src/tutorials/basic/assemblies_library.rst deleted file mode 100644 index d72201a9..00000000 --- a/docs/src/tutorials/basic/assemblies_library.rst +++ /dev/null @@ -1,249 +0,0 @@ -Creating multilayers and surfactant layers -=========================================== - -:py:mod:`easyreflectometry` is designed to be used with a broad range of different assemblies. -Assemblies are collective layers behaving as a single object, for example, a multilayer or a surfactant layer. -These assemblies offer flexibility for the user and enable more powerful analysis by making chemical and physical constraints available with limited code. -In this page, we will document the assemblies that are available with simple examples of the constructors that exist. -Full API documentation is also available for the :py:mod:`easyreflectometry.sample.assemblies` module. - -:py:class:`Multilayer` ----------------------- - -This assembly should be used for a series of layers that should be thought of as a single object. -For example, in the `simple fitting tutorial`_ this assembly type is used to combine the silicon and silicon dioxide layer that as formed into a single object. -All of the separate layers in these objects will be fitted individually, i.e. there is no constraints present, however, there is some cognative benefit to grouping layers together. - -To create a :py:class:`Multilayer` object, we use the following construction. - -.. code-block:: python - - from easyreflectometry.sample import Layer - from easyreflectometry.sample import Material - from easyreflectometry.sample import Multilayer - - si = Material( - sld=2.07, - isld=0, - name='Si' - ) - sio2 = Material( - sld=3.47, - isld=0, - name='SiO2' - ) - si_layer = Layer( - material=si, - thickness=0, - roughness=0, - name='Si layer' - ) - sio2_layer = Layer( - material=sio2, - thickness=30, - roughness=3, - name='SiO2 layer' - ) - - subphase = Multilayer( - layers=[si_layer, sio2_layer], - name='Si/SiO2 subphase' - ) - -This will create a :py:class:`Multilayer` object named :code:`subphase` which we can use in some :py:class:`Structure` for our analysis. - -:py:class:`RepeatingMultilayer` -------------------------------- - -The :py:class:`RepeatingMultilayer` assembly type is an extension of the :py:class:`Multilayer` for the analysis of systems with a multilayer that has some number of repeats. -This assembly type imposes some constraints, specifically that all of the repeats have the exact same structure (i.e. thicknesses, roughnesses, and scattering length densities), -which brings with it some computational saving as the reflectometry coefficients only needs to be calculated once for this structure and propagated for the correct number of repeats. -There is a `tutorial`_ that discusses the utilisation of this assembly type for a nickel-titanium multilayer system. - -The creation of a :py:class:`RepeatingMultilayer` object is very similar to that for the :py:class:`Multilayer`, with the addition of a number of repetitions. - -.. code-block:: python - - from easyreflectometry.sample import Layer - from easyreflectometry.sample import Material - from easyreflectometry.sample import RepeatingMultilayer - - ti = Material( - sld=-1.9493, - isld=0, - name='Ti' - ) - ni = Material( - sld=9.4245, - isld=0, - name='Ni' - ) - ti_layer = Layer( - material=ti, - thickness=40, - roughness=0, - name='Ti Layer' - ) - ni_layer = Layer( - material=ni, - thickness=70, - roughness=0, - name='Ni Layer' - ) - ni_ti = RepeatingMultilayer( - layers=[ti_layer, ni_layer], - repetitions=10, - name='Ni/Ti Multilayer' - ) - -The number of repeats is a parameter that can be varied in the optimisation process, however given this is a value that depends on the synthesis of the sample this is unlikely to be necessary. - -:py:class:`SurfactantLayer` ---------------------------- - -The :py:class:`SurfactantLayer` assembly type allows for the creating of a model to describe a monolayer of surfactant at some interface. -Using this assembly, we can define our surfactant in terms of the chemistry of the head and tail groups and be confident that the constraints are present to ensure the number density if kept constant. -The `surfactant monolayer tutorial`_ looks in detail at the definition of the scattering length density in the :py:class:`SurfactantLayer`. -However, it is founded on the chemical formula for the head and tail group and the area per molecule that these groups occupy. - -The creation of a :py:class:`SurfactantLayer` object is shown below. - -.. code-block:: python - - from easyreflectometry.sample import LayerAreaPerMolecule - from easyreflectometry.sample import Material - from easyreflectometry.sample import SurfactantLayer - - area_per_molecule = 48 - roughness = 3.3 - subphase = Material( - sld=6.36, - isld=0.0, - name='D2O' - ) - superphase = Material( - sld=0.0, - isld=0.0, - name='Air' - ) - tail_layer = LayerAreaPerMolecule( - molecular_formula='C30D64', - thickness=16.0, - solvent=superphase, - solvent_fraction=0.0, - area_per_molecule=area_per_molecule, - roughness=roughness - ) - head_layer = LayerAreaPerMolecule( - molecular_formula='C10H18NO8P', - thickness=10.0, - solvent=subphase, - solvent_fraction=0.2, - area_per_molecule=area_per_molecule, - roughness=roughness - ) - dspc = SurfactantLayer( - tail_layer=tail_layer, - head_layer=head_layer - ) - -On creation, the area per molecule and roughness above both the head and tail layers can be constrained to be the same. -These constraints can be addded by setting :code:`dppc.constrain_area_per_molecule = True` or :code:`dppc.conformal_roughness = True`. -Furthermore, as shown in the `surfactant monolayer tutorial`_ the conformal roughness can be defined by that of the subphase. - -The use of the :py:class:`SurfactantLayer` in multiple contrast data analysis is shown in a `multiple contrast tutorial`_. - -:py:class:`Bilayer` -------------------- - -The :py:class:`Bilayer` assembly type represents a phospholipid bilayer at an interface. -It consists of two surfactant layers where one is inverted, creating the structure: - -.. code-block:: text - - Head₁ - Tail₁ - Tail₂ - Head₂ - -This assembly is particularly useful for studying supported lipid bilayers and membrane systems. -The bilayer comes pre-populated with physically meaningful constraints: - -- Both tail layers share the same structural parameters (thickness, area per molecule) -- Head layers share thickness and area per molecule (different hydration/solvent fraction allowed) -- A single roughness parameter applies to all interfaces (conformal roughness) - -These default constraints can be enabled or disabled as needed for specific analyses. - -The creation of a :py:class:`Bilayer` object is shown below. - -.. code-block:: python - - from easyreflectometry.sample import Bilayer - from easyreflectometry.sample import LayerAreaPerMolecule - from easyreflectometry.sample import Material - - # Create materials for solvents - d2o = Material(sld=6.36, isld=0.0, name='D2O') - air = Material(sld=0.0, isld=0.0, name='Air') - - # Create head layer (used for front, back head will be auto-created with constraints) - head = LayerAreaPerMolecule( - molecular_formula='C10H18NO8P', - thickness=10.0, - solvent=d2o, - solvent_fraction=0.3, - area_per_molecule=48.2, - roughness=3.0, - name='DPPC Head' - ) - - # Create tail layer (both tail positions will share these parameters) - tail = LayerAreaPerMolecule( - molecular_formula='C32D64', - thickness=16.0, - solvent=air, - solvent_fraction=0.0, - area_per_molecule=48.2, - roughness=3.0, - name='DPPC Tail' - ) - - # Create bilayer with default constraints - bilayer = Bilayer( - front_head_layer=head, - tail_layer=tail, - constrain_heads=True, - conformal_roughness=True, - name='DPPC Bilayer' - ) - -The head layers can have different solvent fractions (hydration) even when constrained, -enabling the modeling of asymmetric bilayers at interfaces where the two sides of the -bilayer may have different solvent exposure. - -The constraints can be controlled at runtime: - -.. code-block:: python - - # Disable head constraints to allow different head layer structures - bilayer.constrain_heads = False - - # Disable conformal roughness to allow different roughness values - bilayer.conformal_roughness = False - -Individual layers can be accessed via properties: - -.. code-block:: python - - # Access the four layers - bilayer.front_head_layer # First head layer - bilayer.front_tail_layer # First tail layer - bilayer.back_tail_layer # Second tail layer (constrained to front tail) - bilayer.back_head_layer # Second head layer - -For more detailed examples including simulation and parameter access, see the `bilayer tutorial`_. - - -.. _`simple fitting tutorial`: ../tutorials/simple_fitting.html -.. _`tutorial`: ../tutorials/repeating.html -.. _`surfactant monolayer tutorial`: ../tutorials/monolayer.html -.. _`multiple contrast tutorial`: ../tutorials/multi_contrast.html -.. _`bilayer tutorial`: ../tutorials/simulation/bilayer.html \ No newline at end of file diff --git a/docs/src/tutorials/basic/basic.rst b/docs/src/tutorials/basic/basic.rst deleted file mode 100644 index bcb94f58..00000000 --- a/docs/src/tutorials/basic/basic.rst +++ /dev/null @@ -1,13 +0,0 @@ -Basic -===== - -The :py:mod:`easyreflectometry` package is focused on making easy to use functionality for specific modelling approaches. -In order to achieve this, we have a sample library with different functionality. - -.. toctree:: - :maxdepth: 1 - - model - material_library - layer_library - assemblies_library \ No newline at end of file diff --git a/docs/src/tutorials/basic/layer_library.rst b/docs/src/tutorials/basic/layer_library.rst deleted file mode 100644 index 76c75ad4..00000000 --- a/docs/src/tutorials/basic/layer_library.rst +++ /dev/null @@ -1,84 +0,0 @@ -Defining Layers -=============== - -Similar to a range of different `materials`_, there are a few different ways that a layer can be defined in :py:mod:`easyreflectometry`. - -:py:class:`Layer` ------------------ - -The :py:class:`Layer` is the simplest possible type of layer, taking a :py:class:`Material` and two floats associated with the thickness and upper (that is closer to the source of the incident radiation) roughness. -So we construct a :py:class:`Layer` as follows for a 100 Å thick layer of boron with a roughness of 10 Å. - -.. code-block:: python - - from easyreflectometry.sample import Material - from easyreflectometry.sample import Layer - - boron = Material( - sld=6.908, - isld=-0.278, - name='Boron' - ) - boron_layer = Layer( - material=boron, - thickness=100, - roughness=10, - name='Boron Layer' - ) - -This type of layer is used extensively in the `tutorials`_ - -To create a semi-infinite layer one needs to set the thickness to 0 and the roughness to 0. - -.. code-block:: python - - from easyreflectometry.sample import Material - from easyreflectometry.sample import Layer - - si = Material( - sld=2.07, - isld=0, - name='Si' - ) - semi_infinite_layer = Layer( - material=si, - thickness=0, - roughness=0, - name='Si layer' - ) - -:py:class:`LayerAreaPerMolecule` --------------------------------- - -The :py:class:`LayerAreaPerMolecule` layer type is the fundation of the :py:class:`SurfactantLayer` assemblies type (further information on this can be found in the `assemblies library`_). -The purpose of the :py:class:`LayerAreaPerMolecule` is to allow a layer to be defined in terms of the chemical formula of the material and the area per molecule of the layer. -The area per molecule is a common description of surface density in the surfactant monolayer and bilayer community. - -We can construct a 10 Å thick :py:class:`LayerAreaPerMolecule` of phosphatidylcholine, with an area per molecule of 48 Å squared and a roughness of 3 Å that has 20 % solvent surface coverage with D2O using the following. - -.. code-block:: python - - from easyreflectometry.sample import Material - from easyreflectometry.sample import LayerAreaPerMolecule - - d2o = Material( - sld=6.36, - isld=0, - name='D2O' - ) - molecular_formula = 'C10H18NO8P' - pc = LayerAreaPerMolecule( - molecular_formula=molecular_formula, - thickness=10, - solvent=d2o, - solvent_fraction=.2, - area_per_molecule=48, - roughness=3, - name='PC Layer' - ) - -It is expected that the typical user will not interface directly with the :py:class:`LayerAreaPerMolecule` assembly type, but instead the :py:class:`SurfactantLayer` `assemblies library`_ will be used instead. - -.. _`materials`: ./material_library.html -.. _`tutorials`: ../tutorials/tutorials.html -.. _`assemblies library`: ./assemblies_library.html \ No newline at end of file diff --git a/docs/src/tutorials/basic/material_library.rst b/docs/src/tutorials/basic/material_library.rst deleted file mode 100644 index 2aaf0911..00000000 --- a/docs/src/tutorials/basic/material_library.rst +++ /dev/null @@ -1,87 +0,0 @@ -Defining materials -================== - -In order to support a wide range of applications (and to build complex `assemblies`_) there are a few different types of material that can be utilised in :py:mod:`easyreflectometry`. -These can include constraints or enable the user to define the material based on chemical or physical properties. -Full API documentation for the :py:mod:`easyreflectometry.sample.elements.material` module is also available, but here we will give some simple uses for them. - -:py:class:`Material` --------------------- - -The simplest type of material that is available is the :py:class:`Material`. -This allows the user to define a single type of material, with a real and imaginary component to the scattering length density. -The construction of a :py:class:`Material` is achieved as shown below. - -.. code-block:: python - - from easyreflectometry.sample import Material - - boron = Material( - sld=6.908, - isld=-0.278, - name='Boron' - ) - -The above object will have the properties of :py:attr:`sld` and :py:attr:`isld`, which will have values of :code:`6.908 1/angstrom^2` and :code:`-0.278 1/angstrom^2` respectively. -As is shown in the `tutorials`_, a material can be used to construct a :py:class:`Layer` from which `slab models`_ are created. - -:py:class:`MaterialDensity` ---------------------------- - -In addition to defining a material by its scattering length density, it may be useful to define a material by the mass density and chemical formula. -This is possible with the :py:class:`MaterialDensity` material type, which uses the scattering length and atomic mass from the chemical formula and the density to determine the scattering length density. -It is then possible to vary the density, which defines the scattering length density in turn. -The :py:class:`MaterialDensity` material can be create as follows. - -.. code-block:: python - - from easyreflectometry.sample import MaterialDensity - - chemical_structure = 'SiO2' - si = MaterialDensity( - chemical_structure=chemical_structure, - density=2.65, - name='SiO2 Material' - ) - -The density should be in units of grams per cubic centimeter and the scattering length is calculated from :code:`'SiO2'`. - -:py:class:`MaterialSolvated` ----------------------------- - -Sometimes it is desirable to have a layer that consists of a material and a solvent in some ratio. -An example of this is shown in the `solvation tutorial`_, where a polymer film solvated with D2O is modelled. -To produce a material that is described by such a mixture, there is :py:class:`MaterialSolvated`. -This is constructed from two constituent :py:class:`Materials` and the fractional amount of the material in the solvent. -So to produce a :py:class:`MaterialSolvated` that is 20 % D2O in a polymer, the following is used. - -.. code-block:: python - - from easyreflectometry.sample import Material - from easyreflectometry.sample import MaterialSolvated - - polymer = Material( - sld=2., - isld=0., - name='Polymer' - ) - d2o = Material( - sld=6.36, - isld=0, - name='D2O' - ) - - solvated_polymer = MaterialSolvated( - material=polymer, - solvent=d2o, - solvent_fraction=0.2, - name='Solvated Polymer' - ) - -For the :py:attr:`solvated_polymer` object, the :py:attr:`sld` will be :code:`2.872 1/angstrom^2` (the weighted average of the two scattering length densities). -The :py:class:`MaterialSolvated` includes a constraint such that if the value of either constituent scattering length densities (both real and imaginary components) or the fraction changes, then the resulting material :py:attr:`sld` and :py:attr:`isld` will change appropriately. - -.. _`assemblies`: ./assemblies_library.html -.. _`tutorials`: ../tutorials/tutorials.html -.. _`slab models`: https://www.reflectometry.org/isis_school/3_reflectometry_slab_models/the_slab_model.html -.. _`solvation tutorial`: ../tutorials/solvation.html \ No newline at end of file diff --git a/docs/src/tutorials/basic/model.rst b/docs/src/tutorials/basic/model.rst deleted file mode 100644 index 47134397..00000000 --- a/docs/src/tutorials/basic/model.rst +++ /dev/null @@ -1,80 +0,0 @@ -Creating a model -================ - -The main component of an experiment in :py:mod:`easyreflectometry` is the :py:class:`Model`. -This is a description of the :py:class:`Sample` and the environment in which the experiment is performed. -The :py:class:`Model` is used to calculate the reflectivity of the :py:class:`Sample` at a given set of angles (Q-points). -The :py:func:`resolution_functions` are used to quantify the experimental uncertainties in wavelength and angle, allowing the :py:class:`Model` to accurately describe the data. - -:py:class:`Model` ------------------ - -A :py:class:`Model` instance contains a :py:class:`Sample` and variables describing experimental settings. -To be able to compute reflectivities it is also necessary to have a :py:class:`Calculator` (interface). - -.. code-block:: python - - from easyreflectometry.calculators import CalculatorFactory - from easyreflectometry.model import Model - from easyreflectometry.sample import Sample - - default_sample = Sample() - model = Model( - sample=default_sample, - scale=1.0, - background=1e-6 - ) - - interface = CalculatorFactory() - model.interface = interface - -This will create a :py:class:`Model` instance with the :py:attr:`default_sample` and the environment variables :py:attr:`scale` factor set to 1.0 and a :py:attr:`background` of 1e-6. -Following the :py:attr:`interface` is set to the default calculator that is :py:class:`Refnx`. - - -:py:mod:`resolution_functions` ------------------------------- -A resolution function enables the :py:mod:`easyreflectometry` model to incorporate the experimental uncertainties in wavelength and incident angle into the model. -In its essence the resolution function controls the smearing to apply when determing the reflectivtiy at a given Q-point. -For a given Q-point the smearing to apply is given as a weighted average of the neighboring Q-point, which weigths are by a normal distribution. -This normal distribution is then defined by a Q-point dependent Full Width at the Half Maximum (FWHM) that is given by the resolution function. - -:py:class:`PercentageFwhm` -Often we rely on a resolution function that has a simple functional dependecy of the Q-point. -By this is understood that the applied smearing in an Q-point has a FWHM that is simply a percentage of the value of the Q-point. - -.. code-block:: python - - from easyreflectometry.model import Model - from easyreflectometry.model import PercentageFwhm - - resolution_function = PercentageFwhm(1.1) - - m = Model( - resolution_function=resolution_function - ) - -This will create a :py:class:`Model` instance where the resolution function is defined as 1.1% of the Q-point value, which again is the FWHM for the smearing. - - -:py:func:`LinearSpline` -Alternatively the FWHM value might be determined and declared directly for each measured Q-point. -When this is the case the provided Q-points and the corresponding FWHM values can be used to declare a linear spline function -and thereby enable a determination of the reflectivity at an arbitrary point within the provided range of discrete Q-points. - -.. code-block:: python - - from easyreflectometry.model import Model - from easyreflectometry.model import LinearSpline - - m = Model() - - resolution_function = LinearSpline( - q_data_points=[0.01, 0.2, 0.31], - fwhm_values=[0.001, 0.043, 0.026] - ) - - m.resolution_function = resolution_function - -This will create a :py:class:`Model` instance where the resolution function defining the FWHM is determined from a linear interpolation. -In the present case the provided data Q-points are (`[0.01, 0.2, 0.31]`) and the corresponding FWHM function values are (`[0.001, 0.043, 0.026]`). diff --git a/docs/src/tutorials/extra/extra.rst b/docs/src/tutorials/extra/extra.rst deleted file mode 100644 index 9d68b33a..00000000 --- a/docs/src/tutorials/extra/extra.rst +++ /dev/null @@ -1,7 +0,0 @@ -Extra -===== - -These are extra examples using the :py:mod:`easyreflectometry` library, to get an understanding of the possibilities. - -.. toctree:: - :maxdepth: 1 \ No newline at end of file diff --git a/docs/src/tutorials/fitting/fitting.rst b/docs/src/tutorials/fitting/fitting.rst deleted file mode 100644 index 52b6c14f..00000000 --- a/docs/src/tutorials/fitting/fitting.rst +++ /dev/null @@ -1,12 +0,0 @@ -Fitting -======= - -These are basic fitting examples using the :py:mod:`easyreflectometry` library, to get an understanding of the possibilities. - -.. toctree:: - :maxdepth: 1 - - simple_fitting.ipynb - repeating.ipynb - monolayer.ipynb - material_solvated.ipynb \ No newline at end of file diff --git a/docs/src/tutorials/simulation/simulation.rst b/docs/src/tutorials/simulation/simulation.rst deleted file mode 100644 index 5cf9ced4..00000000 --- a/docs/src/tutorials/simulation/simulation.rst +++ /dev/null @@ -1,11 +0,0 @@ -Simulation -========== - -These are basic simulation examples using the :py:mod:`easyreflectometry` library, to get an understanding of the possibilities. - -.. toctree:: - :maxdepth: 1 - - bilayer.ipynb - magnetism.ipynb - resolution_functions.ipynb \ No newline at end of file diff --git a/docs/src/tutorials/tutorials.rst b/docs/src/tutorials/tutorials.rst deleted file mode 100644 index b73e6666..00000000 --- a/docs/src/tutorials/tutorials.rst +++ /dev/null @@ -1,49 +0,0 @@ -========== -How to use -========== - -Dictionary -========== -The following serves to clarify what we mean by the terms we use in this project. - -Sample ------- -A sample is an ideal representation of a the full physical setup. -This includes the layer(s) under investigation, the surrounding superphase, and the subphase. - -Calculator ----------- -A calculator is the physics engine which calculates the reflectivity curve from our inputted sample parameters. -We rely on third party software to provide the necessary calculators. -Different calculators might have different capabilities and limitations. - -Model ------ -A model combines a sample and calculator. -The model is also responsible for including instrumental effects such as background, scale, and resolution. - - -Calculators & Optimisation -========================== - -:py:mod:`easyreflectometry` is built on the :py:mod:`easyscience` framework which facilities the use of a range of different reflectometry calculation engines and optimiser solutions. -Currently, :py:mod:`easyreflectometry` can offer two different calculation engines, namely: - -* `refnx`_ -* `Refl1D`_ - -And we are working to add more, in particular `bornagain`_ and `GenX`_. - -.. _`refnx`: https://refnx.readthedocs.io/ -.. _`Refl1D`: https://refl1d.readthedocs.io/en/latest/ -.. _`BornAgain`: https://www.bornagainproject.org -.. _`GenX`: https://aglavic.github.io/genx/doc/ - -.. toctree:: - :maxdepth: 2 - - basic/basic - simulation/simulation - fitting/fitting - advancedfitting/advancedfitting - extra/extra diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..1b7139b3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,65 @@ +{ + "name": "reflectometry-lib", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "^3.8.3", + "prettier-plugin-toml": "^2.0.6" + } + }, + "node_modules/@taplo/core": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@taplo/core/-/core-0.2.0.tgz", + "integrity": "sha512-r8bl54Zj1In3QLkiW/ex694bVzpPJ9EhwqT9xkcUVODnVUGirdB1JTsmiIv0o1uwqZiwhi8xNnTOQBRQCpizrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@taplo/lib": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@taplo/lib/-/lib-0.5.0.tgz", + "integrity": "sha512-+xIqpQXJco3T+VGaTTwmhxLa51qpkQxCjRwezjFZgr+l21ExlywJFcDfTrNmL6lG6tqb0h8GyJKO3UPGPtSCWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@taplo/core": "^0.2.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-toml": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/prettier-plugin-toml/-/prettier-plugin-toml-2.0.6.tgz", + "integrity": "sha512-12N/wBuHa9jd/KVy9pRP20NMKxQfQLMseQCt66lIbLaPLItvGUcSIryE1eZZMJ7loSws6Ig3M2Elc2EreNh76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@taplo/lib": "^0.5.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..80724263 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "prettier": "^3.8.3", + "prettier-plugin-toml": "^2.0.6" + } +} diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 00000000..0399edad --- /dev/null +++ b/pixi.lock @@ -0,0 +1,13299 @@ +version: 6 +environments: + default: + channels: + - url: https://conda.anaconda.org/nodefaults/ + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h18e8e13_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.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-py313hf159716_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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 + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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-py313h3dea7bd_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_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.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-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.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.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/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/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/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-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/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-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/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/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/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/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/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 + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: ./ + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h7208f8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.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-py313hde1f3bb_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py313h65a2061_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_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.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-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.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.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/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/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/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-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/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-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/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/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/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/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/95/95/685e5a31f6c4cc24826d3a0df9e6a4bcf13d00f90d0c1f1060f13f5c16f8/python_bidi-0.6.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: ./ + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h2a31948_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.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-py313h3ebfc14_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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.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.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.1-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-py313hd650c13_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_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/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/questionary-2.1.1-pyhd8ed1ab_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_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 + - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/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/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.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/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.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/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/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/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-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/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-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/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/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/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/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-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/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/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 + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + - pypi: ./ + py-311-env: + channels: + - url: https://conda.anaconda.org/nodefaults/ + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py311h6b1f9c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py311h902ca64_1.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-py311h66f275b_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py311h2005dd1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py311hc665b79_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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 + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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/libxcrypt-4.4.36-hd590300_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-py311h3778330_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py311h49ec1c0_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.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py311haee01d2_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py311h902ca64_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py311hdf6b40b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-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.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h57d2397_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py311h902ca64_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py311h49ec1c0_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-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.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.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/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-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/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/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-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/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-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/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/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/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/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/0b/0615dbedb98f5b32a35a53290fbdc6e22306968109278d7e58df82d7a9f6/numba-0.65.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/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 + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: ./ + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h9408147_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py311h36d4fbb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py311h71babbd_1.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-py311hdc60ec4_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py311hd10dc20_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py311hbb43c59_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py311h8948835_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py311hc290fe0_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py311hc949640_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.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py311he363849_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py311hf7c400d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py311h38a6bc0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py311hce6e4fa_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py311h9049b8e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-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.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h745ac33_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py311h71babbd_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py311hc949640_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-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.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.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/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-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/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/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-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/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/0b/b9d1911cfefa61399821dfb37f486d83e0f42630a8d12f7194270c417002/llvmlite-0.47.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-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/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/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/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/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/b3/650500c2eab4534d98e9166f4298e0f3c69c742afdf24e6eabccd1f16ad8/numba-0.65.1-cp311-cp311-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9a/a9383ff12698d5306522122e7007d9fd58f903e00a7715506e3ff5be1678/python_bidi-0.6.9-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: ./ + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py311h71c1bcc_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py311hf51aa87_1.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-py311hc5da9e4_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py311h3485c13_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py311h2098ed6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py311h5dfdfe8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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.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.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.53.1-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-py311h3f79411_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py311h3485c13_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/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py311hf893f09_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py311hf51aa87_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py311hcd7b28f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-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.11-8_cp311.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hff25285_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_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_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 + - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py311hf51aa87_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py311h3485c13_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/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/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.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/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.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/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-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/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/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-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/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-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/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/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/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/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/9a/af61ec03b3116c161fd7a06b9e8a265729a8718458333e8ffbb06d9a3978/numba-0.65.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-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/db/09/a0ab6a246a7ede89e817d749a941df34f27a74bedf15551da51e86ae105e/pycairo-1.29.0-cp311-cp311-win_amd64.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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/a6/1ca2d290381911c17975ba00353671217bb9ed6c56f395871b274435b020/python_bidi-0.6.9-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/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 + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl + - pypi: ./ + py-313-env: + channels: + - url: https://conda.anaconda.org/nodefaults/ + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h18e8e13_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.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-py313hf159716_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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 + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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-py313h3dea7bd_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_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.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-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.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.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/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/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/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-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/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-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/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/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/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/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/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 + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: ./ + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h7208f8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.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-py313hde1f3bb_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py313h65a2061_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_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.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-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.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.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/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/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/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-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/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-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/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/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/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/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/95/95/685e5a31f6c4cc24826d3a0df9e6a4bcf13d00f90d0c1f1060f13f5c16f8/python_bidi-0.6.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: ./ + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h2a31948_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.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-py313h3ebfc14_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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.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.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.1-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-py313hd650c13_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_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/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/questionary-2.1.1-pyhd8ed1ab_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_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 + - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/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/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.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/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.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/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/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/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-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/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-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/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/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/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/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-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/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/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 + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + - pypi: ./ + user: + channels: + - url: https://conda.anaconda.org/nodefaults/ + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h18e8e13_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.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-py313hf159716_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/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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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 + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_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/libgomp-15.2.0-he0feb66_18.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/libsodium-1.0.21-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_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/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_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.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/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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-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.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.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/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.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/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/64/2e/ba6b404a8a0e7c954cdf0fdd2b21723dc41def60f16b69b9b1cbb2e22d91/crysfml-0.6.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/eb/f9f1ded8e4db9638f9530c3782eb01f5ab04945f4cb9e597a51c203fa4c5/diffpy_pdffit2-1.6.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - 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/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-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/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h7208f8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.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-py313hde1f3bb_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/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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-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/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.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/libsodium-1.0.21-h1a92334_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_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.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/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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-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.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.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/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.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/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/35/8c/a917ef8741729ef8b3228f815240f4859717e600f9498359a6c411ed6992/crysfml-0.6.2-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/80/722402571443031989e87a5861f26f2e897778768906dff1f998b42f235d/diffpy_pdffit2-1.6.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl + - 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/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-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/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + - 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.4.0-py313h2a31948_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.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-py313h3ebfc14_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.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.4.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_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 + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_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.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 + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + - 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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-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.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.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.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.1-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-py313hd650c13_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.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_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/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.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.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/paramiko-4.0.0-pyhd8ed1ab_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/pathspec-1.1.1-pyhd8ed1ab_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/plumbum-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/questionary-2.1.1-pyhd8ed1ab_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_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 + - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/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/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.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/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.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/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/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/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.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/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/ed/01/e1da2f721c3a8feec311a16924c42fb28c37445528df0747f47dec5232d5/crysfml-0.6.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/fe/b426215b6843ee70285521e2dd52f08a32096fe1d68e5b92fbdaffdf8ca3/diffpy_pdffit2-1.6.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl + - 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/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-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/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 + md5: a9f577daf3de00bca7c3c76c0ecbd1de + depends: + - __glibc >=2.17,<3.0.a0 + - libgomp >=7.5.0 + constrains: + - openmp_impl <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 28948 + timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 + md5: aaa2a381ccc56eac91d63b6c1240312f + depends: + - cpython + - python-gil + license: MIT + license_family: MIT + purls: [] + size: 8191 + timestamp: 1744137672556 +- pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + name: aiohappyeyeballs + version: 2.6.1 + sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: aiohttp + version: 3.13.5 + sha256: 3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl + name: aiohttp + version: 3.13.5 + sha256: e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl + name: aiohttp + version: 3.13.5 + sha256: a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl + name: aiohttp + version: 3.13.5 + sha256: 69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl + name: aiohttp + version: 3.13.5 + sha256: d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: aiohttp + version: 3.13.5 + sha256: 3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + name: aiosignal + version: 1.4.0 + sha256: 053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e + requires_dist: + - frozenlist>=1.1.0 + - typing-extensions>=4.2 ; python_full_version < '3.13' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + name: annotated-doc + version: 0.0.4 + sha256: 571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320 + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 + md5: 2934f256a8acfe48f6ebb4fce6cde29c + depends: + - python >=3.9 + - typing-extensions >=4.0.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/annotated-types?source=hash-mapping + size: 18074 + timestamp: 1733247158254 +- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + sha256: f09aed24661cd45ba54a43772504f05c0698248734f9ae8cd289d314ac89707e + md5: af2df4b9108808da3dc76710fe50eae2 + depends: + - exceptiongroup >=1.0.2 + - idna >=2.8 + - python >=3.10 + - typing_extensions >=4.5 + - python + constrains: + - trio >=0.32.0 + - uvloop >=0.22.1 + - winloop >=0.2.3 + license: MIT + license_family: MIT + purls: + - pkg:pypi/anyio?source=compressed-mapping + size: 146764 + timestamp: 1774359453364 +- conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + sha256: 8f032b140ea4159806e4969a68b4a3c0a7cab1ad936eb958a2b5ffe5335e19bf + md5: 54898d0f524c9dee622d44bbb081a8ab + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/appnope?source=hash-mapping + size: 10076 + timestamp: 1733332433806 +- pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + name: arabic-reshaper + version: 3.0.1 + sha256: 41c5adc2420f85758eada7e880251c4b6a2adbd83377bd27e5d4eba71f648bc7 + requires_dist: + - fonttools>=4.0 ; extra == 'with-fonttools' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad + md5: 8ac12aff0860280ee0cff7fa2cf63f3b + depends: + - argon2-cffi-bindings + - python >=3.9 + - typing-extensions + constrains: + - argon2_cffi ==999 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi?source=hash-mapping + size: 18715 + timestamp: 1749017288144 +- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_2.conda + sha256: b81f852f13a1d148f6ad7e2a29ab375eb1558b73c9bfa38792d98ea7fb414cff + md5: 6e36e9d2b535c3fbe2e093108df26695 + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=1.0.1 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 35831 + timestamp: 1762509453632 +- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + sha256: ad188ccc06a06c633dc124b09e9e06fb9df4c32ffc38acc96ecc86e506062090 + md5: 27bbec9f2f3a15d32b60ec5734f5b41c + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=1.0.1 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 35943 + timestamp: 1762509452935 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h9408147_2.conda + sha256: c15fc1aa6184185f1b31670a16b76d59f484d3efa25b467f28d59088cf0085c0 + md5: 501c2a606787bcd55b9ddee776208333 + depends: + - __osx >=11.0 + - cffi >=1.0.1 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 34403 + timestamp: 1762509963182 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda + sha256: 05ea6fa7109235cfb4fc24526bae1fe82d88bbb5e697ab3945c313f5f041af5b + md5: e23e087109b2096db4cf9a3985bab329 + depends: + - __osx >=11.0 + - cffi >=1.0.1 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 33947 + timestamp: 1762510144907 +- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_2.conda + sha256: 787e9a5f0a5624fee2af4f58c823bd7933b8ca560e794dd9a821e990b31d2d49 + md5: 5fde0926a521a37d454a5e3162cb6e51 + depends: + - cffi >=1.0.1 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 38502 + timestamp: 1762509668838 +- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda + sha256: 3f8a1affdfeb2be5289d709e365fc6e386d734773895215cf8cbc5100fa6af9a + md5: eabb4b677b54874d7d6ab775fdaa3d27 + depends: + - cffi >=1.0.1 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 38779 + timestamp: 1762509796090 +- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + sha256: 792da8131b1b53ff667bd6fc617ea9087b570305ccb9913deb36b8e12b3b5141 + md5: 85c4f19f377424eafc4ed7911b291642 + depends: + - python >=3.10 + - python-dateutil >=2.7.0 + - python-tzdata + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/arrow?source=hash-mapping + size: 113854 + timestamp: 1760831179410 +- pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + name: asciichartpy + version: 1.5.25 + sha256: 33c417a3c8ef7d0a11b98eb9ea6dd9b2c1b17559e539b207a17d26d4302d0258 + requires_dist: + - setuptools + - flake8 ; extra == 'qa' +- pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl + name: ase + version: 3.28.0 + sha256: 0e24056302d7307b7247f90de281de15e3031c14cf400bedb1116c3b0d0e50b8 + requires_dist: + - numpy>=1.21.6 + - scipy>=1.8.1 + - matplotlib>=3.5.2 + - sphinx ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinxcontrib-video ; extra == 'docs' + - sphinx-gallery ; extra == 'docs' + - pillow ; extra == 'docs' + - pytest>=7.4.0 ; extra == 'test' + - pytest-xdist>=3.2.0 ; extra == 'test' + - spglib>=1.9 ; extra == 'spglib' + - mypy ; extra == 'lint' + - ruff ; extra == 'lint' + - types-docutils ; extra == 'lint' + - types-pymysql ; extra == 'lint' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + name: asn1crypto + version: 1.5.1 + sha256: db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67 +- pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + name: asteval + version: 1.0.8 + sha256: 6c64385c6ff859a474953c124987c7ee8354d781c76509b2c598741c4d1d28e9 + requires_dist: + - build ; extra == 'dev' + - twine ; extra == 'dev' + - sphinx ; extra == 'doc' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - coverage ; extra == 'test' + - asteval[dev,doc,test] ; extra == 'all' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 + md5: 9673a61a297b00016442e022d689faa6 + depends: + - python >=3.10 + constrains: + - astroid >=2,<5 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/asttokens?source=hash-mapping + size: 28797 + timestamp: 1763410017955 +- conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda + sha256: ea8486637cfb89dc26dc9559921640cd1d5fd37e5e02c33d85c94572139f2efe + md5: b85e84cb64c762569cc1a760c2327e0a + depends: + - python >=3.10 + - typing_extensions >=4.0.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/async-lru?source=hash-mapping + size: 22949 + timestamp: 1773926359134 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab + md5: c6b0543676ecb1fb2d7643941fe375f2 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/attrs?source=hash-mapping + size: 64927 + timestamp: 1773935801332 +- pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + name: autopep8 + version: 2.3.2 + sha256: ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128 + requires_dist: + - pycodestyle>=2.12.0 + - tomli ; python_full_version < '3.11' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770 + md5: f1976ce927373500cc19d3c0b2c85177 + depends: + - python >=3.10 + - python + constrains: + - pytz >=2015.7 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/babel?source=compressed-mapping + size: 7684321 + timestamp: 1772555330347 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py311h6b1f9c4_0.conda + sha256: a23717f9dc06924b988b92ba8dca0f4cb9154cac954457a40adb2f19d57896de + md5: aa8c3009fd8903bebdcb22fbcb4c0dea + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 245341 + timestamp: 1777848716685 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py313h18e8e13_0.conda + sha256: f92ccaef33713bb66d563e8e829dcdb643e1de8c0d29246a999943c68bb8eb6e + md5: cfe95f3eab50bc3dbd4c3a03f2674bd8 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 241034 + timestamp: 1777848719564 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py311h36d4fbb_0.conda + sha256: 2e6399a3663e7e20134a208cd09befc44ce1e9efe94d1d36d8911db04f186270 + md5: 3ee7e8ae532366221658b80baee36dc0 + depends: + - python + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 244743 + timestamp: 1777848723707 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py313h7208f8c_0.conda + sha256: 257b234caffc760e48c8d7e642d6d0d8bac4341ca19c7df098421358eff636a1 + md5: 84e922fe79295051f6925d7f8d71c98c + depends: + - python + - __osx >=11.0 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 242188 + timestamp: 1777848729476 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py311h71c1bcc_0.conda + sha256: f551f6d7f1a62be18e4165772d300d21e0bd925956bcd9c2a605cabf9a2835d0 + md5: a0b8a2723108680aa6bb85fe3e413e76 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=hash-mapping + size: 243538 + timestamp: 1777848744191 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py313h2a31948_0.conda + sha256: b9c1465c03cb6ff79fb4f2feb21955b9b89783f868c900cad45dbcb8d81ab429 + md5: ef16917e1231df5c6f2ba245c8fe35e4 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 240632 + timestamp: 1777848740331 +- pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl + name: backrefs + version: '7.0' + sha256: f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a + requires_dist: + - regex ; extra == 'extras' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl + name: backrefs + version: '7.0' + sha256: a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475 + requires_dist: + - regex ; extra == 'extras' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py311h902ca64_1.conda + sha256: 978ce415ca2868f8415ce54a9c33fe2d2c9efd6a78dc1a6fe79a4bc3d055fe66 + md5: 28b014780cae10a8435d012ef08c0202 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.11.* *_cp311 + constrains: + - __glibc >=2.17 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/bcrypt?source=hash-mapping + size: 292775 + timestamp: 1762497725251 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.conda + sha256: cdf7496797af275f8bb5edd270aa06303dde623c7dd3a5941b0ce085d8f4cdc5 + md5: b59ec3796cba93d0db5f71e361176f27 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/bcrypt?source=hash-mapping + size: 292710 + timestamp: 1762497710166 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py311h71babbd_1.conda + sha256: 79594899d1f2472a6d95c3d489268bb9103ba6bab043d9faca379d989074aeb3 + md5: 00ad8eed3ec75b6ec425526968c72e61 + depends: + - python + - python 3.11.* *_cpython + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/bcrypt?source=hash-mapping + size: 268519 + timestamp: 1762497846273 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.conda + sha256: c8e3b95ad2665dc16ac37888fb91dd481fa759bad1fbf799451d12d6bfaec600 + md5: 4b14cba28eaf98170c2ddd7e900efa07 + depends: + - python + - __osx >=11.0 + - python 3.13.* *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - __osx >=11.0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/bcrypt?source=hash-mapping + size: 267465 + timestamp: 1762497730829 +- conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py311hf51aa87_1.conda + sha256: b4cc5ee88f5d19381923519b1f6a1bbc73752f5b1528e4a1df0a3a802e497d13 + md5: fd25556009eae0faab9fc6286150d477 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/bcrypt?source=hash-mapping + size: 170642 + timestamp: 1762497737769 +- conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.conda + sha256: 21baf1316a8160fd3b83e7128803735b107f47c58ba5d5305a372fd6c679d42b + md5: 2a664b5cc93fbc9d7ca595b206a299f0 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/bcrypt?source=hash-mapping + size: 170702 + timestamp: 1762497739995 +- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + sha256: bf1e71c3c0a5b024e44ff928225a0874fc3c3356ec1a0b6fe719108e6d1288f6 + md5: 5267bef8efea4127aacd1f4e1f149b6e + depends: + - python >=3.10 + - soupsieve >=1.2 + - typing-extensions + license: MIT + license_family: MIT + purls: + - pkg:pypi/beautifulsoup4?source=hash-mapping + size: 90399 + timestamp: 1764520638652 +- pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + name: bidict + version: 0.23.1 + sha256: 5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5 + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + sha256: f8ff1f98423674278964a46c93a1766f9e91960d44efd91c6c3ed56a33813f46 + md5: 7c5ebdc286220e8021bf55e6384acd67 + depends: + - python >=3.10 + - webencodings + - python + constrains: + - tinycss2 >=1.1.0,<1.5 + license: Apache-2.0 AND MIT + purls: + - pkg:pypi/bleach?source=hash-mapping + size: 142008 + timestamp: 1770719370680 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + sha256: 7c07a865e5e4cca233cc4e0eb3f0f5ff6c90776461687b4fb0b1764133e1fd61 + md5: f11a319b9700b203aa14c295858782b6 + depends: + - bleach ==6.3.0 pyhcf101f3_1 + - tinycss2 + license: Apache-2.0 AND MIT + purls: [] + size: 4409 + timestamp: 1770719370682 +- pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + name: blinker + version: 1.9.0 + sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311h66f275b_1.conda + sha256: c36eb061d9ead85f97644cfb740d485dba9b8823357f35c17851078e95e975c1 + md5: 86daecb8e4ed1042d5dc6efbe0152590 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 hb03c661_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 367573 + timestamp: 1764017405384 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + sha256: dadec2879492adede0a9af0191203f9b023f788c18efd45ecac676d424c458ae + md5: 6c4d3597cf43f3439a51b2b13e29a4ba + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.2.0 hb03c661_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 367721 + timestamp: 1764017371123 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py311hdc60ec4_1.conda + sha256: 617545ec0e97d35ed2ff7852f2581a20c0dda80b366d0c42a43706687f971ba8 + md5: 150cbf381febcf0a5e470a8d066e1bc0 + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 359588 + timestamp: 1764018467340 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_1.conda + sha256: 2e21dccccd68bedd483300f9ab87a425645f6776e6e578e10e0dd98c946e1be9 + md5: b03732afa9f4f54634d94eb920dfb308 + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 359568 + timestamp: 1764018359470 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py311hc5da9e4_1.conda + sha256: 1803c838946d79ef6485ae8c7dafc93e28722c5999b059a34118ef758387a4c9 + md5: b0c459f98ac5ea504a9d9df6242f7ee1 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hfd05255_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 335333 + timestamp: 1764018370925 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_1.conda + sha256: 3558006cd6e836de8dff53cbe5f0b9959f96ea6a6776b4e14f1c524916dd956c + md5: 916a39a0261621b8c33e9db2366dd427 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hfd05255_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 335605 + timestamp: 1764018132514 +- pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + name: build + 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.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.10' +- pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl + name: bumps + version: 1.0.4 + sha256: 78b8cfaf9fbcbf2fd77f6d4a2f8c906b0e03a794804ba6caf64d56d6f6cce4d4 + requires_dist: + - numpy + - scipy + - h5py + - dill + - cloudpickle + - matplotlib + - blinker + - aiohttp + - python-socketio + - plotly + - mpld3 + - msgpack + - uncertainties + - build ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff ; extra == 'dev' + - wheel ; extra == 'dev' + - setuptools ; extra == 'dev' + - sphinx ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 + md5: d2ffd7602c02f2b316fd921d39876885 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260182 + timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + sha256: 540fe54be35fac0c17feefbdc3e29725cce05d7367ffedfaaa1bdda234b019df + md5: 620b85a3f45526a8bc4d23fd78fc22f0 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 124834 + timestamp: 1771350416561 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + sha256: 76dfb71df5e8d1c4eded2dbb5ba15bb8fb2e2b0fe42d94145d5eed4c75c35902 + md5: 4cb8e6b48f67de0b018719cdf1136306 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 56115 + timestamp: 1771350256444 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e + md5: 920bb03579f15389b9e512095ad995b7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 207882 + timestamp: 1765214722852 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + sha256: 2995f2aed4e53725e5efbc28199b46bf311c3cab2648fc4f10c2227d6d5fa196 + md5: bcb3cba70cf1eec964a03b4ba7775f01 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 180327 + timestamp: 1765215064054 +- 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: 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: 131039 + timestamp: 1776865545798 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + noarch: python + sha256: 561e6660f26c35d137ee150187d89767c988413c978e1b712d53f27ddf70ea17 + md5: 9b347a7ec10940d3f7941ff6c460b551 + depends: + - cached_property >=1.5.2,<1.5.3.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 4134 + timestamp: 1615209571450 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + sha256: 6dbf7a5070cc43d90a1e4c2ec0c541c69d8e30a0e25f50ce9f6e4a432e42c5d7 + md5: 576d629e47797577ab0f1b351297ef4a + depends: + - python >=3.6 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cached-property?source=hash-mapping + size: 11065 + timestamp: 1615209567874 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda + sha256: 989db6e5957c4b44fa600c68c681ec2f36a55e48f7c7f1c073d5e91caa8cd878 + md5: 929471569c93acefb30282a22060dcd5 + depends: + - python >=3.10 + license: ISC + purls: + - pkg:pypi/certifi?source=compressed-mapping + size: 135656 + timestamp: 1776866680878 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_1.conda + sha256: 3ad13377356c86d3a945ae30e9b8c8734300925ef81a3cb0a9db0d755afbe7bb + md5: 3912e4373de46adafd8f1e97e4bd166b + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 303338 + timestamp: 1761202960110 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda + sha256: 2162a91819945c826c6ef5efe379e88b1df0fe9a387eeba23ddcf7ebeacd5bd6 + md5: d0616e7935acab407d1543b28c446f6f + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 298357 + timestamp: 1761202966461 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py311hd10dc20_1.conda + sha256: 1ffde698463d6e7ed571bdb85cb17bfc1d3a107c026d20047995512dcc2749ec + md5: 4d7f6780e36f18e7601811dddf3bbec5 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 294848 + timestamp: 1761203196617 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_1.conda + sha256: 1fa69651f5e81c25d48ac42064db825ed1a3e53039629db69f86b952f5ce603c + md5: 050374657d1c7a4f2ea443c0d0cbd9a0 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 291376 + timestamp: 1761203583358 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py311h3485c13_1.conda + sha256: c9caca6098e3d92b1a269159b759d757518f2c477fbbb5949cb9fee28807c1f1 + md5: f02335db0282d5077df5bc84684f7ff9 + depends: + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 297941 + timestamp: 1761203850323 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_1.conda + sha256: f867a11f42bb64a09b232e3decf10f8a8fe5194d7e3a216c6bac9f40483bd1c6 + md5: 55b44664f66a2caf584d72196aa98af9 + depends: + - pycparser + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 292681 + timestamp: 1761203203673 +- pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + name: cfgv + version: 3.5.0 + sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl + name: chardet + version: 7.4.3 + sha256: 7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-win_amd64.whl + name: chardet + version: 7.4.3 + sha256: e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl + name: chardet + version: 7.4.3 + sha256: 4fbff1907925b0c5a1064cffb5e040cd5e338585c9c552625f30de6bc2f3107a + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-win_amd64.whl + name: chardet + version: 7.4.3 + sha256: ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: chardet + version: 7.4.3 + sha256: bfc134b70c846c21ead8e43ada3ae1a805fff732f6922f8abcf2ff27b8f6493d + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: chardet + version: 7.4.3 + sha256: 4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + sha256: 3f9483d62ce24ecd063f8a5a714448445dc8d9e201147c46699fc0033e824457 + md5: a9167b9571f3baa9d448faa2139d1089 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=compressed-mapping + size: 58872 + timestamp: 1775127203018 +- pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + name: click + version: 8.3.3 + sha256: a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 + requires_dist: + - colorama ; sys_platform == 'win32' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + name: cloudpickle + version: 3.1.2 + sha256: 9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/colorama?source=hash-mapping + size: 27011 + timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 + md5: 2da13f2b299d8e1995bafbbe9689a2f7 + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/comm?source=hash-mapping + size: 14690 + timestamp: 1753453984907 +- pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl + name: contourpy + version: 1.3.3 + sha256: 23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + name: contourpy + version: 1.3.3 + sha256: 1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: contourpy + version: 1.3.3 + sha256: 4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: contourpy + version: 1.3.3 + sha256: 51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + name: contourpy + version: 1.3.3 + sha256: d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl + name: contourpy + version: 1.3.3 + sha256: 3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + sha256: 2ceb1f7edb5e5943f7af69e4d6b3339b2e2580e7c9eef76efed4d18d05ae49db + md5: 12baf87bfa62ac066dbe2526f5d89de1 + depends: + - python >=3.10 + - colorama >=0.4.6 + - dunamai >=1.7.0 + - funcy >=1.17 + - jinja2 >=3.1.5 + - jinja2-ansible-filters >=1.3.1 + - packaging >=23.0 + - pathspec >=0.9.0 + - plumbum >=1.6.9 + - prompt-toolkit <3.0.52 + - pydantic >=2.4.2 + - pygments >=2.7.1 + - pyyaml >=5.3.1 + - questionary >=1.8.1 + - eval-type-backport >=0.1.3,<0.3.0 + - platformdirs >=4.3.6 + - typing_extensions >=4.0.0,<5.0.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/copier?source=hash-mapping + size: 58609 + timestamp: 1777562504238 +- pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl + name: coverage + version: 7.13.5 + sha256: 941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl + name: coverage + version: 7.13.5 + sha256: 145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl + name: coverage + version: 7.13.5 + sha256: 631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.13.5 + sha256: ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.13.5 + sha256: 78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl + name: coverage + version: 7.13.5 + sha256: 258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + noarch: generic + sha256: 836b92c209d4b6b9fb28bd51bd788b22a0c5492ae95eec2724e65a15ed4ab2e1 + md5: 3a8a8b87e72f95b54689fb588e154ec9 + depends: + - python >=3.13,<3.14.0a0 + - python_abi * *_cp313 + license: Python-2.0 + purls: [] + size: 48530 + timestamp: 1775613723457 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py311h2005dd1_0.conda + sha256: 838cde68e70f4e0dc6458613316fa0e50eee5cf86cafb1cee4b7ca1a701a8436 + md5: 96f6e551c7ff30c95c1c8b4d2d1c5c9f + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=2.0 + - libgcc >=14 + - openssl >=3.5.6,<4.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - __glibc >=2.17 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=compressed-mapping + size: 1913519 + timestamp: 1777966158377 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda + sha256: 7c10cdde9f46d4b990e9c90ffb72bfb672b56d2ecae243d95582c9a9fc417ebf + md5: 2310978ddde7f742d0b7a642d4894388 + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=2.0 + - libgcc >=14 + - openssl >=3.5.6,<4.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1916864 + timestamp: 1777966204947 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py311hbb43c59_0.conda + sha256: 6e59412439c8b51ee5f05b3c50b05b43991fa544a895226168fab63be9ba28f9 + md5: cbc7fb4eb2049a4a6cd2c452afd2f7bd + depends: + - __osx >=11.0 + - cffi >=2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1855763 + timestamp: 1777966249758 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda + sha256: 2adca86f420558eaaad8b8a7ecffadc4a23f980c02716d0fd5bee3f51879e76b + md5: 730fd8d2542ece52b71bc4fbb80ceb10 + depends: + - __osx >=11.0 + - cffi >=2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - __osx >=11.0 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1854514 + timestamp: 1777966313532 +- conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py311h2098ed6_0.conda + sha256: a6fca5c54f019724d08fc3d9e54c60ca8c0c8aa8b54571fec3e713bbb64a55c3 + md5: 27def055167d830e4ce6c6821eaee591 + depends: + - cffi >=2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1645132 + timestamp: 1777966325193 +- conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda + sha256: 1fbf66094059379d72f06d3cbb9df5277352a95d25a68d55d1dd60fa6acca5c6 + md5: c216777102e0b49ed307e181df3903d2 + depends: + - cffi >=2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1646298 + timestamp: 1777966340031 +- pypi: https://files.pythonhosted.org/packages/35/8c/a917ef8741729ef8b3228f815240f4859717e600f9498359a6c411ed6992/crysfml-0.6.2-cp313-cp313-macosx_14_0_arm64.whl + name: crysfml + version: 0.6.2 + sha256: 0010858646240f6c64e2711c33ccfffd94ee6602859babd1186785e28d6c5c11 + requires_dist: + - numpy + requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/64/2e/ba6b404a8a0e7c954cdf0fdd2b21723dc41def60f16b69b9b1cbb2e22d91/crysfml-0.6.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: crysfml + version: 0.6.2 + sha256: 734e2180b2c427e77cc9cf9cee4a3651ad563adab28dfe79051f004489f8546c + requires_dist: + - numpy + requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/ed/01/e1da2f721c3a8feec311a16924c42fb28c37445528df0747f47dec5232d5/crysfml-0.6.2-cp313-cp313-win_amd64.whl + name: crysfml + version: 0.6.2 + sha256: b32ddbf4010ce61a535182d58c12c410ed4a0f5de2259e7ef605e7fbcce40be3 + requires_dist: + - numpy + requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl + name: cryspy + version: 0.11.0 + sha256: 0b650655a0fbdc3cfcb28826c2ab9fbc5f491e32e1ea9a47d9b75976cd43f26f + requires_dist: + - numpy + - scipy + - pycifstar + - matplotlib +- pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + name: cssselect2 + version: 0.9.0 + sha256: 6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563 + requires_dist: + - tinycss2 + - webencodings + - sphinx ; extra == 'doc' + - furo ; extra == 'doc' + - pytest ; extra == 'test' + - ruff ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + name: cyclebane + version: 24.10.0 + sha256: 902dd318667e4a222afc270cc5bc72c67d5d6047d2e0e1c36018885fb80f5e5d + requires_dist: + - networkx + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + name: cycler + version: 0.12.1 + sha256: 85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 + requires_dist: + - ipython ; extra == 'docs' + - matplotlib ; extra == 'docs' + - numpydoc ; extra == 'docs' + - sphinx ; extra == 'docs' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + name: darkdetect + version: 0.8.0 + sha256: a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85 + requires_dist: + - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'macos-listener' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl + name: dask + version: 2026.3.0 + sha256: be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d + requires_dist: + - click>=8.1 + - cloudpickle>=3.0.0 + - fsspec>=2021.9.0 + - packaging>=20.0 + - partd>=1.4.0 + - pyyaml>=5.3.1 + - toolz>=0.12.0 + - importlib-metadata>=4.13.0 ; python_full_version < '3.12' + - numpy>=1.24 ; extra == 'array' + - dask[array] ; extra == 'dataframe' + - pandas>=2.0 ; extra == 'dataframe' + - pyarrow>=16.0 ; extra == 'dataframe' + - distributed>=2026.3.0,<2026.3.1 ; extra == 'distributed' + - bokeh>=3.1.0 ; extra == 'diagnostics' + - jinja2>=2.10.3 ; extra == 'diagnostics' + - dask[array,dataframe,diagnostics,distributed] ; extra == 'complete' + - pyarrow>=16.0 ; extra == 'complete' + - lz4>=4.3.2 ; extra == 'complete' + - pandas[test] ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-rerunfailures ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - pre-commit ; extra == 'test' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py311hc665b79_0.conda + sha256: e69be2be543c4d4898895d8aebe758bc683c5a1198583ad676f5719782a07131 + md5: 400e4667a12884216df869cad5fb004b + depends: + - python + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 2733654 + timestamp: 1769744984842 +- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda + sha256: 8d76d4eeb5a8e3c5666880b465593fdf4a44f47fbbe89ff5b8f9abbe43026326 + md5: e94dbbec2589f3b1d821f43a4bf2ab45 + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 2872698 + timestamp: 1769744980407 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py311h8948835_0.conda + sha256: 093b015e9abf27fb4d3b4f7e52417d35cd69a99fab8b95ec5c6c3983275c46ba + md5: 150c921424bc9f08c0378f8a6ae58d05 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 2668163 + timestamp: 1769745020016 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda + sha256: 372464b345220758769f49e76d125933008abec7048df4077a851adcc79da1e8 + md5: b3a832c19cfa5dfcce7575750ef693ed + depends: + - python + - libcxx >=19 + - python 3.13.* *_cp313 + - __osx >=11.0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 2761021 + timestamp: 1769744996428 +- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py311h5dfdfe8_0.conda + sha256: 661e5c582b1f853a46a78d4bb6e55f2bfdac66e68d015e111f1580a11c28abbf + md5: 683be2cd10e80a367790b3083ce529b7 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 3940002 + timestamp: 1769745017274 +- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda + sha256: f6fe9cbd9e3d3c09f173eeb43676a58bc6169e97da0d190e0201e40828a3a62b + md5: 75eb3091b05924429a3a8d2a9bbdfac2 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 4003589 + timestamp: 1769745018248 +- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 + md5: 9ce473d1d1be1cc3810856a48b3fab32 + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/decorator?source=hash-mapping + size: 14129 + timestamp: 1740385067843 +- conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be + md5: 961b3a227b437d82ad7054484cfa71b2 + depends: + - python >=3.6 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/defusedxml?source=hash-mapping + size: 24062 + timestamp: 1615232388757 +- pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + name: dfo-ls + version: 1.6.5 + sha256: d147d42e471e240f9abf8bc38351a88f555ea6a8fcfd83119bbbf93c36f75ab2 + requires_dist: + - setuptools + - numpy + - scipy>=1.11 + - pandas + - pytest ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - trustregion>=1.1 ; extra == 'trustregion' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/15/fe/b426215b6843ee70285521e2dd52f08a32096fe1d68e5b92fbdaffdf8ca3/diffpy_pdffit2-1.6.0-cp313-cp313-win_amd64.whl + name: diffpy-pdffit2 + version: 1.6.0 + sha256: f1bba407eac8ff8ef1264ad5a1d3dff840e2d0a5f71e9fb0c0b9b06d5a8eb139 + requires_dist: + - diffpy-structure + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/33/eb/f9f1ded8e4db9638f9530c3782eb01f5ab04945f4cb9e597a51c203fa4c5/diffpy_pdffit2-1.6.0.tar.gz + name: diffpy-pdffit2 + version: 1.6.0 + sha256: 11a65466f8790f5ac7ae45f2f3fc0d5d116d156d274bcfc079df653123d080e2 + requires_dist: + - diffpy-structure + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/d3/80/722402571443031989e87a5861f26f2e897778768906dff1f998b42f235d/diffpy_pdffit2-1.6.0-cp313-cp313-macosx_11_0_arm64.whl + name: diffpy-pdffit2 + version: 1.6.0 + sha256: 8837088196ddabd688deb77740c3f03ddc33f0df6964a8e3507701c32b67b8b2 + requires_dist: + - diffpy-structure + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl + name: diffpy-structure + version: 3.4.0 + sha256: bd0f06a96635d80316f51ebc0a08003bdeb2cb48c9bb18cbed1455ac60645e48 + requires_dist: + - numpy + - pycifrw + - diffpy-utils + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl + name: diffpy-utils + version: 3.7.2 + sha256: 6100600736791a8e4638e3dd476704f4dabe3cab75bcb5c60c83c16a2032519a + requires_dist: + - numpy + - xraydb + - scipy + requires_python: '>=3.10,<3.15' +- pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + name: dill + version: 0.4.1 + sha256: 1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d + requires_dist: + - objgraph>=1.7.2 ; extra == 'graph' + - gprof2dot>=2022.7.29 ; extra == 'profile' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + name: distlib + version: 0.4.0 + sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 +- pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl + name: dnspython + version: 2.8.0 + sha256: 01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af + requires_dist: + - black>=25.1.0 ; extra == 'dev' + - coverage>=7.0 ; extra == 'dev' + - flake8>=7 ; extra == 'dev' + - hypercorn>=0.17.0 ; extra == 'dev' + - mypy>=1.17 ; extra == 'dev' + - pylint>=3 ; extra == 'dev' + - pytest-cov>=6.2.0 ; extra == 'dev' + - pytest>=8.4 ; extra == 'dev' + - quart-trio>=0.12.0 ; extra == 'dev' + - sphinx-rtd-theme>=3.0.0 ; extra == 'dev' + - sphinx>=8.2.0 ; extra == 'dev' + - twine>=6.1.0 ; extra == 'dev' + - wheel>=0.45.0 ; extra == 'dev' + - cryptography>=45 ; extra == 'dnssec' + - h2>=4.2.0 ; extra == 'doh' + - httpcore>=1.0.0 ; extra == 'doh' + - httpx>=0.28.0 ; extra == 'doh' + - aioquic>=1.2.0 ; extra == 'doq' + - idna>=3.10 ; extra == 'idna' + - trio>=0.30 ; extra == 'trio' + - wmi>=1.5.1 ; sys_platform == 'win32' and extra == 'wmi' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + name: docstring-parser-fork + version: 0.0.14 + sha256: 4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af + requires_dist: + - docstring-parser[docs] ; extra == 'dev' + - docstring-parser[test] ; extra == 'dev' + - pre-commit>=2.16.0 ; python_full_version >= '3.9' and extra == 'dev' + - pydoctor>=25.4.0 ; extra == 'docs' + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl + name: docstripy + version: 0.7.2 + sha256: c4ba35de6c1b1c51f7afad4a46d8953aad55dce1a490d198f7e98c8c63efefda + requires_dist: + - nbformat + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + sha256: 930f21584babebdabc8310f894557d032211f6cb427f7028c7c92cab6fe6cc1f + md5: dbb824a3a87ac1e2ce02f8227be67e74 + depends: + - importlib-metadata >=1.6.0 + - packaging >=20.9 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/dunamai?source=hash-mapping + size: 30866 + timestamp: 1775597880526 +- pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl + name: easydiffraction + version: 0.16.0 + sha256: 2691a1e175974ca79e0ec3c219d92b77f277c38fb3b0b8d25f6f7e99696bf70f + requires_dist: + - asciichartpy + - asteval + - bumps + - colorama + - crysfml + - cryspy + - darkdetect + - dfo-ls + - diffpy-pdffit2 + - diffpy-utils + - essdiffraction + - gemmi + - lmfit + - numpy + - pandas + - plotly + - pooch + - py3dmol + - rich + - scipy + - sympy + - tabulate + - typeguard + - typer + - uncertainties + - varname + - build ; extra == 'dev' + - copier ; extra == 'dev' + - docstripy ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - mike ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-autorefs ; extra == 'dev' + - mkdocs-jupyter ; extra == 'dev' + - mkdocs-markdownextradata-plugin ; extra == 'dev' + - mkdocs-material ; extra == 'dev' + - mkdocs-plugin-inline-svg ; extra == 'dev' + - mkdocstrings-python ; extra == 'dev' + - nbmake ; extra == 'dev' + - nbqa ; extra == 'dev' + - nbstripout ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-benchmark ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' + - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.12' +- pypi: ./ + name: easyreflectometry + version: 1.5.0+devdirty9 + sha256: 6cb81566f4ddb1401f1f6cfdac62844e904c9e177f74d0526912a44ae0489a16 + requires_dist: + - bumps + - easyscience + - orsopy + - pooch + - refl1d>=1.0.0 + - refnx + - scipp + - svglib<1.6 ; sys_platform == 'darwin' or sys_platform == 'linux' + - xhtml2pdf + - build ; extra == 'dev' + - copier ; extra == 'dev' + - docstripy ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - mike ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-autorefs ; extra == 'dev' + - mkdocs-jupyter ; extra == 'dev' + - mkdocs-markdownextradata-plugin ; extra == 'dev' + - mkdocs-material ; extra == 'dev' + - mkdocs-plugin-inline-svg ; extra == 'dev' + - mkdocstrings-python ; extra == 'dev' + - nbmake ; extra == 'dev' + - nbqa ; extra == 'dev' + - nbstripout ; extra == 'dev' + - plopp ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' + - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.11' + editable: true +- pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl + name: easyscience + 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 == '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' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + name: email-validator + version: 2.3.0 + sha256: 80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 + requires_dist: + - dnspython>=2.0.0 + - idna>=2.0.0 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl + name: essdiffraction + version: 26.5.1 + sha256: 8a6c779078c71be250714619214069221ab7968a69580d4e4d3f4b3e9a1a53ad + requires_dist: + - dask>=2022.1.0 + - essreduce>=26.4.0 + - graphviz + - numpy>=2 + - plopp>=26.2.0 + - pythreejs>=2.4.1 + - sciline>=25.4.1 + - scipp>=25.11.0 + - scippneutron>=26.3.0 + - scippnexus>=23.12.0 + - tof>=25.12.0 + - ncrystal[cif]>=4.1.0 + - spglib!=2.7 + - pandas>=2.1.2 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - ipywidgets>=8.1.7 ; extra == 'test' + - autodoc-pydantic ; extra == 'docs' + - ipykernel ; extra == 'docs' + - ipympl ; extra == 'docs' + - ipython!=8.7.0 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbsphinx ; extra == 'docs' + - pandas ; extra == 'docs' + - pooch ; extra == 'docs' + - pydata-sphinx-theme>=0.14 ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-design ; extra == 'docs' + - sphinxcontrib-bibtex ; extra == 'docs' + - pyarrow ; extra == 'docs' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl + name: essreduce + version: 26.4.1 + sha256: 1758a18fffca9c7c2a6fa9547cf87bf45f9d52fc3ccbdffcf7524f71bc060424 + requires_dist: + - sciline>=25.11.0 + - scipp>=26.3.1 + - scippneutron>=25.11.1 + - scippnexus>=25.6.0 + - graphviz>=0.20 ; extra == 'test' + - ipywidgets>=8.1 ; extra == 'test' + - matplotlib>=3.10.7 ; extra == 'test' + - numba>=0.63 ; extra == 'test' + - pooch>=1.9.0 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - scipy>=1.14 ; extra == 'test' + - tof>=25.12.0 ; extra == 'test' + - autodoc-pydantic ; extra == 'docs' + - graphviz>=0.20 ; extra == 'docs' + - ipykernel ; extra == 'docs' + - ipython!=8.7.0 ; extra == 'docs' + - ipywidgets>=8.1 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbsphinx ; extra == 'docs' + - numba>=0.63 ; extra == 'docs' + - plopp ; extra == 'docs' + - pydata-sphinx-theme>=0.14 ; extra == 'docs' + - sphinx>=7 ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-design ; extra == 'docs' + - tof>=25.12.0 ; extra == 'docs' + requires_python: '>=3.11' +- conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + sha256: 05ffdcb83903c159bfbb78a07fbcce6fd6dda41df9c55ed75e0eb1db5528048f + md5: 879479fda1dddb002fdc4885cea33740 + depends: + - eval_type_backport >=0.2.2,<0.2.3.0a0 + - python >=3.9 + license: MIT + license_family: MIT + purls: [] + size: 6662 + timestamp: 1734857849281 +- conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + sha256: 2d721421a60676216e10837a240c75e2190e093920a4016a469fa9a62c95ab5f + md5: 8681d7f876da5e66a1c7fce424509383 + depends: + - python >=3.9 + constrains: + - eval-type-backport >=0.2.2,<0.2.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/eval-type-backport?source=hash-mapping + size: 11520 + timestamp: 1734857840035 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + size: 21333 + timestamp: 1763918099466 +- pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl + name: execnet + version: 2.1.2 + sha256: 67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec + requires_dist: + - hatch ; extra == 'testing' + - pre-commit ; extra == 'testing' + - pytest ; extra == 'testing' + - tox ; extra == 'testing' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad + md5: ff9efb7f7469aed3c4a8106ffa29593c + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/executing?source=hash-mapping + size: 30753 + timestamp: 1756729456476 +- pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + name: filelock + version: 3.29.0 + sha256: 96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + name: fonttools + version: 4.62.1 + sha256: 7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056 + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + name: fonttools + version: 4.62.1 + sha256: c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79 + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl + name: fonttools + version: 4.62.1 + sha256: 40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7 + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: fonttools + version: 4.62.1 + sha256: 1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl + name: fonttools + version: 4.62.1 + sha256: e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2 + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: fonttools + version: 4.62.1 + sha256: 6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1 + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + name: format-docstring + version: 0.2.7 + sha256: c9d50eafebe0f260e3270ca662ff3a0ed4050f64d95e352f8c5f88d9aede42d6 + requires_dist: + - click>=8.0 + - jupyter-notebook-parser>=0.1.4 + - tomli>=1.1.0 ; python_full_version < '3.11' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 + md5: d3549fd50d450b6d9e7dddff25dd2110 + depends: + - cached-property >=1.3.0 + - python >=3.9,<4 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/fqdn?source=hash-mapping + size: 16705 + timestamp: 1733327494780 +- pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl + name: freetype-py + version: 2.5.1 + sha256: 0b7f8e0342779f65ca13ef8bc103938366fecade23e6bb37cb671c2b8ad7f124 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl + name: frozenlist + version: 1.8.0 + sha256: ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl + name: frozenlist + version: 1.8.0 + sha256: f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: frozenlist + version: 1.8.0 + sha256: 2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl + name: frozenlist + version: 1.8.0 + sha256: fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: frozenlist + version: 1.8.0 + sha256: fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + name: frozenlist + version: 1.8.0 + sha256: 878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + name: fsspec + version: 2026.4.0 + sha256: 11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2 + requires_dist: + - adlfs ; extra == 'abfs' + - adlfs ; extra == 'adl' + - pyarrow>=1 ; extra == 'arrow' + - dask ; extra == 'dask' + - distributed ; extra == 'dask' + - pre-commit ; extra == 'dev' + - ruff>=0.5 ; extra == 'dev' + - numpydoc ; extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - yarl ; extra == 'doc' + - dropbox ; extra == 'dropbox' + - dropboxdrivefs ; extra == 'dropbox' + - requests ; extra == 'dropbox' + - adlfs ; extra == 'full' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'full' + - dask ; extra == 'full' + - distributed ; extra == 'full' + - dropbox ; extra == 'full' + - dropboxdrivefs ; extra == 'full' + - fusepy ; extra == 'full' + - gcsfs>2024.2.0 ; extra == 'full' + - libarchive-c ; extra == 'full' + - ocifs ; extra == 'full' + - panel ; extra == 'full' + - paramiko ; extra == 'full' + - pyarrow>=1 ; extra == 'full' + - pygit2 ; extra == 'full' + - requests ; extra == 'full' + - s3fs>2024.2.0 ; extra == 'full' + - smbprotocol ; extra == 'full' + - tqdm ; extra == 'full' + - fusepy ; extra == 'fuse' + - gcsfs>2024.2.0 ; extra == 'gcs' + - pygit2 ; extra == 'git' + - requests ; extra == 'github' + - gcsfs ; extra == 'gs' + - panel ; extra == 'gui' + - pyarrow>=1 ; extra == 'hdfs' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'http' + - libarchive-c ; extra == 'libarchive' + - ocifs ; extra == 'oci' + - s3fs>2024.2.0 ; extra == 's3' + - paramiko ; extra == 'sftp' + - smbprotocol ; extra == 'smb' + - paramiko ; extra == 'ssh' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test' + - numpy ; extra == 'test' + - pytest ; extra == 'test' + - pytest-asyncio!=0.22.0 ; extra == 'test' + - pytest-benchmark ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-recording ; extra == 'test' + - pytest-rerunfailures ; extra == 'test' + - requests ; extra == 'test' + - aiobotocore>=2.5.4,<3.0.0 ; extra == 'test-downstream' + - dask[dataframe,test] ; extra == 'test-downstream' + - moto[server]>4,<5 ; extra == 'test-downstream' + - pytest-timeout ; extra == 'test-downstream' + - xarray ; extra == 'test-downstream' + - adlfs ; extra == 'test-full' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test-full' + - backports-zstd ; python_full_version < '3.14' and extra == 'test-full' + - cloudpickle ; extra == 'test-full' + - dask ; extra == 'test-full' + - distributed ; extra == 'test-full' + - dropbox ; extra == 'test-full' + - dropboxdrivefs ; extra == 'test-full' + - fastparquet ; extra == 'test-full' + - fusepy ; extra == 'test-full' + - gcsfs ; extra == 'test-full' + - jinja2 ; extra == 'test-full' + - kerchunk ; extra == 'test-full' + - libarchive-c ; extra == 'test-full' + - lz4 ; extra == 'test-full' + - notebook ; extra == 'test-full' + - numpy ; extra == 'test-full' + - ocifs ; extra == 'test-full' + - pandas<3.0.0 ; extra == 'test-full' + - panel ; extra == 'test-full' + - paramiko ; extra == 'test-full' + - pyarrow ; extra == 'test-full' + - pyarrow>=1 ; extra == 'test-full' + - pyftpdlib ; extra == 'test-full' + - pygit2 ; extra == 'test-full' + - pytest ; extra == 'test-full' + - pytest-asyncio!=0.22.0 ; extra == 'test-full' + - pytest-benchmark ; extra == 'test-full' + - pytest-cov ; extra == 'test-full' + - pytest-mock ; extra == 'test-full' + - pytest-recording ; extra == 'test-full' + - pytest-rerunfailures ; extra == 'test-full' + - python-snappy ; extra == 'test-full' + - requests ; extra == 'test-full' + - smbprotocol ; extra == 'test-full' + - tqdm ; extra == 'test-full' + - urllib3 ; extra == 'test-full' + - zarr ; extra == 'test-full' + - zstandard ; python_full_version < '3.14' and extra == 'test-full' + - tqdm ; extra == 'tqdm' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + sha256: 4a3e3e86b7b49aaa2a0faa57da2bcf39f7ee858499e8335132f83bfeed79191e + md5: 84f8955e99a8944fdee49da39edb0add + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/funcy?source=hash-mapping + size: 30249 + timestamp: 1734381235500 +- pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: gemmi + version: 0.7.5 + sha256: 750b4d9751aaf1460ac4f0f45308ddced25f47bcf7a30355eb3b1f779f03952a + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl + name: gemmi + version: 0.7.5 + sha256: c7d8b08c33fe6ba375223306149092440c69cbfbd55c3d3e3436e5fb315a225d + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl + name: gemmi + version: 0.7.5 + sha256: ad1f72ffa24adbfaf259e11471f6f071a668667f6ca846051f3bfea024fd337d + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + name: ghp-import + version: 2.1.0 + sha256: 8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 + requires_dist: + - python-dateutil>=2.8.1 + - twine ; extra == 'dev' + - markdown ; extra == 'dev' + - flake8 ; extra == 'dev' + - wheel ; extra == 'dev' +- pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + name: gitdb + version: 4.0.12 + sha256: 67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf + requires_dist: + - smmap>=3.0.1,<6 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + name: gitpython + version: 3.1.50 + sha256: d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9 + requires_dist: + - gitdb>=4.0.1,<5 + - typing-extensions>=3.10.0.2 ; python_full_version < '3.10' + - coverage[toml] ; extra == 'test' + - ddt>=1.1.1,!=1.4.3 ; extra == 'test' + - mock ; python_full_version < '3.8' and extra == 'test' + - mypy==1.18.2 ; python_full_version >= '3.9' and extra == 'test' + - pre-commit ; extra == 'test' + - pytest>=7.3.1 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-sugar ; extra == 'test' + - typing-extensions ; python_full_version < '3.11' and extra == 'test' + - sphinx>=7.4.7,<8 ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + name: graphviz + version: '0.21' + sha256: 54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42 + requires_dist: + - build ; extra == 'dev' + - wheel ; extra == 'dev' + - twine ; extra == 'dev' + - flake8 ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - pep8-naming ; extra == 'dev' + - tox>=3 ; extra == 'dev' + - pytest>=7,<8.1 ; extra == 'test' + - pytest-mock>=3 ; extra == 'test' + - pytest-cov ; extra == 'test' + - coverage ; extra == 'test' + - sphinx>=5,<7 ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-rtd-theme>=0.2.5 ; extra == 'docs' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: greenlet + version: 3.5.0 + sha256: 2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13 + requires_dist: + - sphinx ; extra == 'docs' + - furo ; extra == 'docs' + - objgraph ; extra == 'test' + - psutil ; extra == 'test' + - setuptools ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl + name: greenlet + version: 3.5.0 + sha256: 728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5 + requires_dist: + - sphinx ; extra == 'docs' + - furo ; extra == 'docs' + - objgraph ; extra == 'test' + - psutil ; extra == 'test' + - setuptools ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl + name: griffelib + version: 2.0.2 + sha256: 925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1 + requires_dist: + - pip>=24.0 ; extra == 'pypi' + - platformdirs>=4.2 ; extra == 'pypi' + - wheel>=0.42 ; extra == 'pypi' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb + md5: b8993c19b0c32a2f7b66cbb58ca27069 + depends: + - python >=3.10 + - typing_extensions + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h11?source=compressed-mapping + size: 39069 + timestamp: 1767729720872 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.1,<5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=hash-mapping + size: 95967 + timestamp: 1756364871835 +- pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl + name: h5py + version: 3.16.0 + sha256: fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + name: h5py + version: 3.16.0 + sha256: 42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + name: h5py + version: 3.16.0 + sha256: 9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl + name: h5py + version: 3.16.0 + sha256: c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl + name: h5py + version: 3.16.0 + sha256: 18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl + name: h5py + version: 3.16.0 + sha256: 17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba + md5: 0a802cb9888dd14eeefc611f05c40b6e + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=hash-mapping + size: 30731 + timestamp: 1737618390337 +- pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + name: html5lib + version: '1.1' + sha256: 0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d + requires_dist: + - six>=1.9 + - webencodings + - genshi ; extra == 'all' + - chardet>=2.2 ; extra == 'all' + - lxml ; platform_python_implementation == 'CPython' and extra == 'all' + - chardet>=2.2 ; extra == 'chardet' + - genshi ; extra == 'genshi' + - lxml ; platform_python_implementation == 'CPython' and extra == 'lxml' + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b + md5: 4f14640d58e2cc0aa0819d9d8ba125bb + depends: + - python >=3.9 + - h11 >=0.16 + - h2 >=3,<5 + - sniffio 1.* + - anyio >=4.0,<5.0 + - certifi + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/httpcore?source=hash-mapping + size: 49483 + timestamp: 1745602916758 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 + md5: d6989ead454181f4f9bc987d3dc4e285 + depends: + - anyio + - certifi + - httpcore 1.* + - idna + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/httpx?source=hash-mapping + size: 63082 + timestamp: 1733663449209 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a + md5: c80d8a3b84358cb967fa81e7075fbc8a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 12723451 + timestamp: 1773822285671 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 + md5: f1182c91c0de31a7abd40cedf6a5ebef + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 12361647 + timestamp: 1773822915649 +- pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl + name: identify + 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.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=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 + depends: + - python >=3.10 + - zipp >=3.20 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-metadata?source=compressed-mapping + size: 34387 + timestamp: 1773931568510 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + sha256: a563a51aa522998172838e867e6dedcf630bc45796e8612f5a1f6d73e9c8125a + md5: 0ba6225c279baf7ea9473a62ea0ec9ae + depends: + - python >=3.10 + - zipp >=3.1.0 + constrains: + - importlib-resources >=7.1.0,<7.1.1.0a0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-resources?source=compressed-mapping + size: 34809 + timestamp: 1776068839274 +- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + name: iniconfig + version: 2.3.0 + sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + name: interrogate + version: 1.7.0 + sha256: b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12 + requires_dist: + - attrs + - click>=7.1 + - colorama + - py + - tabulate + - tomli ; python_full_version < '3.11' + - cairosvg ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-autobuild ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-mock ; extra == 'dev' + - coverage[toml] ; extra == 'dev' + - wheel ; extra == 'dev' + - pre-commit ; extra == 'dev' + - sphinx ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - cairosvg ; extra == 'png' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-mock ; extra == 'tests' + - coverage[toml] ; extra == 'tests' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_0.conda + sha256: d7421c54944dec04d2671e23196a15583d48f309d06fbcf995b5dfa6d586a5e9 + md5: 4dee387356d58bdc4b686ae3424fbd9e + depends: + - python >=3.10 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/invoke?source=hash-mapping + size: 133811 + timestamp: 1775579645825 +- pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl + name: ipydatawidgets + version: 4.3.5 + sha256: d590cdb7c364f2f6ab346f20b9d2dd661d27a834ef7845bc9d7113118f05ec87 + requires_dist: + - ipywidgets>=7.0.0 + - numpy + - traittypes>=0.2.0 + - sphinx ; extra == 'docs' + - recommonmark ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - pytest>=4 ; extra == 'test' + - pytest-cov ; extra == 'test' + - nbval>=0.9.2 ; extra == 'test' + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda + sha256: 5c1f3e874adaf603449f2b135d48f168c5d510088c78c229bda0431268b43b27 + md5: 4b53d436f3fbc02ce3eeaf8ae9bebe01 + depends: + - appnope + - __osx + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.8.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio >=1.4 + - packaging >=22 + - psutil >=5.7 + - python >=3.10 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipykernel?source=hash-mapping + size: 132260 + timestamp: 1770566135697 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda + sha256: 9cdadaeef5abadca4113f92f5589db19f8b7df5e1b81cb0225f7024a3aedefa3 + md5: b3a7d5842f857414d9ae831a799444dd + depends: + - __win + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.8.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio >=1.4 + - packaging >=22 + - psutil >=5.7 + - python >=3.10 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipykernel?source=hash-mapping + size: 132382 + timestamp: 1770566174387 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda + sha256: b77ed58eb235e5ad80e742b03caeed4bbc2a2ef064cb9a2deee3b75dfae91b2a + md5: 8b267f517b81c13594ed68d646fd5dcb + depends: + - __linux + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.8.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio >=1.4 + - packaging >=22 + - psutil >=5.7 + - python >=3.10 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipykernel?source=hash-mapping + size: 133644 + timestamp: 1770566133040 +- pypi: https://files.pythonhosted.org/packages/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl + name: ipympl + version: 0.10.0 + sha256: a09c4f0ff86490cc62aed45e53b912fb706e3ec3506c4a51ce4a670d6667f5ce + requires_dist: + - ipython<10 + - ipywidgets>=7.6.0,<9 + - matplotlib>=3.5.0,<4 + - numpy + - pillow + - traitlets<6 + - intersphinx-registry ; extra == 'docs' + - myst-nb ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-thebe ; extra == 'docs' + - sphinx-togglebutton ; extra == 'docs' + - sphinx>=1.5 ; extra == 'docs' + - 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.13.0-pyh53cf698_0.conda + sha256: a0af49948a1842dfd15a0b0b2fd56c94ddbd07e07a6c8b4bc70d43015eafaff0 + md5: 73e9657cd19605740d21efb14d8d0cb9 + depends: + - __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.11 + - stack_data >=0.6.0 + - traitlets >=5.13.0 + - typing_extensions >=4.6 + - pexpect >4.6 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython?source=compressed-mapping + size: 651632 + timestamp: 1777038396606 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda + sha256: f252ec33597115ff21cbb31051f6f9be34ca36cbbbf3d266b597660d8d8edde9 + md5: 5631ab99e902463d9dd4221e5b4eab6d + depends: + - __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.11 + - stack_data >=0.6.0 + - traitlets >=5.13.0 + - typing_extensions >=4.6 + - colorama >=0.4.4 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - 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 + depends: + - pygments + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython-pygments-lexers?source=hash-mapping + size: 13993 + timestamp: 1737123723464 +- pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + name: ipywidgets + version: 8.1.8 + sha256: ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e + requires_dist: + - comm>=0.1.3 + - ipython>=6.1.0 + - traitlets>=4.3.1 + - widgetsnbextension~=4.0.14 + - jupyterlab-widgets~=3.0.15 + - jsonschema ; extra == 'test' + - ipykernel ; extra == 'test' + - pytest>=3.6.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytz ; extra == 'test' + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed + md5: 0b0154421989637d424ccf0f104be51a + depends: + - arrow >=0.15.0 + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/isoduration?source=hash-mapping + size: 19832 + timestamp: 1733493720346 +- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + sha256: 92c4d217e2dc68983f724aa983cca5464dcb929c566627b26a2511159667dba8 + md5: a4f4c5dc9b80bc50e0d3dc4e6e8f1bd9 + depends: + - parso >=0.8.3,<0.9.0 + - python >=3.9 + license: Apache-2.0 AND MIT + purls: + - pkg:pypi/jedi?source=hash-mapping + size: 843646 + timestamp: 1733300981994 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d + depends: + - markupsafe >=2.0 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2?source=compressed-mapping + size: 120685 + timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + sha256: a36789229ca9ce5315265b9d425abce9acb4691f5864aea69e935020545a9acb + md5: 974c5b3e353f031cfcf2365c9d375926 + depends: + - jinja2 + - python >=3.9 + - pyyaml + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2-ansible-filters?source=hash-mapping + size: 20315 + timestamp: 1734906203051 +- conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + sha256: 9daa95bd164c8fa23b3ab196e906ef806141d749eddce2a08baa064f722d25fa + md5: 1269891272187518a0a75c286f7d0bbf + depends: + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/json5?source=compressed-mapping + size: 34731 + timestamp: 1774655440045 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + sha256: a3d10301b6ff399ba1f3d39e443664804a3d28315a4fb81e745b6817845f70ae + md5: 89bf346df77603055d3c8fe5811691e6 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpointer?source=hash-mapping + size: 14190 + timestamp: 1774311356147 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 + md5: ada41c863af263cc4c5fcbaff7c3e4dc + depends: + - attrs >=22.2.0 + - jsonschema-specifications >=2023.3.6 + - python >=3.10 + - referencing >=0.28.4 + - rpds-py >=0.25.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jsonschema?source=hash-mapping + size: 82356 + timestamp: 1767839954256 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 + md5: 439cd0f567d697b20a8f45cb70a1005a + depends: + - python >=3.10 + - referencing >=0.31.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jsonschema-specifications?source=hash-mapping + size: 19236 + timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + sha256: 6886fc61e4e4edd38fd38729976b134e8bd2143f7fce56cc80d7ac7bac99bce1 + md5: 8368d58342d0825f0843dc6acdd0c483 + depends: + - jsonschema >=4.26.0,<4.26.1.0a0 + - fqdn + - idna + - isoduration + - jsonpointer >1.13 + - rfc3339-validator + - rfc3986-validator >0.1.0 + - rfc3987-syntax >=1.1.0 + - uri-template + - webcolors >=24.6.0 + license: MIT + license_family: MIT + purls: [] + size: 4740 + timestamp: 1767839954258 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda + sha256: 3766e2ae59641c172cec8a821528bfa6bf9543ffaaeb8b358bfd5259dcf18e4e + md5: 0c3b465ceee138b9c39279cc02e5c4a0 + depends: + - importlib-metadata >=4.8.3 + - jupyter_server >=1.1.2 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-lsp?source=compressed-mapping + size: 61633 + timestamp: 1775136333147 +- pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + name: jupyter-notebook-parser + version: 0.1.4 + sha256: 27b3b67cf898684e646d569f017cb27046774ad23866cb0bdf51d5f76a46476b + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda + sha256: e402bd119720862a33229624ec23645916a7d47f30e1711a4af9e005162b84f3 + md5: 8a3d6d0523f66cf004e563a50d9392b3 + depends: + - jupyter_core >=5.1 + - python >=3.10 + - python-dateutil >=2.8.2 + - pyzmq >=25.0 + - tornado >=6.4.1 + - traitlets >=5.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-client?source=hash-mapping + size: 112785 + timestamp: 1767954655912 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda + sha256: ed709a6c25b731e01563521ef338b93986cd14b5bc17f35e9382000864872ccc + md5: a8db462b01221e9f5135be466faeb3e0 + depends: + - __win + - pywin32 + - platformdirs >=2.5 + - python >=3.10 + - traitlets >=5.3 + - python + constrains: + - pywin32 >=300 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-core?source=hash-mapping + size: 64679 + timestamp: 1760643889625 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + sha256: 1d34b80e5bfcd5323f104dbf99a2aafc0e5d823019d626d0dce5d3d356a2a52a + md5: b38fe4e78ee75def7e599843ef4c1ab0 + depends: + - __unix + - python + - platformdirs >=2.5 + - python >=3.10 + - traitlets >=5.3 + - python + constrains: + - pywin32 >=300 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-core?source=hash-mapping + size: 65503 + timestamp: 1760643864586 +- 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.10 + - python-json-logger >=2.0.4 + - pyyaml >=5.3 + - referencing + - rfc3339-validator + - rfc3986-validator >=0.1.1 + - traitlets >=5.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-events?source=compressed-mapping + size: 24002 + timestamp: 1776861872237 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda + sha256: 04fb8ea7749f67abaf76df6257bf86688e1389ceed55eb4fb0176fd2e882dbd6 + md5: 5ee7945accf0f215ddd6055d25d7cd83 + depends: + - anyio >=3.1.0 + - argon2-cffi >=21.1 + - jinja2 >=3.0.3 + - jupyter_client >=7.4.4 + - jupyter_core >=4.12,!=5.0.* + - jupyter_events >=0.11.0 + - jupyter_server_terminals >=0.4.4 + - nbconvert-core >=6.4.4 + - nbformat >=5.3.0 + - overrides >=5.0 + - packaging >=22.0 + - prometheus_client >=0.9 + - python >=3.10 + - pyzmq >=24 + - send2trash >=1.8.2 + - terminado >=0.8.3 + - tornado >=6.2.0 + - traitlets >=5.6.0 + - websocket-client >=1.7 + - python + license: BSD-3-Clause + purls: + - pkg:pypi/jupyter-server?source=compressed-mapping + size: 360522 + timestamp: 1778060967727 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + sha256: 5eda79ed9f53f590031d29346abd183051263227dd9ee667b5ca1133ce297654 + md5: 7b8bace4943e0dc345fc45938826f2b8 + depends: + - python >=3.10 + - terminado >=0.8.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-server-terminals?source=hash-mapping + size: 22052 + timestamp: 1768574057200 +- 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 + - ipykernel >=6.5.0,!=6.30.0 + - jinja2 >=3.0.3 + - jupyter-lsp >=2.0.0 + - jupyter_core + - jupyter_server >=2.4.0,<3 + - jupyterlab_server >=2.28.0,<3 + - notebook-shim >=0.2 + - packaging + - python >=3.10 + - setuptools >=41.1.0 + - tomli >=1.2.2 + - tornado >=6.2.0 + - traitlets + license: BSD-3-Clause + license_family: BSD + purls: + - 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 + sha256: 45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8 + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 + md5: fd312693df06da3578383232528c468d + depends: + - pygments >=2.4.1,<3 + - python >=3.9 + constrains: + - jupyterlab >=4.0.8,<5.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-pygments?source=hash-mapping + size: 18711 + timestamp: 1733328194037 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + sha256: 381d2d6a259a3be5f38a69463e0f6c5dcf1844ae113058007b51c3bef13a7cee + md5: a63877cb23de826b1620d3adfccc4014 + depends: + - babel >=2.10 + - jinja2 >=3.0.3 + - json5 >=0.9.0 + - jsonschema >=4.18 + - jupyter_server >=1.21,<3 + - packaging >=21.3 + - python >=3.10 + - requests >=2.31 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-server?source=hash-mapping + size: 51621 + timestamp: 1761145478692 +- pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + name: jupyterquiz + version: 2.9.6.4 + sha256: f8c4418f6c827454523fc882a30d744b585cb58ac1ae277769c3059d04fc272b +- pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + name: jupytext + version: 1.19.1 + sha256: d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9 + requires_dist: + - markdown-it-py>=1.0 + - mdit-py-plugins + - nbformat + - packaging + - pyyaml + - tomli ; python_full_version < '3.11' + - autopep8 ; extra == 'dev' + - black ; extra == 'dev' + - flake8 ; extra == 'dev' + - gitpython ; extra == 'dev' + - ipykernel ; extra == 'dev' + - isort ; extra == 'dev' + - jupyter-fs[fs]>=1.0 ; extra == 'dev' + - jupyter-server!=2.11 ; extra == 'dev' + - marimo>=0.17.6,<=0.19.4 ; extra == 'dev' + - nbconvert ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-asyncio ; extra == 'dev' + - pytest-cov>=2.6.1 ; extra == 'dev' + - pytest-randomly ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-gallery>=0.8 ; extra == 'dev' + - myst-parser ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - pytest ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - pytest-randomly ; extra == 'test' + - pytest-xdist ; extra == 'test' + - black ; extra == 'test-cov' + - ipykernel ; extra == 'test-cov' + - jupyter-server!=2.11 ; extra == 'test-cov' + - nbconvert ; extra == 'test-cov' + - pytest ; extra == 'test-cov' + - pytest-asyncio ; extra == 'test-cov' + - pytest-cov>=2.6.1 ; extra == 'test-cov' + - pytest-randomly ; extra == 'test-cov' + - pytest-xdist ; extra == 'test-cov' + - autopep8 ; extra == 'test-external' + - black ; extra == 'test-external' + - flake8 ; extra == 'test-external' + - gitpython ; extra == 'test-external' + - ipykernel ; extra == 'test-external' + - isort ; extra == 'test-external' + - jupyter-fs[fs]>=1.0 ; extra == 'test-external' + - jupyter-server!=2.11 ; extra == 'test-external' + - marimo>=0.17.6,<=0.19.4 ; extra == 'test-external' + - nbconvert ; extra == 'test-external' + - pre-commit ; extra == 'test-external' + - pytest ; extra == 'test-external' + - pytest-asyncio ; extra == 'test-external' + - pytest-randomly ; extra == 'test-external' + - pytest-xdist ; extra == 'test-external' + - sphinx ; extra == 'test-external' + - sphinx-gallery>=0.8 ; extra == 'test-external' + - black ; extra == 'test-functional' + - pytest ; extra == 'test-functional' + - pytest-asyncio ; extra == 'test-functional' + - pytest-randomly ; extra == 'test-functional' + - pytest-xdist ; extra == 'test-functional' + - black ; extra == 'test-integration' + - ipykernel ; extra == 'test-integration' + - jupyter-server!=2.11 ; extra == 'test-integration' + - nbconvert ; extra == 'test-integration' + - pytest ; extra == 'test-integration' + - pytest-asyncio ; extra == 'test-integration' + - pytest-randomly ; extra == 'test-integration' + - pytest-xdist ; extra == 'test-integration' + - bash-kernel ; extra == 'test-ui' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 + md5: b38117a3c920364aff79f870c984b4a3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-or-later + purls: [] + size: 134088 + timestamp: 1754905959823 +- pypi: https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl + name: kiwisolver + version: 1.5.0 + sha256: 0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: kiwisolver + version: 1.5.0 + sha256: 332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: kiwisolver + version: 1.5.0 + sha256: 2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl + name: kiwisolver + version: 1.5.0 + sha256: dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl + name: kiwisolver + version: 1.5.0 + sha256: beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl + name: kiwisolver + version: 1.5.0 + sha256: 0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + sha256: 3e307628ca3527448dd1cb14ad7bb9d04d1d28c7d4c5f97ba196ae984571dd25 + md5: fb53fb07ce46a575c5d004bbc96032c2 + depends: + - __glibc >=2.17,<3.0.a0 + - keyutils >=1.6.3,<2.0a0 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - libgcc >=14 + - libstdcxx >=14 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 1386730 + timestamp: 1769769569681 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + sha256: c0a0bf028fe7f3defcdcaa464e536cf1b202d07451e18ad83fdd169d15bef6ed + md5: e446e1822f4da8e5080a9de93474184d + depends: + - __osx >=11.0 + - libcxx >=19 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 1160828 + timestamp: 1769770119811 +- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + sha256: eb60f1ad8b597bcf95dee11bc11fe71a8325bc1204cf51d2bb1f2120ffd77761 + md5: 4432f52dc0c8eb6a7a6abc00a037d93c + depends: + - openssl >=3.5.5,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 751055 + timestamp: 1769769688841 +- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + sha256: 49570840fb15f5df5d4b4464db8ee43a6d643031a2bc70ef52120a52e3809699 + md5: 9b965c999135d43a3d0f7bd7d024e26a + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - 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 + name: lazy-loader + version: '0.5' + sha256: ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005 + requires_dist: + - packaging + - pytest>=8.0 ; extra == 'test' + - pytest-cov>=5.0 ; extra == 'test' + - coverage[toml]>=7.2 ; extra == 'test' + - pre-commit==4.3.0 ; extra == 'lint' + - changelist==0.5 ; extra == 'dev' + - spin==0.15 ; extra == 'dev' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c + md5: 18335a698559cdbcd86150a48bf54ba6 + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 728002 + timestamp: 1774197446916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + sha256: a7a4481a4d217a3eadea0ec489826a69070fcc3153f00443aa491ed21527d239 + md5: 6f7b4302263347698fd24565fbf11310 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + constrains: + - libabseil-static =20260107.1=cxx17* + - abseil-cpp =20260107.1 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 1384817 + timestamp: 1770863194876 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + sha256: 756611fbb8d2957a5b4635d9772bd8432cb6ddac05580a6284cca6fdc9b07fca + md5: bb65152e0d7c7178c0f1ee25692c9fd1 + depends: + - __osx >=11.0 + - libcxx >=19 + constrains: + - abseil-cpp =20260107.1 + - libabseil-static =20260107.1=cxx17* + license: Apache-2.0 + license_family: Apache + purls: [] + size: 1229639 + timestamp: 1770863511331 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e + md5: 72c8fd1af66bd67bf580645b426513ed + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 79965 + timestamp: 1764017188531 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + sha256: a7cb9e660531cf6fbd4148cff608c85738d0b76f0975c5fc3e7d5e92840b7229 + md5: 006e7ddd8a110771134fcc4e1e3a6ffa + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 79443 + timestamp: 1764017945924 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b + md5: 366b40a69f0ad6072561c1d09301c886 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 34632 + timestamp: 1764017199083 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + sha256: 2eae444039826db0454b19b52a3390f63bfe24f6b3e63089778dd5a5bf48b6bf + md5: 079e88933963f3f149054eec2c487bc2 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: [] + size: 29452 + timestamp: 1764017979099 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d + md5: 4ffbb341c8b616aa2494b6afb26a0c5f + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 298378 + timestamp: 1764017210931 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + sha256: 01436c32bb41f9cb4bcf07dda647ce4e5deb8307abfc3abdc8da5317db8189d1 + md5: b2b7c8288ca1a2d71ff97a8e6a1e8883 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: [] + size: 290754 + timestamp: 1764018009077 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_0.conda + sha256: 76c3d3c702593b6ef57dc2f99c082193a99835526634b844f4a78ae3aecdb6c8 + md5: d0a2e921c5a89df73118cff1afed5213 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + size: 570968 + timestamp: 1778110367423 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 + md5: c277e0a4d549b03ac1e9d6cbbe3d017b + depends: + - ncurses + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 134676 + timestamp: 1738479519902 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + sha256: 66aa216a403de0bb0c1340a88d1a06adaff66bae2cfd196731aa24db9859d631 + md5: 44083d2d2c2025afca315c7a172eab2b + depends: + - ncurses + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 107691 + timestamp: 1738479560845 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 + md5: 172bf1cd1ff8629f2b1179945ed45055 + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 112766 + timestamp: 1702146165126 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + sha256: 95cecb3902fbe0399c3a7e67a5bed1db813e5ab0e22f4023a5e0f722f2cc214f + md5: 36d33e440c31857372a72137f78bacf5 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 107458 + timestamp: 1702146414478 +- 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.8.0.* + license: MIT + license_family: MIT + purls: [] + 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.8.0.* + license: MIT + license_family: MIT + purls: [] + 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.8.0.* + license: MIT + license_family: MIT + purls: [] + size: 71094 + timestamp: 1777846223617 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 + md5: a360c33a5abe61c07959e449fa1453eb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 58592 + timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + sha256: 6686a26466a527585e6a75cc2a242bf4a3d97d6d6c86424a441677917f28bec7 + md5: 43c04d9cb46ef176bb2a4c77e324d599 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 40979 + timestamp: 1769456747661 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + sha256: 59d01f2dfa8b77491b5888a5ab88ff4e1574c9359f7e229da254cdfe27ddc190 + md5: 720b39f5ec0610457b725eb3f396219a + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 45831 + timestamp: 1769456418774 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 + md5: 0aa00f03f9e39fb9876085dee11a85d4 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 he0feb66_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 1041788 + timestamp: 1771378212382 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 + md5: d5e96b1ed75ca01906b3d2469b4ce493 + depends: + - libgcc 15.2.0 he0feb66_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 27526 + timestamp: 1771378224552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 + md5: 239c5e9546c38a1e884d69effcf4c882 + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 603262 + timestamp: 1771378117851 +- 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.3.* + license: 0BSD + purls: [] + 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.3.* + license: 0BSD + purls: [] + 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.3.* + license: 0BSD + purls: [] + size: 106486 + timestamp: 1775825663227 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 + md5: 2c21e66f50753a083cbe6b80f38268fa + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 92400 + timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + sha256: 1089c7f15d5b62c622625ec6700732ece83be8b705da8c6607f4dabb0c4bd6d2 + md5: 57c4be259f5e0b99a5983799a228ae55 + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 73690 + timestamp: 1769482560514 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + sha256: 40dcd0b9522a6e0af72a9db0ced619176e7cfdb114855c7a64f278e73f8a7514 + md5: e4a9fc2bba3b022dad998c78856afe47 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 89411 + timestamp: 1769482314283 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + sha256: 663444d77a42f2265f54fb8b48c5450bfff4388d9c0f8253dd7855f0d993153f + md5: 2a45e7f8af083626f009645a6481f12d + depends: + - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.6,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 663344 + timestamp: 1773854035739 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + sha256: 2bc7bc3978066f2c274ebcbf711850cc9ab92e023e433b9631958a098d11e10a + md5: 6ea18834adbc3b33df9bd9fb45eaf95b + depends: + - __osx >=11.0 + - c-ares >=1.34.6,<2.0a0 + - libcxx >=19 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 576526 + timestamp: 1773854624224 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 + md5: d864d34357c3b65a4b731f78c0801dc4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-only + license_family: GPL + purls: [] + size: 33731 + timestamp: 1750274110928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda + sha256: 64e5c80cbce4680a2d25179949739a6def695d72c40ca28f010711764e372d97 + md5: 7af961ef4aa2c1136e11dd43ded245ab + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: ISC + purls: [] + size: 277661 + timestamp: 1772479381288 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda + sha256: df603472ea1ebd8e7d4fb71e4360fe48d10b11c240df51c129de1da2ff9e8227 + md5: 7cc5247987e6d115134ebab15186bc13 + depends: + - __osx >=11.0 + license: ISC + purls: [] + size: 248039 + timestamp: 1772479570912 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda + sha256: d915f4fa8ebbf237c7a6e511ed458f2cfdc7c76843a924740318a15d0dd33d6d + md5: da2aa614d16a795b3007b6f4a1318a81 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: ISC + purls: [] + size: 276860 + timestamp: 1772479407566 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda + sha256: 54cdcd3214313b62c2a8ee277e6f42150d9b748264c1b70d958bf735e420ef8d + md5: 7dc38adcbf71e6b38748e919e16e0dce + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + size: 954962 + timestamp: 1777986471789 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda + sha256: 49daec7c83e70d4efc17b813547824bc2bcf2f7256d84061d24fbfe537da9f74 + md5: 6681822ea9d362953206352371b6a904 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + size: 920047 + timestamp: 1777987051643 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda + sha256: e70562450332ca8954bc16f3455468cca5ef3695c7d7187ecc87f8fc3c70e9eb + md5: 7fea434a17c323256acc510a041b80d7 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + size: 1304178 + timestamp: 1777986510497 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e + md5: 1b08cd684f34175e4514474793d44bcb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.2.0 he0feb66_18 + constrains: + - libstdcxx-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 5852330 + timestamp: 1771378262446 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda + sha256: bc1b08c92626c91500fd9f26f2c797f3eb153b627d53e9c13cd167f1e12b2829 + md5: 38ffe67b78c9d4de527be8315e5ada2c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 40297 + timestamp: 1775052476770 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b + md5: 0f03292cc56bf91a077a134ea8747118 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 895108 + timestamp: 1753948278280 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda + sha256: 042c7488ad97a5629ec0a991a8b2a3345599401ecc75ad6a5af73b60e6db9689 + md5: c0d87c3c8e075daf1daf6c31b53e8083 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 421195 + timestamp: 1753948426421 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + size: 100393 + timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 63629 + timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + sha256: 361415a698514b19a852f5d1123c5da746d4642139904156ddfca7c922d23a05 + md5: bc5a5721b6439f2f62a84f2548136082 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 47759 + timestamp: 1774072956767 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 + md5: dbabbd6234dea34040e631f87676292f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 58347 + timestamp: 1774072851498 +- pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: llvmlite + version: 0.47.0 + sha256: 2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/34/0b/b9d1911cfefa61399821dfb37f486d83e0f42630a8d12f7194270c417002/llvmlite-0.47.0-cp311-cp311-macosx_11_0_arm64.whl + name: llvmlite + version: 0.47.0 + sha256: 74090f0dcfd6f24ebbef3f21f11e38111c4d7e6919b54c4416e1e357c3446b07 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: llvmlite + version: 0.47.0 + sha256: ca14f02e29134e837982497959a8e2193d6035235de1cb41a9cb2bd6da4eedbb + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl + name: llvmlite + version: 0.47.0 + sha256: c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl + name: llvmlite + version: 0.47.0 + sha256: a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl + name: llvmlite + version: 0.47.0 + sha256: c37d6eb7aaabfa83ab9c2ff5b5cdb95a5e6830403937b2c588b7490724e05327 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + name: lmfit + version: 1.3.4 + sha256: afce1593b42324d37ae2908249b0c55445e2f4c1a0474ff706a8e2f7b5d949fa + requires_dist: + - asteval>=1.0 + - numpy>=1.24 + - scipy>=1.10.0 + - uncertainties>=3.2.2 + - dill>=0.3.4 + - build ; extra == 'dev' + - check-wheel-contents ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - pre-commit ; extra == 'dev' + - twine ; extra == 'dev' + - cairosvg ; extra == 'doc' + - corner ; extra == 'doc' + - emcee>=3.0.0 ; extra == 'doc' + - ipykernel ; extra == 'doc' + - jupyter-sphinx>=0.2.4 ; extra == 'doc' + - matplotlib ; extra == 'doc' + - numdifftools ; extra == 'doc' + - pandas ; extra == 'doc' + - pillow ; extra == 'doc' + - pycairo ; sys_platform == 'win32' and extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-gallery>=0.10 ; extra == 'doc' + - sphinxcontrib-svg2pdfconverter ; extra == 'doc' + - sympy ; extra == 'doc' + - coverage ; extra == 'test' + - flaky ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - lmfit[dev,doc,test] ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl + name: locket + version: 1.0.0 + sha256: b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl + name: lxml + version: 6.1.0 + sha256: 183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl + name: lxml + version: 6.1.0 + sha256: a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl + name: lxml + version: 6.1.0 + sha256: cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl + name: lxml + version: 6.1.0 + sha256: 4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: lxml + version: 6.1.0 + sha256: 363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: lxml + version: 6.1.0 + sha256: e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + name: mando + version: 0.7.1 + sha256: 26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a + requires_dist: + - six + - argparse ; python_full_version < '2.7' + - funcsigs ; python_full_version < '3.3' + - rst2ansi ; extra == 'restructuredtext' +- pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + name: markdown + version: 3.10.2 + sha256: e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36 + requires_dist: + - coverage ; extra == 'testing' + - pyyaml ; extra == 'testing' + - mkdocs>=1.6 ; extra == 'docs' + - mkdocs-nature>=0.6 ; extra == 'docs' + - mdx-gh-links>=0.2 ; extra == 'docs' + - mkdocstrings[python]>=0.28.3 ; extra == 'docs' + - mkdocs-gen-files ; extra == 'docs' + - mkdocs-section-index ; extra == 'docs' + - mkdocs-literate-nav ; extra == 'docs' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + name: markdown-it-py + version: 4.1.0 + sha256: d4939a62a2dd0cd9cb80a191a711ba1d39bac8ed5ef9e9966895b0171c01c46d + requires_dist: + - mdurl~=0.1 + - psutil ; extra == 'benchmarking' + - pytest ; extra == 'benchmarking' + - pytest-benchmark ; extra == 'benchmarking' + - commonmark~=0.9 ; extra == 'compare' + - markdown~=3.4 ; extra == 'compare' + - mistletoe~=1.0 ; extra == 'compare' + - mistune~=3.0 ; extra == 'compare' + - panflute~=2.3 ; extra == 'compare' + - markdown-it-pyrs ; extra == 'compare' + - linkify-it-py>=1,<3 ; extra == 'linkify' + - mdit-py-plugins>=0.5.0 ; extra == 'plugins' + - gprof2dot ; extra == 'profiling' + - mdit-py-plugins>=0.5.0 ; extra == 'rtd' + - myst-parser ; extra == 'rtd' + - pyyaml ; extra == 'rtd' + - sphinx ; extra == 'rtd' + - sphinx-copybutton ; extra == 'rtd' + - sphinx-design ; extra == 'rtd' + - sphinx-book-theme~=1.0 ; extra == 'rtd' + - jupyter-sphinx ; extra == 'rtd' + - ipykernel ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + - pytest-timeout ; extra == 'testing' + - requests ; extra == 'testing' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + name: markdown-it-py + version: 4.2.0 + sha256: 9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a + requires_dist: + - mdurl~=0.1 + - psutil ; extra == 'benchmarking' + - pytest ; extra == 'benchmarking' + - pytest-benchmark ; extra == 'benchmarking' + - commonmark~=0.9 ; extra == 'compare' + - markdown~=3.4 ; extra == 'compare' + - mistletoe~=1.0 ; extra == 'compare' + - mistune~=3.0 ; extra == 'compare' + - panflute~=2.3 ; extra == 'compare' + - markdown-it-pyrs ; extra == 'compare' + - linkify-it-py>=1,<3 ; extra == 'linkify' + - mdit-py-plugins>=0.5.0 ; extra == 'plugins' + - gprof2dot ; extra == 'profiling' + - mdit-py-plugins>=0.5.0 ; extra == 'rtd' + - myst-parser ; extra == 'rtd' + - pyyaml ; extra == 'rtd' + - sphinx ; extra == 'rtd' + - sphinx-copybutton ; extra == 'rtd' + - sphinx-design ; extra == 'rtd' + - sphinx-book-theme~=1.0 ; extra == 'rtd' + - jupyter-sphinx ; extra == 'rtd' + - ipykernel ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + - pytest-timeout ; extra == 'testing' + - requests ; extra == 'testing' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py311h3778330_1.conda + sha256: 710e207b2e91308a34bcfe547c60ad86c1fa294827266ba18548c1fe1a9d8333 + md5: f9efdf9b0f3d0cc309d56af6edf2a6b0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 26756 + timestamp: 1772445078834 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda + sha256: 72ed7c0216541d65a17b171bf2eec4a3b81e9158d8ed48e59e1ecd3ae302d263 + md5: aeb9b9da79fd0258b3db091d1fefcd71 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 26100 + timestamp: 1772445154165 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py311hc290fe0_1.conda + sha256: d635f2b1d9e19e8e68c5d33150f7e4f62df08ef2ef0e85977f743e81939afc01 + md5: ff068874356bbc7f9bd2d793f809f44b + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=compressed-mapping + size: 26511 + timestamp: 1772445369187 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_1.conda + sha256: f62892a42948c61aa0a13d9a36ff811651f0a1102331223594aecf3cc042bece + md5: 0195d558b0c0ab8f4af3089af83067c5 + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 26009 + timestamp: 1772445537524 +- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py311h3f79411_1.conda + sha256: 3d37fb1900e31131f84549560e7a4bfea5f39aa3ecd73345fef1f33975cf0baa + md5: f55de41c947bdd2ff9bbeffedf8089f7 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 29362 + timestamp: 1772445178723 +- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py313hd650c13_1.conda + sha256: 9dc626b6c00bc2dbd2494df689876ff675b93d92636ba5df8e37b99040a1f6bc + md5: 5cc690ddf943700e0ef50a265df31f03 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 28992 + timestamp: 1772445161959 +- pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl + name: matplotlib + version: 3.10.9 + sha256: dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl + name: matplotlib + version: 3.10.9 + sha256: b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: matplotlib + version: 3.10.9 + sha256: 8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl + name: matplotlib + version: 3.10.9 + sha256: d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: matplotlib + version: 3.10.9 + sha256: 8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl + name: matplotlib + version: 3.10.9 + sha256: f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921 + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<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 + sha256: 9d690334de0cd1d22c51bc28420663f4277cfa60d34fa5cad1ce284a13f1d603 + md5: 00e120ce3e40bad7bfc78861ce3c4a25 + depends: + - python >=3.10 + - traitlets + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/matplotlib-inline?source=hash-mapping + size: 15175 + timestamp: 1761214578417 +- pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + name: mdit-py-plugins + version: 0.5.0 + sha256: 07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f + requires_dist: + - markdown-it-py>=2.0.0,<5.0.0 + - pre-commit ; extra == 'code-style' + - myst-parser ; extra == 'rtd' + - sphinx-book-theme ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + name: mdurl + version: 0.1.2 + sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + name: mergedeep + version: 1.3.4 + sha256: 70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + name: mike + version: 2.2.0 + sha256: e1f4981c1152eec7c2490a3401142292cc47d686194188416db2648fdfe1d040 + requires_dist: + - jinja2>=2.7 + - mkdocs~=1.0 + - pyparsing>=3.0 + - pyyaml>=5.1 + - pyyaml-env-tag + - verspec + - importlib-metadata ; python_full_version < '3.10' + - importlib-resources ; python_full_version < '3.10' + - coverage ; extra == 'dev' + - flake8-quotes ; extra == 'dev' + - flake8>=3.0 ; extra == 'dev' + - shtab ; extra == 'dev' + - coverage ; extra == 'test' + - flake8-quotes ; extra == 'test' + - flake8>=3.0 ; extra == 'test' + - shtab ; extra == 'test' +- 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 + purls: + - 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 + sha256: db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e + requires_dist: + - click>=7.0 + - colorama>=0.4 ; sys_platform == 'win32' + - ghp-import>=1.0 + - importlib-metadata>=4.4 ; python_full_version < '3.10' + - jinja2>=2.11.1 + - markdown>=3.3.6 + - markupsafe>=2.0.1 + - mergedeep>=1.3.4 + - mkdocs-get-deps>=0.2.0 + - packaging>=20.5 + - pathspec>=0.11.1 + - pyyaml-env-tag>=0.1 + - pyyaml>=5.1 + - watchdog>=2.0 + - babel>=2.9.0 ; extra == 'i18n' + - babel==2.9.0 ; extra == 'min-versions' + - click==7.0 ; extra == 'min-versions' + - colorama==0.4 ; sys_platform == 'win32' and extra == 'min-versions' + - ghp-import==1.0 ; extra == 'min-versions' + - importlib-metadata==4.4 ; python_full_version < '3.10' and extra == 'min-versions' + - jinja2==2.11.1 ; extra == 'min-versions' + - markdown==3.3.6 ; extra == 'min-versions' + - markupsafe==2.0.1 ; extra == 'min-versions' + - mergedeep==1.3.4 ; extra == 'min-versions' + - mkdocs-get-deps==0.2.0 ; extra == 'min-versions' + - packaging==20.5 ; extra == 'min-versions' + - pathspec==0.11.1 ; extra == 'min-versions' + - pyyaml-env-tag==0.1 ; extra == 'min-versions' + - pyyaml==5.1 ; extra == 'min-versions' + - watchdog==2.0 ; extra == 'min-versions' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + name: mkdocs-autorefs + version: 1.4.4 + sha256: 834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089 + requires_dist: + - markdown>=3.3 + - markupsafe>=2.0.1 + - mkdocs>=1.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + name: mkdocs-get-deps + version: 0.2.2 + sha256: e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650 + requires_dist: + - importlib-metadata>=4.3 ; python_full_version < '3.10' + - mergedeep>=1.3.4 + - platformdirs>=2.2.0 + - pyyaml>=5.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl + name: mkdocs-jupyter + version: 0.26.3 + sha256: cd6644fb578131157194d750fd4d10fc2fd8f1e84e00036ee62df3b5b4b84c82 + requires_dist: + - ipykernel>6.0.0,<8 + - jupytext>1.13.8,<2 + - mkdocs-material>9.0.0 + - mkdocs>=1.4.0,<2 + - nbconvert>=7.2.9,<8 + - pygments>2.12.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + name: mkdocs-markdownextradata-plugin + version: 0.2.6 + sha256: 34dd40870781784c75809596b2d8d879da783815b075336d541de1f150c94242 + requires_dist: + - mkdocs + - pyyaml + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl + name: mkdocs-material + version: 9.7.6 + sha256: 71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba + requires_dist: + - babel>=2.10 + - backrefs>=5.7.post1 + - colorama>=0.4 + - jinja2>=3.1 + - markdown>=3.2 + - mkdocs-material-extensions>=1.3 + - mkdocs>=1.6,<2 + - paginate>=0.5 + - pygments>=2.16 + - pymdown-extensions>=10.2 + - requests>=2.30 + - mkdocs-git-committers-plugin-2>=1.1 ; extra == 'git' + - mkdocs-git-revision-date-localized-plugin>=1.2.4 ; extra == 'git' + - cairosvg>=2.6 ; extra == 'imaging' + - pillow>=10.2 ; extra == 'imaging' + - mkdocs-minify-plugin>=0.7 ; extra == 'recommended' + - mkdocs-redirects>=1.2 ; extra == 'recommended' + - mkdocs-rss-plugin>=1.6 ; extra == 'recommended' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + name: mkdocs-material-extensions + version: 1.3.1 + sha256: adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + name: mkdocs-plugin-inline-svg + version: 0.1.0 + sha256: a5aab2d98a19b24019f8e650f54fc647c2f31e7d0e36fc5cf2d2161acc0ea49a + requires_dist: + - mkdocs + requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl + name: mkdocstrings + version: 1.0.4 + sha256: 63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b + requires_dist: + - jinja2>=3.1 + - markdown>=3.6 + - markupsafe>=1.1 + - mkdocs>=1.6 + - mkdocs-autorefs>=1.4 + - pymdown-extensions>=6.3 + - mkdocstrings-crystal>=0.3.4 ; extra == 'crystal' + - mkdocstrings-python-legacy>=0.2.1 ; extra == 'python-legacy' + - mkdocstrings-python>=1.16.2 ; extra == 'python' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl + name: mkdocstrings-python + version: 2.0.3 + sha256: 0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12 + requires_dist: + - mkdocstrings>=0.30 + - mkdocs-autorefs>=1.4 + - griffelib>=2.0 + - typing-extensions>=4.0 ; python_full_version < '3.11' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + name: mpld3 + version: 0.5.12 + sha256: bea31799a4041029a906f53f2662bbf1c49903e0c0bc712b412354158ec7cf54 + requires_dist: + - jinja2 + - matplotlib +- pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + name: mpltoolbox + version: 26.2.0 + sha256: cd2668db4216fc4d7c2ba37974961aa61445f1517527b645b6082930e35ba7f0 + requires_dist: + - matplotlib + - ipympl ; extra == 'test' + - pytest>=8.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + name: mpmath + version: 1.3.0 + sha256: a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c + requires_dist: + - pytest>=4.6 ; extra == 'develop' + - pycodestyle ; extra == 'develop' + - pytest-cov ; extra == 'develop' + - codecov ; extra == 'develop' + - wheel ; extra == 'develop' + - sphinx ; extra == 'docs' + - gmpy2>=2.1.0a4 ; platform_python_implementation != 'PyPy' and extra == 'gmpy' + - pytest>=4.6 ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py311h49ec1c0_0.conda + sha256: 59123ba4af69c1e7ca6b1b8ffcc40f2aff9a635814495bda3f4f555b3b929165 + md5: 0347d2804701019ac005528df9eb502c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 217556 + timestamp: 1776337480302 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_0.conda + sha256: a44d23085117672e4e19629ea3e6c53f516984322513c4bfdd052b1c7c6fc8ad + md5: 15c679f7133347d68058b688f3519cea + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 219767 + timestamp: 1776337423071 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py311hc949640_0.conda + sha256: 55ce08c77b6e87fcc5cca61a516e81fdcc1cd0969e0204fce0a32ca84118a148 + md5: 24707aa446fa7babd806397b8c5349a2 + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 207945 + timestamp: 1776338655947 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_0.conda + sha256: 63cdc0052bd638f1f3df6de438e69275b7b273b4b77dbd1ac34ce25d98cf973c + md5: 2dd8d3b25e88780abc058559bc7975c6 + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 214497 + timestamp: 1776338358818 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py311h3485c13_0.conda + sha256: d096ad02a47a61aeeade2ad3639cc300adf950790cca097717040d46ca5bff52 + md5: 6e6fd9a9012358254bebee33eb3d7f71 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 199272 + timestamp: 1776337711230 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_0.conda + sha256: 4e70a7b9bee6b9dadd1f038da6fcf1627cc53581d1cc01355b66d638ae55c0e3 + md5: 96575971ffc81497b83bf321f0b9e945 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 201017 + timestamp: 1776337591817 +- pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: multidict + version: 6.7.1 + sha256: 439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + name: multidict + version: 6.7.1 + sha256: 935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: multidict + version: 6.7.1 + sha256: 9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + name: multidict + version: 6.7.1 + sha256: 960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl + name: multidict + version: 6.7.1 + sha256: bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl + name: multidict + version: 6.7.1 + sha256: f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + name: narwhals + version: 2.20.0 + sha256: 16e750ea5507d4ba6e8d03455b5f93a535e0405976561baea235bca5dc9f475d + requires_dist: + - cudf-cu12>=24.10.0 ; extra == 'cudf' + - dask[dataframe]>=2024.8 ; extra == 'dask' + - duckdb>=1.1 ; extra == 'duckdb' + - ibis-framework>=6.0.0 ; extra == 'ibis' + - packaging ; extra == 'ibis' + - pyarrow-hotfix ; extra == 'ibis' + - rich ; extra == 'ibis' + - modin ; extra == 'modin' + - pandas>=1.1.3 ; extra == 'pandas' + - polars>=0.20.4 ; extra == 'polars' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - pyspark>=3.5.0 ; extra == 'pyspark' + - pyspark[connect]>=3.5.0 ; extra == 'pyspark-connect' + - duckdb>=1.1 ; extra == 'sql' + - sqlparse ; extra == 'sql' + - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda + sha256: 1b66960ee06874ddceeebe375d5f17fb5f393d025a09e15b830ad0c4fffb585b + md5: 00f5b8dafa842e0c27c1cd7296aa4875 + depends: + - jupyter_client >=6.1.12 + - jupyter_core >=4.12,!=5.0.* + - nbformat >=5.1 + - python >=3.8 + - traitlets >=5.4 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbclient?source=compressed-mapping + size: 28473 + timestamp: 1766485646962 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda + sha256: ab2ac79c5892c5434d50b3542d96645bdaa06d025b6e03734be29200de248ac2 + md5: 2bce0d047658a91b99441390b9b27045 + depends: + - beautifulsoup4 + - bleach-with-css !=5.0.0 + - defusedxml + - importlib-metadata >=3.6 + - jinja2 >=3.0 + - jupyter_core >=4.7 + - jupyterlab_pygments + - markupsafe >=2.0 + - mistune >=2.0.3,<4 + - nbclient >=0.5.0 + - nbformat >=5.7 + - packaging + - pandocfilters >=1.4.1 + - pygments >=2.4.1 + - python >=3.10 + - traitlets >=5.1 + - python + constrains: + - pandoc >=2.9.2,<4.0.0 + - nbconvert ==7.17.1 *_0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbconvert?source=compressed-mapping + size: 202229 + timestamp: 1775615493260 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 + md5: bbe1963f1e47f594070ffe87cdf612ea + depends: + - jsonschema >=2.6 + - jupyter_core >=4.12,!=5.0.* + - python >=3.9 + - python-fastjsonschema >=2.15 + - traitlets >=5.1 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbformat?source=hash-mapping + size: 100945 + timestamp: 1733402844974 +- pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + name: nbmake + version: 1.5.5 + sha256: c6fbe6e48b60cacac14af40b38bf338a3b88f47f085c54ac5b8639ff0babaf4b + requires_dist: + - ipykernel>=5.4.0 + - nbclient>=0.6.6 + - nbformat>=5.0.4 + - pygments>=2.7.3 + - pytest>=6.1.0 + requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + name: nbqa + version: 1.9.1 + sha256: 95552d2f6c2c038136252a805aa78d85018aef922586270c3a074332737282e5 + requires_dist: + - autopep8>=1.5 + - ipython>=7.8.0 + - tokenize-rt>=3.2.0 + - tomli + - black ; extra == 'toolchain' + - blacken-docs ; extra == 'toolchain' + - flake8 ; extra == 'toolchain' + - isort ; extra == 'toolchain' + - jupytext ; extra == 'toolchain' + - mypy ; extra == 'toolchain' + - pylint ; extra == 'toolchain' + - pyupgrade ; extra == 'toolchain' + - ruff ; extra == 'toolchain' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + name: nbstripout + version: 0.9.1 + sha256: ca027ee45742ee77e4f8e9080254f9a707f1161ba11367b82fdf4a29892c759e + requires_dist: + - nbformat + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl + name: ncrystal + version: 4.4.2 + sha256: e02fa7d743addc3fbea23287737a88b8d01192450fdca51554d3f9032fe4617c + requires_dist: + - ncrystal-core==4.4.2 + - ncrystal-python==4.4.2 + - spglib>=2.1.0 ; extra == 'composer' + - ase>=3.23.0 ; extra == 'cif' + - gemmi>=0.6.1 ; extra == 'cif' + - spglib>=2.1.0 ; extra == 'cif' + - endf-parserpy>=0.14.3 ; extra == 'endf' + - matplotlib>=3.6.0 ; extra == 'plot' + - ase>=3.23.0 ; extra == 'all' + - endf-parserpy>=0.14.3 ; extra == 'all' + - gemmi>=0.6.1 ; extra == 'all' + - matplotlib>=3.6.0 ; extra == 'all' + - spglib>=2.1.0 ; extra == 'all' + - pyyaml>=6.0.0 ; extra == 'devel' + - ase>=3.23.0 ; extra == 'devel' + - cppcheck ; extra == 'devel' + - endf-parserpy>=0.14.3 ; extra == 'devel' + - gemmi>=0.6.1 ; extra == 'devel' + - matplotlib>=3.6.0 ; extra == 'devel' + - mpmath>=1.3.0 ; extra == 'devel' + - numpy>=1.22 ; extra == 'devel' + - pybind11>=2.11.0 ; extra == 'devel' + - ruff>=0.8.1 ; extra == 'devel' + - simple-build-system>=1.6.0 ; extra == 'devel' + - spglib>=2.1.0 ; extra == 'devel' + - tomli>=2.0.0 ; python_full_version < '3.11' and extra == 'devel' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl + name: ncrystal-core + version: 4.4.2 + sha256: 9b28a90b63849e6a3a807a0a59f7c2ee57e4c64f5643b2dcb6a798ac8ccf666a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl + name: ncrystal-core + version: 4.4.2 + sha256: b7e6101a6850aa18cf441825214381614db444ffcba648de8266fe1c4d1024ce + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: ncrystal-core + version: 4.4.2 + sha256: d0d9c47cd017b7cefc52dde50546d7c151bfdd75d345e42e2b3e74ab5fe83c62 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl + name: ncrystal-python + version: 4.4.2 + sha256: f419318d088fade6bcff1e39e15baf6fe69fcf5306dd681fca1106d1f63a89ce + requires_dist: + - numpy>=1.22 + requires_python: '>=3.8' +- 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 >=14 + license: X11 AND BSD-3-Clause + purls: [] + 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: 805509 + timestamp: 1777423252320 +- conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 + md5: 598fd7d4d0de2455fb74f56063969a97 + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/nest-asyncio?source=hash-mapping + size: 11543 + timestamp: 1733325673691 +- pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + name: networkx + version: 3.6.1 + sha256: d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 + requires_dist: + - asv ; extra == 'benchmarking' + - virtualenv ; extra == 'benchmarking' + - numpy>=1.25 ; extra == 'default' + - scipy>=1.11.2 ; extra == 'default' + - matplotlib>=3.8 ; extra == 'default' + - pandas>=2.0 ; extra == 'default' + - pre-commit>=4.1 ; extra == 'developer' + - mypy>=1.15 ; extra == 'developer' + - sphinx>=8.0 ; extra == 'doc' + - pydata-sphinx-theme>=0.16 ; extra == 'doc' + - sphinx-gallery>=0.18 ; extra == 'doc' + - numpydoc>=1.8.0 ; extra == 'doc' + - pillow>=10 ; extra == 'doc' + - texext>=0.6.7 ; extra == 'doc' + - myst-nb>=1.1 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - osmnx>=2.0.0 ; extra == 'example' + - momepy>=0.7.2 ; extra == 'example' + - contextily>=1.6 ; extra == 'example' + - seaborn>=0.13 ; extra == 'example' + - cairocffi>=1.7 ; extra == 'example' + - igraph>=0.11 ; extra == 'example' + - scikit-learn>=1.5 ; extra == 'example' + - iplotx>=0.9.0 ; extra == 'example' + - lxml>=4.6 ; extra == 'extra' + - pygraphviz>=1.14 ; extra == 'extra' + - pydot>=3.0.1 ; extra == 'extra' + - sympy>=1.10 ; extra == 'extra' + - build>=0.10 ; extra == 'release' + - twine>=4.0 ; extra == 'release' + - wheel>=0.40 ; extra == 'release' + - changelist==0.5 ; extra == 'release' + - pytest>=7.2 ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pytest-mpl ; extra == 'test-extras' + - pytest-randomly ; extra == 'test-extras' + requires_python: '>=3.11,!=3.14.1' +- pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + name: nodeenv + version: 1.10.0 + sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' +- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda + sha256: d1a673d1418d9e956b6e4e46c23e72a511c5c1d45dc5519c947457427036d5e2 + md5: baffb1570b3918c784d4490babc52fbf + depends: + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.28,<3.0.a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libuv >=1.51.0,<2.0a0 + - c-ares >=1.34.6,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - libsqlite >=3.52.0,<4.0a0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* + - zstd >=1.5.7,<1.6.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + license: MIT + license_family: MIT + purls: [] + size: 18829340 + timestamp: 1774514313036 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda + sha256: 4782b172b3b8a557b60bf5f591821cf100e2092ba7a5494ce047dfa41626de26 + md5: ca8277c52fdface8bb8ebff7cd9a6f56 + depends: + - libcxx >=19 + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libuv >=1.51.0,<2.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + - c-ares >=1.34.6,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* + license: MIT + license_family: MIT + purls: [] + size: 17101803 + timestamp: 1774517834028 +- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + sha256: 5e38e51da1aa4bc352db9b4cec1c3e25811de0f4408edaa24e009a64de6dbfdf + md5: e626ee7934e4b7cb21ce6b721cff8677 + license: MIT + license_family: MIT + purls: [] + size: 31271315 + timestamp: 1774517904472 +- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + sha256: 7b920e46b9f7a2d2aa6434222e5c8d739021dbc5cc75f32d124a8191d86f9056 + md5: e7f89ea5f7ea9401642758ff50a2d9c1 + depends: + - jupyter_server >=1.8,<3 + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/notebook-shim?source=hash-mapping + size: 16817 + timestamp: 1733408419340 +- pypi: https://files.pythonhosted.org/packages/44/0b/0615dbedb98f5b32a35a53290fbdc6e22306968109278d7e58df82d7a9f6/numba-0.65.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: numba + version: 0.65.1 + sha256: f80ed83774b5173abd6581cd8d2165d1d38e13d2e5c8155c0c0b421784745420 + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl + name: numba + version: 0.65.1 + sha256: 1735c15c1134a5108b4d6a5c77fc0947924ea066a738dc09a52008c13df9cad3 + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/96/b3/650500c2eab4534d98e9166f4298e0f3c69c742afdf24e6eabccd1f16ad8/numba-0.65.1-cp311-cp311-macosx_12_0_arm64.whl + name: numba + version: 0.65.1 + sha256: 7020d74b19cdb8cff16506542fdd510756e28c5e7f3bd0b7f574f0f42272fcd9 + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: numba + version: 0.65.1 + sha256: c09f49117ef255e1f1c6dad0c7a1ed39868243862a73be5706793241a3755f1b + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bd/9a/af61ec03b3116c161fd7a06b9e8a265729a8718458333e8ffbb06d9a3978/numba-0.65.1-cp311-cp311-win_amd64.whl + name: numba + version: 0.65.1 + sha256: df40a5028a975b9ea66f6a2a3f7abbdbd541a863070e34ed367aff21141248e4 + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl + name: numba + version: 0.65.1 + sha256: 85be74c0d036842699a30058f82fb88fc5ffdc59f7615cab5792ea92914c9b62 + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl + name: numpy + version: 2.4.4 + sha256: 5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl + name: numpy + version: 2.4.4 + sha256: 86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl + name: numpy + version: 2.4.4 + sha256: 6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: numpy + version: 2.4.4 + sha256: df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: numpy + version: 2.4.4 + sha256: c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + name: numpy + version: 2.4.4 + sha256: 4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af + requires_python: '>=3.11' +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + sha256: c0ef482280e38c71a08ad6d71448194b719630345b0c9c60744a2010e8a8e0cb + md5: da1b85b6a87e141f5140bb9924cecab0 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3167099 + timestamp: 1775587756857 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + sha256: c91bf510c130a1ea1b6ff023e28bac0ccaef869446acd805e2016f69ebdc49ea + md5: 25dcccd4f80f1638428613e0d7c9b4e1 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3106008 + timestamp: 1775587972483 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + sha256: feb5815125c60f2be4a411e532db1ed1cd2d7261a6a43c54cb6ae90724e2e154 + md5: 05c7d624cff49dbd8db1ad5ba537a8a3 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 9410183 + timestamp: 1775589779763 +- pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + name: orsopy + version: 1.2.3 + sha256: 11d3ee9a1185467ba4363b24d6b98b268956ee0fb528aeff7ff106b49f363180 + requires_dist: + - pyyaml>=5.4.1 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + name: oscrypto + version: 1.3.0 + sha256: 2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085 + requires_dist: + - asn1crypto>=1.5.1 +- conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c + md5: e51f1e4089cad105b6cac64bd8166587 + depends: + - python >=3.9 + - typing_utils + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/overrides?source=hash-mapping + size: 30139 + timestamp: 1734587755455 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-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=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 + sha256: b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591 + requires_dist: + - pytest ; extra == 'dev' + - tox ; extra == 'dev' + - black ; extra == 'lint' +- pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl + name: pandas + version: 3.0.2 + sha256: d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 3.0.2 + sha256: 5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 3.0.2 + sha256: 61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl + name: pandas + version: 3.0.2 + sha256: ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + name: pandas + version: 3.0.2 + sha256: 07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl + name: pandas + version: 3.0.2 + sha256: dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f + md5: 457c2c8c08e54905d6954e79cb5b5db9 + depends: + - python !=3.0,!=3.1,!=3.2,!=3.3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandocfilters?source=hash-mapping + size: 11627 + timestamp: 1631603397334 +- conda: https://conda.anaconda.org/conda-forge/noarch/paramiko-4.0.0-pyhd8ed1ab_0.conda + sha256: ce76d5a1fc6c7ef636cbdbf14ce2d601a1bfa0dd8d286507c1fd02546fccb94b + md5: 1a884d2b1ea21abfb73911dcdb8342e4 + depends: + - bcrypt >=3.2 + - cryptography >=3.3 + - invoke >=2.0 + - pynacl >=1.5 + - python >=3.9 + license: LGPL-2.1-or-later + license_family: LGPL + purls: + - pkg:pypi/paramiko?source=hash-mapping + size: 159896 + timestamp: 1755102147074 +- 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=compressed-mapping + size: 82472 + timestamp: 1777722955579 +- pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + name: partd + version: 1.4.2 + sha256: 978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f + requires_dist: + - locket + - toolz + - numpy>=1.20.0 ; extra == 'complete' + - pandas>=1.3 ; extra == 'complete' + - pyzmq ; extra == 'complete' + - blosc ; extra == 'complete' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.1.1-pyhd8ed1ab_0.conda + sha256: 6eaee417d33f298db79bc7185ab1208604c0e6cf51dade34cd513c6f9db9c6f3 + md5: 11adc78451c998c0fd162584abfa3559 + depends: + - python >=3.10 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/pathspec?source=compressed-mapping + size: 56559 + timestamp: 1777271601895 +- pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + name: periodictable + version: 2.1.0 + sha256: e9155d2bf5ac10050abeff2f99096d4f04312c0c8a6bb432e28744367c5064b3 + requires_dist: + - pyparsing>=3.0.0 + - numpy + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a + md5: d0d408b1f18883a944376da5cf8101ea + depends: + - ptyprocess >=0.5 + - python >=3.9 + license: ISC + purls: + - pkg:pypi/pexpect?source=hash-mapping + size: 53561 + timestamp: 1733302019362 +- pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl + name: pillow + version: 12.2.0 + sha256: 390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl + name: pillow + version: 12.2.0 + sha256: 71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: pillow + version: 12.2.0 + sha256: eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl + name: pillow + version: 12.2.0 + sha256: 6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + name: pillow + version: 12.2.0 + sha256: 56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: pillow + version: 12.2.0 + sha256: e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + name: pip + version: 26.1.1 + sha256: 99cb1c2899893b075ff56e4ed0af55669a955b49ad7fb8d8603ecdaf4ed653fb + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/pixi-kernel-0.7.1-pyhbbac1ac_0.conda + sha256: 506c9330b8dc5ae98f4c32629fa59fa40e6bdd42a681c48d2f9554693dd01156 + md5: d57ef7cb7ad6b5d62cef8b9bdf1d400b + depends: + - ipykernel >=6 + - jupyter_client >=7 + - jupyter_server >=2.4 + - msgspec >=0.18 + - python >=3.10 + - returns >=0.23 + - tomli >=2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pixi-kernel?source=hash-mapping + size: 39509 + timestamp: 1764156429044 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda + sha256: 8f29915c172f1f7f4f7c9391cd5dac3ebf5d13745c8b7c8006032615246345a5 + md5: 89c0b6d1793601a2a3a3f7d2d3d8b937 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/platformdirs?source=compressed-mapping + size: 25862 + timestamp: 1775741140609 +- pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + name: plopp + version: 26.4.2 + sha256: 5cab99bb0905ce08a1d1d7d82f0f64cee7d594269ec1bd01a8a361bd14ab7bff + requires_dist: + - lazy-loader>=0.4 + - matplotlib>=3.8 + - 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' + - ipywidgets>=8.1.0 ; extra == 'test' + - ipykernel>=6.26,<7 ; extra == 'test' + - mpltoolbox>=24.6.0 ; extra == 'test' + - pandas>=2.2.2 ; extra == 'test' + - plotly>=5.15.0 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'test' + - pytest>=8.0 ; extra == 'test' + - pythreejs>=2.4.1 ; extra == 'test' + - 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/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl + name: plotly + version: 6.7.0 + sha256: ac8aca1c25c663a59b5b9140a549264a5badde2e057d79b8c772ae2920e32ff0 + requires_dist: + - narwhals>=1.15.1 + - packaging + - anywidget ; extra == 'dev' + - build ; extra == 'dev' + - colorcet ; extra == 'dev' + - fiona<=1.9.6 ; python_full_version < '3.9' and extra == 'dev' + - geopandas ; extra == 'dev' + - inflect ; extra == 'dev' + - jupyterlab ; extra == 'dev' + - kaleido>=1.1.0 ; extra == 'dev' + - numpy>=1.22 ; extra == 'dev' + - orjson ; extra == 'dev' + - pandas ; extra == 'dev' + - pdfrw ; extra == 'dev' + - pillow ; extra == 'dev' + - plotly-geo ; extra == 'dev' + - polars[timezone] ; extra == 'dev' + - pyarrow ; extra == 'dev' + - pyshp ; extra == 'dev' + - pytest ; extra == 'dev' + - pytz ; extra == 'dev' + - requests ; extra == 'dev' + - ruff==0.11.12 ; extra == 'dev' + - scikit-image ; extra == 'dev' + - scipy ; extra == 'dev' + - shapely ; extra == 'dev' + - statsmodels ; extra == 'dev' + - vaex ; python_full_version < '3.10' and extra == 'dev' + - xarray ; extra == 'dev' + - build ; extra == 'dev-build' + - jupyterlab ; extra == 'dev-build' + - pytest ; extra == 'dev-build' + - requests ; extra == 'dev-build' + - ruff==0.11.12 ; extra == 'dev-build' + - pytest ; extra == 'dev-core' + - requests ; extra == 'dev-core' + - ruff==0.11.12 ; extra == 'dev-core' + - anywidget ; extra == 'dev-optional' + - build ; 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' + - jupyterlab ; extra == 'dev-optional' + - kaleido>=1.1.0 ; extra == 'dev-optional' + - numpy>=1.22 ; 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' + - pytest ; extra == 'dev-optional' + - pytz ; extra == 'dev-optional' + - requests ; extra == 'dev-optional' + - ruff==0.11.12 ; 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' + - numpy>=1.22 ; extra == 'express' + - kaleido>=1.1.0 ; extra == 'kaleido' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + name: pluggy + version: 1.6.0 + sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + requires_dist: + - pre-commit ; extra == 'dev' + - tox ; extra == 'dev' + - pytest ; extra == 'testing' + - pytest-benchmark ; extra == 'testing' + - coverage ; extra == 'testing' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + sha256: 972e0c1c9f0b1763d482e885732baef7f9a0cbe8686f5e2c6bdd88838619a59a + md5: 7fd2e38f4e608f5ebd80f871352c5495 + depends: + - python >=3.10 + - pywin32-on-windows + - paramiko + - importlib_resources + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/plumbum?source=hash-mapping + size: 103911 + timestamp: 1761911487086 +- pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl + name: ply + version: '3.11' + sha256: 096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce +- pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + name: pooch + version: 1.9.0 + sha256: f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b + requires_dist: + - platformdirs>=2.5.0 + - packaging>=20.0 + - requests>=2.19.0 + - tqdm>=4.41.0,<5.0.0 ; extra == 'progress' + - paramiko>=2.7.0 ; extra == 'sftp' + - xxhash>=1.4.3 ; extra == 'xxhash' + - pytest-httpserver ; extra == 'test' + - pytest-localftpserver ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl + name: pre-commit + version: 4.6.0 + sha256: e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b + requires_dist: + - cfgv>=2.0.0 + - identify>=1.0.0 + - nodeenv>=0.11.1 + - pyyaml>=5.1 + - virtualenv>=20.10.0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl + name: prettytable + version: 3.17.0 + sha256: aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287 + requires_dist: + - wcwidth + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-lazy-fixtures ; extra == 'tests' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + sha256: 4d7ec90d4f9c1f3b4a50623fefe4ebba69f651b102b373f7c0e9dbbfa43d495c + md5: a11ab1f31af799dd93c3a39881528884 + depends: + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/prometheus-client?source=compressed-mapping + size: 57113 + timestamp: 1775771465170 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: d17ae9db4dc594267181bd199bf9a551 + depends: + - python >=3.9 + - wcwidth + constrains: + - prompt_toolkit 3.0.51 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/prompt-toolkit?source=hash-mapping + size: 271841 + timestamp: 1744724188108 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + sha256: 936189f0373836c1c77cd2d6e71ba1e583e2d3920bf6d015e96ee2d729b5e543 + md5: 1e61ab85dd7c60e5e73d853ea035dc29 + depends: + - prompt-toolkit >=3.0.51,<3.0.52.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7182 + timestamp: 1744724189376 +- pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: propcache + version: 0.4.1 + sha256: fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + name: propcache + version: 0.4.1 + sha256: cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl + name: propcache + version: 0.4.1 + sha256: 6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: propcache + version: 0.4.1 + sha256: d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl + name: propcache + version: 0.4.1 + sha256: 364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + name: propcache + version: 0.4.1 + sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py311haee01d2_0.conda + sha256: 8d9325af538a8f56013e42bbb91a4dc6935aece34476e20bafacf6007b571e86 + md5: 2ed8f6fe8b51d8e19f7621941f7bb95f + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 231786 + timestamp: 1769678156460 +- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + sha256: f19fd682d874689dfde20bf46d7ec1a28084af34583e0405685981363af47c91 + md5: 25fe6e02c2083497b3239e21b49d8093 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 228663 + timestamp: 1769678153829 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py311he363849_0.conda + sha256: 2b774e8f4ccaac8783d1908f281e4bb20b7036d6708dc913ee7811b070f5b4b5 + md5: 7cff50265141513c960f00ba586780ea + depends: + - python + - python 3.11.* *_cpython + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 245203 + timestamp: 1769678306347 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + sha256: 1d2a6039fb71d61134b1d6816202529f2f6286c83b59bc1491fd288f5c08046e + md5: ba2d89e51a855963c767648f44c03871 + depends: + - python + - __osx >=11.0 + - python 3.13.* *_cp313 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 242596 + timestamp: 1769678288893 +- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py311hf893f09_0.conda + sha256: 32da17824abadd1f5b46faedfa4964c7b1817b11887c2e8bb4e48628da51b93a + md5: fd968cdacc7967efd0ff5ef1805b812c + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 249478 + timestamp: 1769678166841 +- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + sha256: 3ec3373748f83069bef93b540de416e637ee30231b222d5df8f712e93f2f9195 + md5: 761b299a6289c77459defea3563f8fc0 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 246062 + timestamp: 1769678176886 +- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 + md5: 7d9daffbb8d8e0af0f769dbbcd173a54 + depends: + - python >=3.9 + license: ISC + purls: + - pkg:pypi/ptyprocess?source=hash-mapping + size: 19457 + timestamp: 1733302371990 +- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 + md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pure-eval?source=hash-mapping + size: 16668 + timestamp: 1733569518868 +- pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + name: py + version: 1.11.0 + sha256: 607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl + name: py3dmol + version: 2.5.4 + sha256: 32806726b5310524a2b5bfee320737f7feef635cafc945c991062806daa9e43a + requires_dist: + - ipython ; extra == 'ipython' +- pypi: https://files.pythonhosted.org/packages/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.whl + name: pycairo + version: 1.29.0 + sha256: 91114e4b3fbf4287c2b0788f83e1f566ce031bda49cf1c3c3c19c3e986e95c38 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/db/09/a0ab6a246a7ede89e817d749a941df34f27a74bedf15551da51e86ae105e/pycairo-1.29.0-cp311-cp311-win_amd64.whl + name: pycairo + version: 1.29.0 + sha256: 3391532db03f9601c1cee9ebfa15b7d1db183c6020f3e75c1348cee16825934f + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: pycifrw + version: 5.0.1 + sha256: 65a90ee34e46e6be20834493382585277d12daaa8d1a145cb158c163b3d9fc1c + requires_dist: + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl + name: pycifrw + version: 5.0.1 + sha256: e9ad2fdb4fca6398ed5ae50c19908bf6238434893b68d0125bda79a54a03d708 + requires_dist: + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl + name: pycifrw + version: 5.0.1 + sha256: 8632df76b99932408ab432e09b30aa9a6c390df884a5f34f1e1f76201e625156 + requires_dist: + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + name: pycifstar + version: 0.3.0 + sha256: 5892fdf16c83372ee5f32557127d5f36e14b0bbe520883a4e2e70365382f70ed +- pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + name: pycodestyle + version: 2.14.0 + sha256: dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 + md5: 12c566707c80111f9799308d9e265aef + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + size: 110100 + timestamp: 1733195786147 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + sha256: 69700e31165df070e9716315e042196aa92525dae5deb5107785847ab9f4189f + md5: 729843edafc0899b3348bd3f19525b9d + depends: + - typing-inspection >=0.4.2 + - typing_extensions >=4.14.1 + - python >=3.10 + - annotated-types >=0.6.0 + - pydantic-core ==2.46.4 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic?source=compressed-mapping + size: 346511 + timestamp: 1778103405862 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py311h902ca64_0.conda + sha256: ac3faa31154a85f4b1a4618957b4a06623ff2c6a85f8eac20efe9dbac5757d91 + md5: b6cfa054aec29586d383f9e666bd0e9a + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.11.* *_cp311 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1884122 + timestamp: 1778084220486 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + sha256: d6705962afa2655cc22edcd075ce1f7e67c4407c005137d6d63c0dc5eaa76f47 + md5: ef0f9efec6ec28b17e22f787c7672c67 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1893749 + timestamp: 1778084222642 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py311hf7c400d_0.conda + sha256: 194e8872dcda301703c8f1427e480fee6421e3901803ffc863b68eb886372eaf + md5: 80d6864b30e0697b652a9bd580b2d351 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __osx >=11.0 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1730463 + timestamp: 1778084304998 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + sha256: 5bcc20f2c21d8a20d8f2821caf31d8c0ef2fa3bcdaf7a9bdce62e37be819f1d7 + md5: 32bb0d4dbb1be2064c06248a1190aa96 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - python 3.13.* *_cp313 + - __osx >=11.0 + - python_abi 3.13.* *_cp313 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1720475 + timestamp: 1778084300413 +- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py311hf51aa87_0.conda + sha256: 4cf860418a6a1a678bcc23bc29a83f4f0916682fd6ab1ff209b4e5ee3dc5e15f + md5: 85efdb748cbec9210e718c4a8860749c + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1898998 + timestamp: 1778084255268 +- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + sha256: 6466b7e441adb71e29308de2a87c9787d5d0100601ede2d02ed4f85c251699d6 + md5: 9981bc46be6341306a5473b290b2f366 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1888029 + timestamp: 1778084253466 +- pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + name: pydoclint + version: 0.8.3 + sha256: 5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164 + requires_dist: + - click>=8.1.0 + - docstring-parser-fork>=0.0.12 + - tomli>=2.0.1 ; python_full_version < '3.11' + - flake8>=4 ; extra == 'flake8' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + sha256: cf70b2f5ad9ae472b71235e5c8a736c9316df3705746de419b59d442e8348e86 + md5: 16c18772b340887160c79a6acc022db0 + depends: + - python >=3.10 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pygments?source=compressed-mapping + size: 893031 + timestamp: 1774796815820 +- pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + name: pyhanko + version: 0.35.1 + sha256: 57c085f29d7f87334be2a5fbd38a1ca3c987f40f51aea38bde628497d6d9ef2e + requires_dist: + - asn1crypto>=1.5.1 + - tzlocal>=4.3 + - pyhanko-certvalidator>=0.31.1,<0.32 + - requests>=2.31.0 + - pyyaml>=6.0 + - cryptography>=48.0.0 + - lxml>=5.4.0 + - fonttools>=4.33.3 ; extra == 'opentype' + - uharfbuzz>=0.25.0,<0.55.0 ; extra == 'opentype' + - qrcode>=7.3.1 ; extra == 'qr' + - pillow>=7.2.0 ; extra == 'image-support' + - python-barcode==0.16.1 ; extra == 'image-support' + - python-pkcs11~=0.9.3 ; extra == 'pkcs11' + - aiohttp>=3.9,<3.14 ; extra == 'async-http' + - xsdata>=24.4,<27.0 ; extra == 'etsi' + - signxml>=4.2.0 ; extra == 'etsi' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + name: pyhanko-certvalidator + version: 0.31.1 + sha256: 0aabe25b536ed555f3b18e5eb2c62ff46adeff9d15e27f0c9d5d81952d956f45 + requires_dist: + - asn1crypto>=1.5.1 + - oscrypto>=1.1.0 + - cryptography>=48.0.0 + - uritools>=3.0.1 + - requests>=2.31.0 + - aiohttp>=3.9,<3.14 ; extra == 'async-http' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + name: pymdown-extensions + version: 10.21.2 + sha256: 5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638 + requires_dist: + - markdown>=3.6 + - pyyaml + - pygments>=2.19.1 ; extra == 'extra' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py311hdf6b40b_1.conda + sha256: 4b89f502dc58df0abb27d4677ca4b0ef5230f5c28e24b80eef3dc0c9c968e225 + md5: fb7e00fed4fddee97e6cf0bd3ae9a27e + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=1.4.1 + - libgcc >=14 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - six + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1151915 + timestamp: 1772171237221 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda + sha256: 51e80a7bef95025ad47a92acb69ee0e78f01e107655c86fe76abcde2ac688166 + md5: c4426edfc5514a2c9be6871557bce52b + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=1.4.1 + - libgcc >=14 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - six + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1191901 + timestamp: 1772171244923 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py311h38a6bc0_1.conda + sha256: 503b3a2543577f2fb70ddc44113a4da7ecdfdfb2bf51ecd1203adcb93e7257df + md5: 64d4dd58b1eb87d7e6ba084c7dc9259b + depends: + - __osx >=11.0 + - cffi >=1.4.1 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - six + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1188895 + timestamp: 1772171487639 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda + sha256: ef6ab85953ebf81b3b0c6d0b6377eb1a318b07823e76d2c8e850365e60d9ca5c + md5: fca94b9ddea6d5c63ccb6b5ad6d8e232 + depends: + - __osx >=11.0 + - cffi >=1.4.1 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - six + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1192924 + timestamp: 1772171603602 +- conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py311hcd7b28f_1.conda + sha256: cd35ad681895b45f578bac8fd8dafc87116c2db42489dda9bef76ff3518bbc73 + md5: e2197089006b3cda7e03c7a51fa7050e + depends: + - cffi >=1.4.1 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - six + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1147862 + timestamp: 1772171327674 +- conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda + sha256: 0960e727d0bd70e07bca0c049c4c4afa0b41bcea050135db49f54c1c0c02ed7f + md5: 99b7617a643bed6a403fc908c1e167f0 + depends: + - cffi >=1.4.1 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - six + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1186552 + timestamp: 1772171311072 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py311hce6e4fa_0.conda + sha256: f6dced0ea4f220abc1278f6217e22ca1c631f6154dd086b0a7a2866589d6b78c + md5: 812e22c5c7859e719ec225ece0c6f2c1 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-core?source=hash-mapping + size: 481434 + timestamp: 1763151508974 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda + sha256: 307ca29ebf2317bd2561639b1ee0290fd8c03c3450fa302b9f9437d8df6a5280 + md5: 31a0a72f3466682d0ea2ebcbd7d319b8 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-core?source=hash-mapping + size: 481508 + timestamp: 1763152124940 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py311h9049b8e_0.conda + sha256: 67e209a5e9ebaf08aa36e2a4b9139ff91a7ecee7c17408d0a3a1ea1dc3a67681 + md5: b215a767b5ef6f16347cacebc409e66b + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pyobjc-core 12.1.* + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping + size: 383445 + timestamp: 1763160541362 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda + sha256: 194e188d8119befc952d04157079733e2041a7a502d50340ddde632658799fdc + md5: a6d28c8fc266a3d3c3dae183e25c4d31 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pyobjc-core 12.1.* + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping + size: 376136 + timestamp: 1763160678792 +- pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + name: pyparsing + version: 3.3.2 + sha256: 850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d + requires_dist: + - railroad-diagrams ; extra == 'diagrams' + - jinja2 ; extra == 'diagrams' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + name: pypdf + version: 6.10.2 + sha256: aa53be9826655b51c96741e5d7983ca224d898ac0a77896e64636810517624aa + requires_dist: + - typing-extensions>=4.0 ; python_full_version < '3.11' + - cryptography ; extra == 'crypto' + - pycryptodome ; extra == 'cryptodome' + - flit ; extra == 'dev' + - pip-tools ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-socket ; extra == 'dev' + - pytest-timeout ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - wheel ; extra == 'dev' + - myst-parser ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - cryptography ; extra == 'full' + - pillow>=8.0.0 ; extra == 'full' + - pillow>=8.0.0 ; extra == 'image' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca + md5: e2fd202833c4a981ce8a65974fe4abd1 + depends: + - __win + - python >=3.9 + - win_inet_pton + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21784 + timestamp: 1733217448189 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21085 + timestamp: 1733217331982 +- pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + name: pytest + version: 9.0.3 + sha256: 2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 + requires_dist: + - colorama>=0.4 ; sys_platform == 'win32' + - exceptiongroup>=1 ; python_full_version < '3.11' + - iniconfig>=1.0.1 + - packaging>=22 + - pluggy>=1.5,<2 + - pygments>=2.7.2 + - tomli>=1 ; python_full_version < '3.11' + - argcomplete ; extra == 'dev' + - attrs>=19.2 ; extra == 'dev' + - hypothesis>=3.56 ; extra == 'dev' + - mock ; extra == 'dev' + - requests ; extra == 'dev' + - setuptools ; extra == 'dev' + - xmlschema ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + name: pytest-cov + version: 7.1.0 + sha256: a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 + requires_dist: + - coverage[toml]>=7.10.6 + - pluggy>=1.2 + - pytest>=7 + - process-tests ; extra == 'testing' + - pytest-xdist ; extra == 'testing' + - virtualenv ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + name: pytest-xdist + version: 3.8.0 + sha256: 202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88 + requires_dist: + - execnet>=2.1 + - pytest>=7.0.0 + - filelock ; extra == 'testing' + - psutil>=3.0 ; extra == 'psutil' + - setproctitle ; extra == 'setproctitle' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + sha256: bf6a32c69889d38482436a786bea32276756cedf0e9805cc856ffd088e8d00f0 + md5: a5ebcefec0c12a333bcd6d7bf3bddc1f + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 30949404 + timestamp: 1772730362552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + build_number: 100 + sha256: 7f77eb57648f545c1f58e10035d0d9d66b0a0efb7c4b58d3ed89ec7269afdde1 + md5: 05051be49267378d2fcd12931e319ac3 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libuuid >=2.42,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 37358322 + timestamp: 1775614712638 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda + sha256: 9a846065863925b2562126a5c6fecd7a972e84aaa4de9e686ad3715ca506acfa + md5: 49c7d96c58b969585cf09fb01d74e08e + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 14753109 + timestamp: 1772730203101 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + build_number: 100 + sha256: d0fffc5fde21d1ae350da545dfb9e115a8c53bed8a9c5761f9efd4a5581853c1 + md5: 9991a930e81d3873eba7a299ba783ec4 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 12966447 + timestamp: 1775615694085 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + sha256: a1f1031088ce69bc99c82b95980c1f54e16cbd5c21f042e9c1ea25745a8fc813 + md5: d09dbf470b41bca48cbe6a78ba1e009b + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 18416208 + timestamp: 1772728847666 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda + build_number: 100 + sha256: b8108d7f83f71fb15fbb4a263406c2065a8990b3d7eba2cbd7a3075b9a6392ba + md5: 7065f7067762c4c2bda1912f18d16239 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Python-2.0 + purls: [] + size: 16618694 + timestamp: 1775613654892 + python_site_packages_path: Lib/site-packages +- pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: python-bidi + version: 0.6.9 + sha256: 29e40e02bf2bddec225730eb3ba9f4ca948dced9877358458b74d7a447d936ac + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz + name: python-bidi + version: 0.6.9 + sha256: 001c1769893fd859216b0dc39dd4679c7260bf292c3637b727a928402a254322 + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3e/9a/a9383ff12698d5306522122e7007d9fd58f903e00a7715506e3ff5be1678/python_bidi-0.6.9-cp311-cp311-macosx_11_0_arm64.whl + name: python-bidi + version: 0.6.9 + sha256: c3554a3cb9b95e503bc348a8c461b330bf1812c500f51bc9bbd789544fc4a8aa + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: python-bidi + version: 0.6.9 + sha256: 6cf6941062f0589291a396b3d81636b7e35a99105098bc147c48e077c0fb4b45 + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/95/95/685e5a31f6c4cc24826d3a0df9e6a4bcf13d00f90d0c1f1060f13f5c16f8/python_bidi-0.6.9-cp313-cp313-macosx_11_0_arm64.whl + name: python-bidi + version: 0.6.9 + sha256: c3e4445f0fd1e2c16d3efdb28622a38f57ab4b971eb47803a334be477cee173e + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ac/a6/1ca2d290381911c17975ba00353671217bb9ed6c56f395871b274435b020/python_bidi-0.6.9-cp311-cp311-win_amd64.whl + name: python-bidi + version: 0.6.9 + sha256: be28092eef062fad59e7e0402bf2c1db797ccadedd866a53bbd7ddb97e42cac1 + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 + md5: 5b8d21249ff20967101ffa321cab24e8 + depends: + - python >=3.9 + - six >=1.5 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/python-dateutil?source=hash-mapping + size: 233310 + timestamp: 1751104122689 +- pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + name: python-discovery + version: 1.3.0 + sha256: 441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f + requires_dist: + - filelock>=3.15.4 + - platformdirs>=4.3.6,<5 + - furo>=2025.12.19 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.6.3 ; extra == 'docs' + - sphinx>=9.1 ; extra == 'docs' + - sphinxcontrib-mermaid>=2 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'testing' + - coverage>=7.5.4 ; extra == 'testing' + - pytest-mock>=3.14 ; extra == 'testing' + - pytest>=8.3.5 ; extra == 'testing' + - setuptools>=75.1 ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + name: python-engineio + version: 4.13.1 + sha256: f32ad10589859c11053ad7d9bb3c9695cdf862113bfb0d20bc4d890198287399 + requires_dist: + - simple-websocket>=0.10.0 + - requests>=2.21.0 ; extra == 'client' + - websocket-client>=0.54.0 ; extra == 'client' + - aiohttp>=3.11 ; extra == 'asyncio-client' + - tox ; extra == 'dev' + - sphinx ; extra == 'docs' + - furo ; extra == 'docs' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 + md5: 23029aae904a2ba587daba708208012f + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/fastjsonschema?source=hash-mapping + size: 244628 + timestamp: 1755304154927 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda + sha256: b2e51d83e5ebeb7e9fde1cde822a60e8564cc9dabd786ad853056afbf708a466 + md5: fd00e4b24ea88093c93f5c9bad27b52f + depends: + - cpython 3.13.13.* + - python_abi * *_cp313 + license: Python-2.0 + purls: [] + size: 48536 + timestamp: 1775613791711 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + sha256: 1c55116c22512cef7b01d55ae49697707f2c1fd829407183c19817e2d300fd8d + md5: 1cd2f3e885162ee1366312bd1b1677fd + depends: + - python >=3.10 + - typing_extensions + license: BSD-2-Clause + license_family: BSD + purls: + - 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 + sha256: a3eb1702e92aa2f2b5d3ba00261b61f062cce51f1cfb6900bf3ab4d1934d2d35 + requires_dist: + - bidict>=0.21.0 + - python-engineio>=4.11.0 + - requests>=2.21.0 ; extra == 'client' + - websocket-client>=0.54.0 ; extra == 'client' + - aiohttp>=3.4 ; extra == 'asyncio-client' + - tox ; extra == 'dev' + - sphinx ; extra == 'docs' + - furo ; extra == 'docs' + requires_python: '>=3.8' +- 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: 146639 + timestamp: 1777068997932 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + build_number: 8 + sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 + md5: 8fcb6b0e2161850556231336dae58358 + constrains: + - python 3.11.* *_cpython + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7003 + timestamp: 1752805919375 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + build_number: 8 + sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 + md5: 94305520c52a4aa3f6c2b1ff6008d9f8 + constrains: + - python 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 7002 + timestamp: 1752805902938 +- pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + name: pythreejs + version: 2.4.2 + sha256: 8418807163ad91f4df53b58c4e991b26214852a1236f28f1afeaadf99d095818 + requires_dist: + - ipywidgets>=7.2.1 + - ipydatawidgets>=1.1.1 + - numpy + - traitlets + - sphinx>=1.5 ; extra == 'docs' + - nbsphinx>=0.2.13 ; extra == 'docs' + - nbsphinx-link ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - scipy ; extra == 'examples' + - matplotlib ; extra == 'examples' + - scikit-image ; extra == 'examples' + - ipywebrtc ; extra == 'examples' + - nbval ; extra == 'test' + - pytest-check-links ; extra == 'test' + - numpy>=1.14 ; extra == 'test' + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda + sha256: e3ef7e0cc53111ab81b8a9dd3eabc1374d7420d4c9fce3c8631e73310203ad55 + md5: c1cfe9f5d8e278cc4d2d4c7b0126634d + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/pywin32?source=hash-mapping + size: 6729388 + timestamp: 1756487145061 +- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda + sha256: 87eaeb79b5961e0f216aa840bc35d5f0b9b123acffaecc4fda4de48891901f20 + md5: 1ce4f826332dca56c76a5b0cc89fb19e + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/pywin32?source=hash-mapping + size: 6695114 + timestamp: 1756487139550 +- conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 + sha256: 09803b75cccc16d8586d2f41ea890658d165f4afc359973fa1c7904a2c140eae + md5: 91733394059b880d9cc0d010c20abda0 + depends: + - python >=2.7 + - pywin32 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 5282 + timestamp: 1646866839398 +- conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb + md5: 2807a0becd1d986fe1ef9b7f8135f215 + depends: + - __unix + - python >=2.7 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 4856 + timestamp: 1646866525560 +- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_1.conda + sha256: b1f6b3a907e36f7af486faf3892f47fab42993c13c934cc19855bbae227f2b18 + md5: e5dd9afed138ff193d4593f1b15a388b + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - winpty + license: MIT + license_family: MIT + purls: + - pkg:pypi/pywinpty?source=hash-mapping + size: 215911 + timestamp: 1759557817579 +- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda + sha256: d34a7cd0a4a7dc79662cb6005e01d630245d9a942e359eb4d94b2fb464ed2552 + md5: 8f01ed27e2baa455e753301218e054fd + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - winpty + license: MIT + license_family: MIT + purls: + - pkg:pypi/pywinpty?source=hash-mapping + size: 216075 + timestamp: 1759556799508 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda + sha256: c9a6cd2c290d7c3d2b30ea34a0ccda30f770e8ddb2937871f2c404faf60d0050 + md5: a24add9a3bababee946f3bc1c829acfe + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 206190 + timestamp: 1770223702917 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda + sha256: ef7df29b38ef04ec67a8888a4aa039973eaa377e8c4b59a7be0a1c50cd7e4ac6 + md5: f256753e840c3cd3766488c9437a8f8b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=compressed-mapping + size: 201616 + timestamp: 1770223543730 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda + sha256: 984e73d7957460689e10533059de8adb38a308853d298900a37acc58edd84cec + md5: e4b908da7cd496b3fa6798c0f60a2a19 + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 192948 + timestamp: 1770223655988 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_1.conda + sha256: 950725516f67c9691d81bb8dde8419581c5332c5da3da10c9ba8cbb1698b825d + md5: 5d0c8b92128c93027632ca8f8dc1190f + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 188763 + timestamp: 1770224094408 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda + sha256: 301c3ba100d25cd5ae37895988ee3ab986210d4d972aa58efed948fbe857773d + md5: a0153c033dc55203e11d1cac8f6a9cf2 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 187108 + timestamp: 1770223467913 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_1.conda + sha256: dfaed50de8ee72a51096163b87631921688851001e38c78a841eba1ae8b35889 + md5: c1bdb8dd255c79fb9c428ad25cc6ee54 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 180992 + timestamp: 1770223457761 +- pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + name: pyyaml-env-tag + version: '1.1' + sha256: 17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 + requires_dist: + - pyyaml + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h57d2397_2.conda + sha256: 4137226663b353919396f8cf239971cfa54107cd077cf3b65e979ba3e1f8a037 + md5: 759edfe34f07c5c4565b6c5ec0c7fb17 + depends: + - python + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - zeromq >=4.3.5,<4.4.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 383627 + timestamp: 1771716979818 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda + noarch: python + sha256: be66c1f85c3b48137200d62c12d918f4f8ad329423daef04fed292818efd3c28 + md5: 082985717303dab433c976986c674b35 + depends: + - python + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - zeromq >=4.3.5,<4.4.0a0 + - _python_abi3_support 1.* + - cpython >=3.12 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 211567 + timestamp: 1771716961404 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h745ac33_2.conda + sha256: c5d65e1192241d764a68eb0e95ef3ec4800a7e8584260aa2bdcfad0a2d769609 + md5: 9648524ed194922a084f0c2c2c2ada6a + depends: + - python + - __osx >=11.0 + - python 3.11.* *_cpython + - libcxx >=19 + - zeromq >=4.3.5,<4.4.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 359869 + timestamp: 1771717160649 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda + noarch: python + sha256: 2f31f799a46ed75518fae0be75ecc8a1b84360dbfd55096bc2fe8bd9c797e772 + md5: 2f6b79700452ef1e91f45a99ab8ffe5a + depends: + - python + - libcxx >=19 + - __osx >=11.0 + - _python_abi3_support 1.* + - cpython >=3.12 + - zeromq >=4.3.5,<4.4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 191641 + timestamp: 1771717073430 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hff25285_2.conda + sha256: d37e831618e8df0506d32e16820adab550b74b36d039da753f7543bb967b700d + md5: 0ad824e928af5d6200a97649d810ab2b + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + - zeromq >=4.3.5,<4.3.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 363314 + timestamp: 1771716977369 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda + noarch: python + sha256: d84bcc19a945ca03d1fd794be3e9896ab6afc9f691d58d9c2da514abe584d4df + md5: eb1ec67a70b4d479f7dd76e6c8fe7575 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zeromq >=4.3.5,<4.3.6.0a0 + - _python_abi3_support 1.* + - cpython >=3.12 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 183235 + timestamp: 1771716967192 +- conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda + sha256: 0604c6dff3af5f53e34fceb985395d08287137f220450108a795bcd1959caf14 + md5: 34fa231b5c5927684b03bb296bb94ddc + depends: + - prompt_toolkit >=2.0,<4.0 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/questionary?source=hash-mapping + size: 31257 + timestamp: 1757356458097 +- pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + name: radon + version: 6.0.1 + sha256: 632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859 + requires_dist: + - mando>=0.6,<0.8 + - colorama==0.4.1 ; python_full_version < '3.5' + - colorama>=0.4.1 ; python_full_version >= '3.5' + - tomli>=2.0.1 ; extra == 'toml' +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 + md5: d7d95fc8287ea7bf33e0e7116d2b95ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 345073 + timestamp: 1765813471974 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + sha256: a77010528efb4b548ac2a4484eaf7e1c3907f2aec86123ed9c5212ae44502477 + md5: f8381319127120ce51e081dce4865cf4 + depends: + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 313930 + timestamp: 1765813902568 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 + md5: 870293df500ca7e18bedefa5838a22ab + depends: + - attrs >=22.2.0 + - python >=3.10 + - rpds-py >=0.7.0 + - typing_extensions >=4.4.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/referencing?source=hash-mapping + size: 51788 + timestamp: 1760379115194 +- pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + name: refl1d + version: 1.0.1 + sha256: 8cc52121790df465e3a659d0fb0298f2501256c6f2d7fca26085927297112eb1 + requires_dist: + - bumps==1.0.4 + - matplotlib + - numba + - numpy + - periodictable + - scipy + - orsopy + - ipython ; extra == 'dev' + - matplotlib ; extra == 'dev' + - nbsphinx ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydantic ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx<8.2 ; extra == 'dev' + - versioningit ; extra == 'dev' + - wxpython ; extra == 'full' + - ipython ; extra == 'full' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl + name: refnx + version: 0.1.63 + sha256: 3a12accac6e469fd2c0ef49357301299f6b2e4a9ecdfdfb90b592dad59214eb9 + requires_dist: + - numpy>=2.0 + - scipy + - orsopy + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - traitlets ; extra == 'all' + - matplotlib ; extra == 'all' + - xlrd ; extra == 'all' + - h5py ; extra == 'all' + - jupyter ; extra == 'all' + - tqdm ; extra == 'all' + - pymc ; extra == 'all' + - pytensor ; extra == 'all' + - attrs ; extra == 'all' + - pandas ; extra == 'all' + - pyparsing ; extra == 'all' + - periodictable ; extra == 'all' + - pyqt6 ; extra == 'all' + - qtpy ; extra == 'all' + - corner ; extra == 'all' + - numba ; extra == 'all' + - pytest ; extra == 'test' + - uncertainties ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl + name: refnx + version: 0.1.63 + sha256: 9bc88eba956e97892805d2fb18a7268780f943c5d74feb5d1c58f5ae90699fa2 + requires_dist: + - numpy>=2.0 + - scipy + - orsopy + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - traitlets ; extra == 'all' + - matplotlib ; extra == 'all' + - xlrd ; extra == 'all' + - h5py ; extra == 'all' + - jupyter ; extra == 'all' + - tqdm ; extra == 'all' + - pymc ; extra == 'all' + - pytensor ; extra == 'all' + - attrs ; extra == 'all' + - pandas ; extra == 'all' + - pyparsing ; extra == 'all' + - periodictable ; extra == 'all' + - pyqt6 ; extra == 'all' + - qtpy ; extra == 'all' + - corner ; extra == 'all' + - numba ; extra == 'all' + - pytest ; extra == 'test' + - uncertainties ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: refnx + version: 0.1.63 + sha256: 4f397b0d6e18ac1bb793372ec92c9f4a122383387a706dea456df5a2c90dff5e + requires_dist: + - numpy>=2.0 + - scipy + - orsopy + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - traitlets ; extra == 'all' + - matplotlib ; extra == 'all' + - xlrd ; extra == 'all' + - h5py ; extra == 'all' + - jupyter ; extra == 'all' + - tqdm ; extra == 'all' + - pymc ; extra == 'all' + - pytensor ; extra == 'all' + - attrs ; extra == 'all' + - pandas ; extra == 'all' + - pyparsing ; extra == 'all' + - periodictable ; extra == 'all' + - pyqt6 ; extra == 'all' + - qtpy ; extra == 'all' + - corner ; extra == 'all' + - numba ; extra == 'all' + - pytest ; extra == 'test' + - uncertainties ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + name: reportlab + version: 4.5.0 + sha256: b8cc8996947d84e805368b47b2376070966f091d029351a0d8a1f238984c2c7f + requires_dist: + - pillow>=9.0.0 + - charset-normalizer + - rl-accel>=0.9.0,<1.1 ; extra == 'accel' + - rl-renderpm>=4.0.3,<4.1 ; extra == 'renderpm' + - rlpycairo>=0.2.0,<1 ; extra == 'pycairo' + - freetype-py>=2.3.0,<2.4 ; extra == 'pycairo' + - rlbidi ; extra == 'bidi' + - uharfbuzz ; extra == 'shaping' + requires_python: '>=3.9,<4' +- 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 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.26,<3 + - python + constrains: + - chardet >=3.0.2,<8 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=compressed-mapping + 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.11 + - typing_extensions >=4.0,<5.0 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/returns?source=hash-mapping + size: 100559 + timestamp: 1776176903101 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 + md5: 36de09a8d3e5d5e6f4ee63af49e59706 + depends: + - python >=3.9 + - six + license: MIT + license_family: MIT + purls: + - pkg:pypi/rfc3339-validator?source=hash-mapping + size: 10209 + timestamp: 1733600040800 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + sha256: 2a5b495a1de0f60f24d8a74578ebc23b24aa53279b1ad583755f223097c41c37 + md5: 912a71cc01012ee38e6b90ddd561e36f + depends: + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rfc3986-validator?source=hash-mapping + size: 7818 + timestamp: 1598024297745 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + sha256: 70001ac24ee62058557783d9c5a7bbcfd97bd4911ef5440e3f7a576f9e43bc92 + md5: 7234f99325263a5af6d4cd195035e8f2 + depends: + - python >=3.9 + - lark >=1.2.2 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rfc3987-syntax?source=hash-mapping + size: 22913 + timestamp: 1752876729969 +- pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + name: rich + version: 15.0.0 + sha256: 33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb + requires_dist: + - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' + - markdown-it-py>=2.2.0 + - pygments>=2.13.0,<3.0.0 + requires_python: '>=3.9.0' +- pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl + name: rlpycairo + version: 0.4.0 + sha256: 3ce83825d5761c03bc3571c7db12a336ad51417e63189e3512d11b8922576aa9 + requires_dist: + - pycairo>=1.20.0 + - freetype-py>=2.3 + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py311h902ca64_0.conda + sha256: bf5e6197fb08b8c6e421ca0126e966b7c3ae62b84d7b98523356b4fd5ae6f8ae + md5: 3893f7b40738f9fe87510cb4468cdda5 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.11.* *_cp311 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 383153 + timestamp: 1764543197251 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + sha256: 076d26e51c62c8ecfca6eb19e3c1febdd7632df1990a7aa53da5df5e54482b1c + md5: 779e3307a0299518713765b83a36f4b1 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 383230 + timestamp: 1764543223529 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py311h71babbd_0.conda + sha256: 15873755f078583cea046f7ca7fc0d5348d1f29a16a30b73bdb53dd62f2ba379 + md5: 4408829b022e8e0d19365c0c00be00c4 + depends: + - python + - python 3.11.* *_cpython + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 357600 + timestamp: 1764543142990 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda + sha256: db63344f91e8bfe77703c6764aa9eeafb44d165e286053214722814eabda0264 + md5: 190c2d0d4e98ec97df48cdb74caf44d8 + depends: + - python + - __osx >=11.0 + - python 3.13.* *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 358961 + timestamp: 1764543165314 +- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py311hf51aa87_0.conda + sha256: 6edeab1412def450e72f0e96a5d8bb31a2a0b4e56624699c916d3bafd4d9b475 + md5: 43ab63451a9df29f2c499da524665de9 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 241288 + timestamp: 1764543026991 +- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda + sha256: 27bd383787c0df7a0a926b11014fd692d60d557398dcf1d50c55aa2378507114 + md5: 58ae648b12cfa6df3923b5fd219931cb + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 243419 + timestamp: 1764543047271 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + noarch: python + sha256: 98c771464ed93a2733bae460c39cfa9640feb20972d2e651b44e85db296942eb + md5: 3c8f229055ad244fa3a97c6c45b77099 + depends: + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 9266480 + timestamp: 1778119386343 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + noarch: python + sha256: 91992a557456cbe9980e8ffb0f0d1200f65f741adeefc7ecf568d1935690a7bc + md5: 7151a0851207b88c0031f388a7825e9b + depends: + - python + - __osx >=11.0 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 8485007 + timestamp: 1778119519471 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + noarch: python + sha256: 9b47c39449fd0102315bb62bd632707a754d32cf6c6f08d2a9c74b0a476f2282 + md5: 2db0d3419f3e6fc05516da561f81cda5 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 9719930 + timestamp: 1778119435755 +- pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + name: sciline + version: 25.11.1 + sha256: 13c378287b8157e819b9b67d7e973c65bc6bdc545a3602d18204c365b0c336f9 + requires_dist: + - cyclebane>=24.6.0 + - pytest ; extra == 'test' + - pytest-randomly>=3 ; extra == 'test' + - dask ; extra == 'test' + - graphviz ; extra == 'test' + - jsonschema ; extra == 'test' + - numpy ; extra == 'test' + - pandas ; extra == 'test' + - pydantic ; extra == 'test' + - rich ; extra == 'test' + - rich ; extra == 'progress' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipp + version: 26.3.1 + sha256: 993706e7c31f0317be2db5f528f9142ba67b2e52d7af174fcad195f702e1d6c7 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl + name: scipp + version: 26.3.1 + sha256: 03c6dbf8936a2ed62587c5abe8ab5266a5098834a0709321ce799bd1328eb3e6 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl + name: scipp + version: 26.3.1 + sha256: 67d275fc83b062053df9aa7ce3af4d2205109c2bc3ab22467bcd73ceb0a83df2 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl + name: scipp + version: 26.3.1 + sha256: 8dfe8adedb5cba05acaaea15e3b6fe1820ac2f497e87c1e581ba4be9d82c53bb + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipp + version: 26.3.1 + sha256: ab5859a24b3150b588dd2c67e68b0c7f07c9444eae501f3b6326d6b4a34cbf10 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl + name: scipp + version: 26.3.1 + sha256: 37877cf07b4f54f224d5465c265d6a1e591d605d0c23dd350a4b48d95c26ab7b + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl + name: scippneutron + version: 26.4.1 + sha256: 3b9865ebdb7923eb25739b7cda624555d45275341000f099c1dcf371b1dd7e35 + requires_dist: + - python-dateutil>=2.8 + - email-validator>=2 + - h5py>=3.12 + - lazy-loader>=0.4 + - mpltoolbox>=24.6.0 + - numpy>=1.20 + - plopp>=26.4.1 + - pydantic>=2 + - scipp>=24.7.0 + - scippnexus>=23.11.0 + - scipy>=1.7.0 + - scipp[all]>=23.7.0 ; extra == 'all' + - pooch>=1.5 ; extra == 'all' + - hypothesis>=6.100 ; extra == 'test' + - ipympl>0.9.0 ; extra == 'test' + - ipykernel>6.30 ; extra == 'test' + - pace-neutrons>=0.3 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - psutil>=5.0 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pythreejs>=2.4.1 ; extra == 'test' + - sciline>=25.1.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + name: scippnexus + version: 26.1.1 + sha256: 899a0a5e71291b7809d902c17b6c74addf5a805397eabcec557491ff74eead12 + requires_dist: + - scipp>=24.2.0 + - scipy>=1.10.0 + - h5py>=3.12 + - pytest>=7.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipy + version: 1.17.1 + sha256: 43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4 + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + name: scipy + version: 1.17.1 + sha256: 37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448 + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + name: scipy + version: 1.17.1 + sha256: 7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl + name: scipy + version: 1.17.1 + sha256: a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl + name: scipy + version: 1.17.1 + sha256: d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipy + version: 1.17.1 + sha256: 581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464 + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + sha256: 8fc024bf1a7b99fc833b131ceef4bef8c235ad61ecb95a71a6108be2ccda63e8 + md5: b70e2d44e6aa2beb69ba64206a16e4c6 + depends: + - __osx + - pyobjc-framework-cocoa + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/send2trash?source=hash-mapping + size: 22519 + timestamp: 1770937603551 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda + sha256: 305446a0b018f285351300463653d3d3457687270e20eda37417b12ee386ef76 + md5: 6ac53f3fff2c416d63511843a04646fa + depends: + - __win + - pywin32 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/send2trash?source=hash-mapping + size: 22864 + timestamp: 1770937641143 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + sha256: 59656f6b2db07229351dfb3a859c35e57cc8e8bcbc86d4e501bff881a6f771f1 + md5: 28eb91468df04f655a57bcfbb35fc5c5 + depends: + - __linux + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/send2trash?source=hash-mapping + size: 24108 + timestamp: 1770937597662 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 + md5: 8e194e7b992f99a5015edbd4ebd38efd + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=hash-mapping + size: 639697 + timestamp: 1773074868565 +- pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + name: shellingham + version: 1.5.4 + sha256: 7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + name: simple-websocket + version: 1.1.0 + sha256: 4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c + requires_dist: + - wsproto + - tox ; extra == 'dev' + - flake8 ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - sphinx ; extra == 'docs' + requires_python: '>=3.6' +- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/six?source=hash-mapping + size: 18455 + timestamp: 1753199211006 +- pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + name: smmap + version: 5.0.3 + sha256: c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad + md5: 03fe290994c5e4ec17293cfb6bdce520 + depends: + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/sniffio?source=hash-mapping + size: 15698 + timestamp: 1762941572482 +- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + sha256: 23b71ecf089967d2900126920e7f9ff18cdcef82dbff3e2f54ffa360243a17ac + md5: 18de09b20462742fe093ba39185d9bac + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/soupsieve?source=hash-mapping + size: 38187 + timestamp: 1769034509657 +- pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + name: spdx-headers + version: 1.5.1 + sha256: 73bcb1ed087824b55ccaa497d03d8f0f0b0eaf30e5f0f7d5bbd29d2c4fe78fcf + requires_dist: + - chardet>=5.2.0 + - requests>=2.32.3 + - black>=23.0.0 ; extra == 'dev' + - build>=0.10.0 ; extra == 'dev' + - hatch>=1.9.0 ; extra == 'dev' + - isort>=5.12.0 ; extra == 'dev' + - mypy>=1.0.0 ; extra == 'dev' + - pre-commit>=4.3.0 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'dev' + - pytest>=7.0.0 ; extra == 'dev' + - ruff>=0.5.0 ; extra == 'dev' + - twine>=4.0.0 ; extra == 'dev' + - types-requests>=2.31.0.6 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'test' + - pytest-mock>=3.10.0 ; extra == 'test' + - pytest>=7.0.0 ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl + name: spglib + version: 2.6.0 + sha256: ff1632524f6ac0031423474e48d6b69f4932ecb7eb4446a501f59619e2b5cbc9 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl + name: spglib + version: 2.6.0 + sha256: 12db7a0d6ad84c55e61eda67590a438edeb48e57ffd5df868cd931b57fb8c630 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: spglib + version: 2.6.0 + sha256: cc9d856d7cc936dc73b6b303aaf8b2fb4230a8527659373450c6e1139cbb2a5c + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl + name: sqlalchemy + version: 2.0.49 + sha256: 12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148 + requires_dist: + - importlib-metadata ; python_full_version < '3.8' + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' + - typing-extensions>=4.6.0 + - greenlet>=1 ; extra == 'asyncio' + - mypy>=0.910 ; extra == 'mypy' + - pyodbc ; extra == 'mssql' + - pymssql ; extra == 'mssql-pymssql' + - pyodbc ; extra == 'mssql-pyodbc' + - mysqlclient>=1.4.0 ; extra == 'mysql' + - mysql-connector-python ; extra == 'mysql-connector' + - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector' + - cx-oracle>=8 ; extra == 'oracle' + - oracledb>=1.0.1 ; extra == 'oracle-oracledb' + - psycopg2>=2.7 ; extra == 'postgresql' + - pg8000>=1.29.1 ; extra == 'postgresql-pg8000' + - greenlet>=1 ; extra == 'postgresql-asyncpg' + - asyncpg ; extra == 'postgresql-asyncpg' + - psycopg2-binary ; extra == 'postgresql-psycopg2binary' + - psycopg2cffi ; extra == 'postgresql-psycopg2cffi' + - psycopg>=3.0.7 ; extra == 'postgresql-psycopg' + - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary' + - pymysql ; extra == 'pymysql' + - greenlet>=1 ; extra == 'aiomysql' + - aiomysql>=0.2.0 ; extra == 'aiomysql' + - greenlet>=1 ; extra == 'aioodbc' + - aioodbc ; extra == 'aioodbc' + - greenlet>=1 ; extra == 'asyncmy' + - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy' + - greenlet>=1 ; extra == 'aiosqlite' + - aiosqlite ; extra == 'aiosqlite' + - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' + - sqlcipher3-binary ; extra == 'sqlcipher' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl + name: sqlalchemy + version: 2.0.49 + sha256: df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120 + requires_dist: + - importlib-metadata ; python_full_version < '3.8' + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' + - typing-extensions>=4.6.0 + - greenlet>=1 ; extra == 'asyncio' + - mypy>=0.910 ; extra == 'mypy' + - pyodbc ; extra == 'mssql' + - pymssql ; extra == 'mssql-pymssql' + - pyodbc ; extra == 'mssql-pyodbc' + - mysqlclient>=1.4.0 ; extra == 'mysql' + - mysql-connector-python ; extra == 'mysql-connector' + - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector' + - cx-oracle>=8 ; extra == 'oracle' + - oracledb>=1.0.1 ; extra == 'oracle-oracledb' + - psycopg2>=2.7 ; extra == 'postgresql' + - pg8000>=1.29.1 ; extra == 'postgresql-pg8000' + - greenlet>=1 ; extra == 'postgresql-asyncpg' + - asyncpg ; extra == 'postgresql-asyncpg' + - psycopg2-binary ; extra == 'postgresql-psycopg2binary' + - psycopg2cffi ; extra == 'postgresql-psycopg2cffi' + - psycopg>=3.0.7 ; extra == 'postgresql-psycopg' + - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary' + - pymysql ; extra == 'pymysql' + - greenlet>=1 ; extra == 'aiomysql' + - aiomysql>=0.2.0 ; extra == 'aiomysql' + - greenlet>=1 ; extra == 'aioodbc' + - aioodbc ; extra == 'aioodbc' + - greenlet>=1 ; extra == 'asyncmy' + - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy' + - greenlet>=1 ; extra == 'aiosqlite' + - aiosqlite ; extra == 'aiosqlite' + - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' + - sqlcipher3-binary ; extra == 'sqlcipher' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: sqlalchemy + version: 2.0.49 + sha256: 47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3 + requires_dist: + - importlib-metadata ; python_full_version < '3.8' + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' + - typing-extensions>=4.6.0 + - greenlet>=1 ; extra == 'asyncio' + - mypy>=0.910 ; extra == 'mypy' + - pyodbc ; extra == 'mssql' + - pymssql ; extra == 'mssql-pymssql' + - pyodbc ; extra == 'mssql-pyodbc' + - mysqlclient>=1.4.0 ; extra == 'mysql' + - mysql-connector-python ; extra == 'mysql-connector' + - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector' + - cx-oracle>=8 ; extra == 'oracle' + - oracledb>=1.0.1 ; extra == 'oracle-oracledb' + - psycopg2>=2.7 ; extra == 'postgresql' + - pg8000>=1.29.1 ; extra == 'postgresql-pg8000' + - greenlet>=1 ; extra == 'postgresql-asyncpg' + - asyncpg ; extra == 'postgresql-asyncpg' + - psycopg2-binary ; extra == 'postgresql-psycopg2binary' + - psycopg2cffi ; extra == 'postgresql-psycopg2cffi' + - psycopg>=3.0.7 ; extra == 'postgresql-psycopg' + - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary' + - pymysql ; extra == 'pymysql' + - greenlet>=1 ; extra == 'aiomysql' + - aiomysql>=0.2.0 ; extra == 'aiomysql' + - greenlet>=1 ; extra == 'aioodbc' + - aioodbc ; extra == 'aioodbc' + - greenlet>=1 ; extra == 'asyncmy' + - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy' + - greenlet>=1 ; extra == 'aiosqlite' + - aiosqlite ; extra == 'aiosqlite' + - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' + - sqlcipher3-binary ; extra == 'sqlcipher' + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 + md5: b1b505328da7a6b246787df4b5a49fbc + depends: + - asttokens + - executing + - pure_eval + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/stack-data?source=hash-mapping + size: 26988 + timestamp: 1733569565672 +- pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + name: svglib + version: 1.5.1 + sha256: 3ae765d3a9409ee60c0fb4d24c2deb6a80617aa927054f5bcd7fc98f0695e587 + requires_dist: + - reportlab + - lxml + - tinycss2>=0.6.0 + - cssselect2>=0.2.0 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl + name: svglib + version: 1.6.0 + sha256: 9aea8e2e81cbbf9c844460e4c7dc90e0a06aea7983bc201975ccd279d7b2d194 + requires_dist: + - cssselect2>=0.2.0 + - lxml>=6.0.0 + - reportlab>=4.4.3 + - rlpycairo>=0.4.0 + - tinycss2>=0.6.0 + - mypy>=1.18.1 ; extra == 'dev' + - pre-commit>=4.3.0 ; extra == 'dev' + - pytest-cov>=7.0.0 ; extra == 'dev' + - pytest-runner>=6.0.1 ; extra == 'dev' + - pytest>=8.3.5 ; extra == 'dev' + - ruff>=0.13.0 ; extra == 'dev' + - tox>=4.30.2 ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl + name: sympy + version: 1.14.0 + sha256: e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5 + requires_dist: + - mpmath>=1.1.0,<1.4 + - pytest>=7.1.0 ; extra == 'dev' + - hypothesis>=6.70.0 ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl + name: tabulate + version: 0.10.0 + sha256: f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3 + requires_dist: + - wcwidth ; extra == 'widechars' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda + sha256: b375e8df0d5710717c31e7c8e93c025c37fa3504aea325c7a55509f64e5d4340 + md5: e43ca10d61e55d0a8ec5d8c62474ec9e + depends: + - __win + - pywinpty >=1.1.0 + - python >=3.10 + - tornado >=6.1.0 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/terminado?source=hash-mapping + size: 23665 + timestamp: 1766513806974 +- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + sha256: 6b6727a13d1ca6a23de5e6686500d0669081a117736a87c8abf444d60c1e40eb + md5: 17b43cee5cc84969529d5d0b0309b2cb + depends: + - __unix + - ptyprocess + - python >=3.10 + - tornado >=6.1.0 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/terminado?source=hash-mapping + size: 24749 + timestamp: 1766513766867 +- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 + md5: f1acf5fdefa8300de697982bcb1761c9 + depends: + - python >=3.5 + - webencodings >=0.4 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/tinycss2?source=hash-mapping + size: 28285 + timestamp: 1729802975370 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac + md5: cffd3bdd58090148f4cfcd831f4b26ab + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3301196 + timestamp: 1769460227866 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 + md5: a9d86bc62f39b94c4661716624eb21b0 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3127137 + timestamp: 1769460817696 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 + md5: 0481bfd9814bf525bd4b3ee4b51494c4 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: TCL + license_family: BSD + purls: [] + size: 3526350 + timestamp: 1769460339384 +- pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl + name: tof + version: 26.3.0 + sha256: e89783a072b05fdb53d9e76fbf919dc8935e75e118fdaf17ca5cc33727ef002b + requires_dist: + - plopp>=23.10.0 + - pooch>=1.5.0 + - scipp>=25.1.0 + - lazy-loader>=0.3 + - pytest>=8.0 ; extra == 'test' + - scippneutron>=24.12.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + name: tokenize-rt + version: 6.2.0 + sha256: a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44 + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd + md5: b5325cf06a000c5b14970462ff5e4d58 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + size: 21561 + timestamp: 1774492402955 +- pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl + name: toolz + version: 1.1.0 + sha256: 15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8 + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py311h49ec1c0_0.conda + sha256: cd8bc08ca661291cfbb05581b79f7e6a6971a072a54a335abcea5460c1230880 + md5: 73b44a114241e564deb5846e7394bf19 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=compressed-mapping + size: 876817 + timestamp: 1774358035290 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda + sha256: 9e8497e1ecca77d03c6be2d3b5f901dfe0ab99686af4fb94ab418b7d449ac547 + md5: 6c0b0ae017b5bfd9c8d718217efd8f14 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 882996 + timestamp: 1774358035145 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py311hc949640_0.conda + sha256: 572923958ec987f3f68e228dfec243c89ab94b50398731215d36e2e040db8703 + md5: 6f12d9ff025ab1875dff67ad779ca29d + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 875022 + timestamp: 1774358570256 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda + sha256: c5b0ee042d8a0b88a3823226dc95b794c042c498aee330aa9b4d78bfad01d099 + md5: 303333dd882dfeb303cc8bfac178464b + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 883472 + timestamp: 1774358832451 +- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py311h3485c13_0.conda + sha256: 71f58fe09f7729ad82c8eb942e775ddeffcef454483911d19d1041ac5d81eec3 + md5: b004afcc680af88cb877978e71d42667 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 878544 + timestamp: 1774358187588 +- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda + sha256: b97fab804ab457cf4157103289317e3619e801a77410e756bb35c6223418cc6e + md5: 7d53f0d25ad5fd7d6962ce4eb385fb07 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 883689 + timestamp: 1774358224157 +- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + sha256: dfb681579be59c2e790c95f7f49b7529a9b0511d6385ad276e3c8988cbd54d2c + md5: 4bada6a6d908a27262af8ebddf4f7492 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + purls: + - pkg:pypi/traitlets?source=compressed-mapping + size: 115165 + timestamp: 1778074251714 +- pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + name: traittypes + version: 0.2.3 + sha256: 49016082ce740d6556d9bb4672ee2d899cd14f9365f17cbb79d5d96b47096d4e + requires_dist: + - traitlets>=4.2.2 + - numpy ; extra == 'test' + - pandas ; extra == 'test' + - xarray ; extra == 'test' + - pytest ; extra == 'test' +- pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + name: trove-classifiers + version: 2026.4.28.13 + sha256: 8f4b1eb4e16296b57d612965444f87a83861cc989a0451ac97fe4265ddef03b8 +- pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + name: typeguard + version: 4.5.1 + sha256: 44d2bf329d49a244110a090b55f5f91aa82d9a9834ebfd30bcc73651e4a8cc40 + requires_dist: + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - typing-extensions>=4.14.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl + name: typer + version: 0.25.1 + sha256: 75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89 + requires_dist: + - click>=8.2.1 + - shellingham>=1.3.0 + - rich>=13.8.0 + - annotated-doc>=0.0.2 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c + md5: edd329d7d3a4ab45dcf905899a7a6115 + depends: + - typing_extensions ==4.15.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + size: 91383 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a + md5: 53f5409c5cfd6c5a66417d68e3f0a864 + depends: + - python >=3.10 + - typing_extensions >=4.12.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/typing-inspection?source=compressed-mapping + size: 20935 + timestamp: 1777105465795 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + size: 51692 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c + md5: f6d7aa696c67756a650e91e15e88223c + depends: + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/typing-utils?source=hash-mapping + size: 15183 + timestamp: 1733331395943 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 + license: LicenseRef-Public-Domain + purls: [] + size: 119135 + timestamp: 1767016325805 +- pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl + name: tzlocal + version: 5.3.1 + sha256: eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d + requires_dist: + - tzdata ; sys_platform == 'win32' + - pytest>=4.3 ; extra == 'devenv' + - pytest-mock>=3.3 ; extra == 'devenv' + - pytest-cov ; extra == 'devenv' + - check-manifest ; extra == 'devenv' + - zest-releaser ; extra == 'devenv' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + size: 694692 + timestamp: 1756385147981 +- pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + name: uncertainties + version: 3.2.3 + sha256: 313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a + requires_dist: + - numpy ; extra == 'arrays' + - pytest ; extra == 'test' + - pytest-codspeed ; extra == 'test' + - pytest-cov ; extra == 'test' + - scipy ; extra == 'test' + - sphinx ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - python-docs-theme ; extra == 'doc' + - uncertainties[arrays,doc,test] ; extra == 'all' + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 + md5: e7cb0f5745e4c5035a460248334af7eb + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/uri-template?source=hash-mapping + size: 23990 + timestamp: 1733323714454 +- pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + name: uritools + version: 6.1.0 + sha256: f1b699052459711f1dd03721ca79341dc345054e6286f47313f8bce777782415 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a + md5: 9272daa869e03efe68833e3dc7a02130 + depends: + - backports.zstd >=1.0.0 + - brotli-python >=1.2.0 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=hash-mapping + size: 103172 + timestamp: 1767817860341 +- pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + name: validate-pyproject + version: '0.25' + sha256: f9d05e2686beff82f9ea954f582306b036ced3d3feb258c1110f2c2a495b1981 + requires_dist: + - fastjsonschema>=2.16.2,<=3 + - packaging>=24.2 ; extra == 'all' + - trove-classifiers>=2021.10.20 ; extra == 'all' + - tomli>=1.2.1 ; python_full_version < '3.11' and extra == 'all' + - validate-pyproject-schema-store ; extra == 'store' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl + name: varname + version: 1.0.0 + sha256: 1125bfe981c3bbbe56988f5cb85fdcd7cad923b153283c2d464aea8b4c833d51 + requires_dist: + - executing>=2.1 + - typing-extensions>=4.13 ; python_full_version < '3.10' + - asttokens==3.* ; extra == 'all' + - pure-eval==0.* ; extra == 'all' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda + sha256: 9dc40c2610a6e6727d635c62cced5ef30b7b30123f5ef67d6139e23d21744b3a + md5: 1e610f2416b6acdd231c5f573d754a0f + depends: + - vc14_runtime >=14.44.35208 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 19356 + timestamp: 1767320221521 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + sha256: 02732f953292cce179de9b633e74928037fa3741eb5ef91c3f8bae4f761d32a5 + md5: 37eb311485d2d8b2c419449582046a42 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_34 + constrains: + - vs2015_runtime 14.44.35208.* *_34 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 683233 + timestamp: 1767320219644 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + sha256: 878d5d10318b119bd98ed3ed874bd467acbe21996e1d81597a1dbf8030ea0ce6 + md5: 242d9f25d2ae60c76b38a5e42858e51d + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_34 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 115235 + timestamp: 1767320173250 +- pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + name: versioningit + version: 3.3.0 + sha256: 23b1db3c4756cded9bd6b0ddec6643c261e3d0c471707da3e0b230b81ce53e4b + requires_dist: + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - packaging>=17.1 + - tomli>=1.2,<3.0 ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl + name: verspec + version: 0.1.0 + sha256: 741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31 + requires_dist: + - coverage ; extra == 'test' + - flake8>=3.7 ; extra == 'test' + - mypy ; extra == 'test' + - pretend ; extra == 'test' + - pytest ; extra == 'test' +- pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + name: virtualenv + 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.2.2 + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + name: watchdog + version: 6.0.0 + sha256: 20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl + name: watchdog + version: 6.0.0 + sha256: afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + name: watchdog + version: 6.0.0 + sha256: cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + name: watchdog + version: 6.0.0 + sha256: a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b + requires_dist: + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- 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=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 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/webcolors?source=hash-mapping + size: 18987 + timestamp: 1761899393153 +- conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 + md5: 2841eb5bfc75ce15e9a0054b98dcd64d + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/webencodings?source=hash-mapping + size: 15496 + timestamp: 1733236131358 +- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + sha256: 42a2b61e393e61cdf75ced1f5f324a64af25f347d16c60b14117393a98656397 + md5: 2f1ed718fcd829c184a6d4f0f2e07409 + depends: + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/websocket-client?source=hash-mapping + size: 61391 + timestamp: 1759928175142 +- pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + name: widgetsnbextension + version: 4.0.15 + sha256: 8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366 + requires_python: '>=3.7' +- conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f + md5: 46e441ba871f524e2b067929da3051c2 + depends: + - __win + - python >=3.9 + license: LicenseRef-Public-Domain + purls: + - pkg:pypi/win-inet-pton?source=hash-mapping + size: 9555 + timestamp: 1733130678956 +- conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 + sha256: 9df10c5b607dd30e05ba08cbd940009305c75db242476f4e845ea06008b0a283 + md5: 1cee351bf20b830d991dbe0bc8cd7dfe + license: MIT + license_family: MIT + purls: [] + size: 1176306 +- pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + name: wsproto + version: 1.3.2 + sha256: 61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584 + requires_dist: + - h11>=0.16.0,<1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + name: xhtml2pdf + version: 0.2.17 + sha256: 61a7ecac829fed518f7dbcb916e9d56bea6e521e02e54644b3d0ca33f0658315 + requires_dist: + - arabic-reshaper>=3.0.0 + - html5lib>=1.1 + - pillow>=8.1.1 + - pyhanko>=0.12.1 + - pyhanko-certvalidator>=0.19.5 + - pypdf>=3.1.0 + - python-bidi>=0.5.0 + - reportlab>=4.0.4,<5 + - svglib>=1.2.1 + - reportlab[pycairo]>=4.0.4,<5 ; extra == 'pycairo' + - reportlab[renderpm]>=4.0.4,<5 ; extra == 'renderpm' + - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'test' + - tox ; extra == 'test' + - coverage>=5.3 ; extra == 'test' + - sphinx>=6 ; extra == 'docs' + - sphinx-rtd-theme>=0.5.0 ; extra == 'docs' + - sphinx-reredirects>=0.1.3 ; extra == 'docs' + - build ; extra == 'release' + - twine ; extra == 'release' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl + name: xraydb + version: 4.5.8 + sha256: 2215baafa6a03d00d0254a94525aafc6493c8c285e4ac4477fbd6271b25e6a51 + requires_dist: + - numpy>=1.19 + - scipy>=1.6 + - sqlalchemy>=2.0.1 + - platformdirs + - build ; extra == 'dev' + - twine ; extra == 'dev' + - sphinx ; extra == 'doc' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - coverage ; extra == 'test' + - xraydb[dev,doc,test] ; extra == 'all' + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad + md5: a77f85f77be52ff59391544bfe73390a + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + size: 85189 + timestamp: 1753484064210 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + sha256: b03433b13d89f5567e828ea9f1a7d5c5d697bf374c28a4168d71e9464f5dafac + md5: 78a0fe9e9c50d2c381e8ee47e3ea437d + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 83386 + timestamp: 1753484079473 +- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + sha256: 80ee68c1e7683a35295232ea79bcc87279d31ffeda04a1665efdb43cbd50a309 + md5: 433699cba6602098ae8957a323da2664 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + size: 63944 + timestamp: 1753484092156 +- pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: yarl + version: 1.23.0 + sha256: 34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4 + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + name: yarl + version: 1.23.0 + sha256: baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3 + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl + name: yarl + version: 1.23.0 + sha256: bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46 + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: yarl + version: 1.23.0 + sha256: 99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598 + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + name: yarl + version: 1.23.0 + sha256: 7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl + name: yarl + version: 1.23.0 + sha256: dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432 + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h41580af_10.conda + sha256: 325d370b28e2b9cc1f765c5b4cdb394c91a5d958fbd15da1a14607a28fee09f6 + md5: 755b096086851e1193f3b10347415d7c + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - krb5 >=1.22.2,<1.23.0a0 + - libsodium >=1.0.21,<1.0.22.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + size: 311150 + timestamp: 1772476812121 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda + sha256: 2705360c72d4db8de34291493379ffd13b09fd594d0af20c9eefa8a3f060d868 + md5: e85dcd3bde2b10081cdcaeae15797506 + depends: + - __osx >=11.0 + - libcxx >=19 + - krb5 >=1.22.2,<1.23.0a0 + - libsodium >=1.0.21,<1.0.22.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + size: 245246 + timestamp: 1772476886668 +- conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda + sha256: b8568dfde46edf3455458912ea6ffb760e4456db8230a0cf34ecbc557d3c275f + md5: 1ab0237036bfb14e923d6107473b0021 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libsodium >=1.0.21,<1.0.22.0a0 + - krb5 >=1.22.2,<1.23.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + size: 265665 + timestamp: 1772476832995 +- 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=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 + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 601375 + timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + sha256: 9485ba49e8f47d2b597dd399e88f4802e100851b27c21d7525625b0b4025a5d9 + md5: ab136e4c34e97f34fb621d2592a393d8 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 433413 + timestamp: 1764777166076 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + sha256: 368d8628424966fd8f9c8018326a9c779e06913dd39e646cf331226acc90e5b2 + md5: 053b84beec00b71ea8ff7a4f84b55207 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 388453 + timestamp: 1764777142545 diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 00000000..b0fb2b8d --- /dev/null +++ b/pixi.toml @@ -0,0 +1,278 @@ +########### +# WORKSPACE +########### + +[workspace] + +# Supported platforms for the lock file (pixi.lock) +platforms = ['win-64', 'linux-64', 'osx-arm64'] + +# Channels for fetching packages +channels = ['nodefaults', 'conda-forge'] + +########## +# FEATURES +########## + +# Default features: + +[activation.env] +PYTHONIOENCODING = 'utf-8' + +[system-requirements] +# Set minimum supported version for macOS to be 14.0 to ensure packages +# like `scipp` that only have wheels for macOS 14.0+ (macosx_14_0_arm64) +# are used instead of building from source. This is a workaround for +# Pixi, see https://github.com/prefix-dev/pixi/issues/5667 +macos = '14.0' + +# Non-default features: + +# Set specific Python versions to be used in CI testing. + +[feature.py-min.dependencies] +python = '3.11.*' + +[feature.py-max.dependencies] +python = '3.13.*' + +# Development dependencies for local development and testing with +# editable installations. + +[feature.dev.dependencies] +nodejs = '*' # Required for Prettier (non-Python formatting) +jupyterlab = '*' # Jupyter notebooks +ipython = '*' # Interactive Python shell +pixi-kernel = '*' # Pixi Jupyter kernel + +[feature.dev.pypi-dependencies] +pip = '*' +easyreflectometry = { path = '.', editable = true, extras = ['dev'] } + +# User-like behavior for testing with pip-installed dependencies instead +# of editable installations. + +[feature.user.dependencies] +jupyterlab = '*' # Jupyter notebooks +ipython = '*' # Interactive Python shell +pixi-kernel = '*' # Pixi Jupyter kernel + +[feature.user.pypi-dependencies] +pip = '*' +easydiffraction = '*' + +############## +# ENVIRONMENTS +############## + +[environments] + +# The `default` feature is always included in all environments. +# Additional features can be specified per environment. + +# Specific environments for CI testing with different Python versions. +py-311-env = { features = ['py-min', 'dev'] } +py-313-env = { features = ['py-max', 'dev'] } + +# The `default` environment is developer-oriented for local development +# and testing with editable installation of the current package. +default = { features = ['py-max', 'dev'] } + +# The `user` environment allows testing with pip-installed dependencies +# instead of editable installation of the current package. +user = { features = ['py-max', 'user'] } + +####### +# TASKS +####### + +[tasks] + +################## +# 🧪 Testing Tasks +################## + +unit-tests = 'python -m pytest tests/unit/ --color=yes -v' +functional-tests = 'python -m pytest tests/functional/ --color=yes -v' +integration-tests = 'python -m pytest tests/integration/ --color=yes -n auto -v' +notebook-tests = 'python -m pytest --nbmake docs/docs/tutorials/**/ --nbmake-timeout=1200 --color=yes -n auto -v' + +test = { depends-on = ['unit-tests'] } + +########### +# ✔️ Checks +########### + +pyproject-check = 'python -m validate_pyproject pyproject.toml' +docstring-lint-check = 'pydoclint --quiet src/' +notebook-lint-check = 'nbqa ruff docs/docs/tutorials/' +py-lint-check = 'ruff check src/ tests/ docs/docs/tutorials/' +py-format-check = 'ruff format --check src/ tests/ docs/docs/tutorials/' +nonpy-format-check = 'npx prettier --list-different --config=prettierrc.toml --ignore-unknown .' +nonpy-format-check-modified = 'python tools/nonpy_prettier_modified.py' + +check = 'pre-commit run --hook-stage manual --all-files' + +########## +# 🛠️ Fixes +########## + +docstring-transform = 'pixi run docstripy src/ -s=numpy -w' +docstring-format-fix = 'format-docstring src/' +notebook-lint-fix = 'nbqa ruff --fix docs/docs/tutorials/' +py-lint-fix = 'ruff check --fix src/ tests/ docs/docs/tutorials/' +py-lint-fix-unsafe = 'ruff check --fix --unsafe-fixes src/ tests/ docs/docs/tutorials/' +py-format-fix = 'ruff format src/ tests/ docs/docs/tutorials/' +nonpy-format-fix = 'npx prettier --write --list-different --config=prettierrc.toml --ignore-unknown .' +nonpy-format-fix-modified = 'python tools/nonpy_prettier_modified.py --write' +success-message = 'echo "✅ All auto-formatting steps completed successfully!"' + +fix = { depends-on = [ + 'docstring-format-fix', + 'py-format-fix', + 'py-lint-fix', + 'nonpy-format-fix', + 'notebook-lint-fix', + 'success-message', +] } + +#################### +# 🧮 Code Complexity +#################### + +complexity-check = 'radon cc -s src/' +complexity-check-json = 'radon cc -s -j src/' +maintainability-check = 'radon mi src/' +maintainability-check-json = 'radon mi -j src/' +raw-metrics = 'radon raw -s src/' +raw-metrics-json = 'radon raw -s -j src/' + +############# +# 📊 Coverage +############# + +unit-tests-coverage = 'pixi run unit-tests --cov=src/easyreflectometry --cov-report=term-missing' +functional-tests-coverage = 'pixi run functional-tests --cov=src/easyreflectometry --cov-report=term-missing' +integration-tests-coverage = 'pixi run integration-tests --cov=src/easyreflectometry --cov-report=term-missing' +docstring-coverage = 'interrogate -c pyproject.toml src/easyreflectometry' + +cov = { depends-on = [ + 'docstring-coverage', + 'unit-tests-coverage', + 'integration-tests-coverage', +] } + +######################## +# 📓 Notebook Management +######################## + +notebook-convert = 'jupytext docs/docs/tutorials/*.py --from py:percent --to ipynb' +notebook-strip = 'nbstripout docs/docs/tutorials/**/*.ipynb' +notebook-tweak = 'python tools/tweak_notebooks.py docs/docs/tutorials/**/*.ipynb' +notebook-exec = 'python -m pytest --nbmake docs/docs/tutorials/ --nbmake-timeout=1200 --overwrite --color=yes -n auto -v' + +notebook-prepare = { depends-on = [ + #'notebook-convert', + 'notebook-strip', + #'notebook-tweak', +] } + +######################## +# 📚 Documentation Tasks +######################## + +docs-vars = "JUPYTER_PLATFORM_DIRS=1 PYTHONWARNINGS='ignore::RuntimeWarning'" +docs-pre = 'pixi run docs-vars python -m mkdocs' +docs-serve = 'pixi run docs-pre serve -f docs/mkdocs.yml' +docs-serve-dirty = 'pixi run docs-serve --dirty' +docs-build = 'pixi run docs-pre build -f docs/mkdocs.yml' +docs-build-local = 'pixi run docs-build --no-directory-urls' + +docs-deploy-pre = 'mike deploy -F docs/mkdocs.yml --push --branch gh-pages --update-aliases --alias-type redirect' +docs-set-default-pre = 'mike set-default -F docs/mkdocs.yml --push --branch gh-pages' + +docs-update-assets = 'python tools/update_docs_assets.py' + +############################## +# 📦 Template Management Tasks +############################## + +copier-copy = 'copier copy gh:easyscience/templates . --data-file .copier-answers.yml --data template_type=lib' +copier-recopy = 'copier recopy --data-file .copier-answers.yml --data template_type=lib' +copier-update = 'copier update --data-file .copier-answers.yml --data template_type=lib' + +##################### +# 🪝 Pre-commit Hooks +##################### + +pre-commit-clean = 'pre-commit clean' +pre-commit-install = 'pre-commit install --hook-type pre-commit --hook-type pre-push --overwrite' +pre-commit-uninstall = 'pre-commit uninstall --hook-type pre-commit --hook-type pre-push' +pre-commit-setup = { depends-on = [ + 'pre-commit-clean', + 'pre-commit-uninstall', + 'pre-commit-install', +] } + +################# +# 🐙️ GitHub Tasks +################# + +repo-wiki = 'gh api -X PATCH repos/easyscience/reflectometry-lib -f has_wiki=false' +repo-discussions = 'gh api -X PATCH repos/easyscience/reflectometry-lib -f has_discussions=true' +repo-description = "gh api -X PATCH repos/easyscience/reflectometry-lib -f description='Reflectometry data analysis'" +repo-homepage = "gh api -X PATCH repos/easyscience/reflectometry-lib -f homepage='https://easyscience.github.io/reflectometry-lib'" +repo-config = { depends-on = [ + 'repo-wiki', + 'repo-discussions', + 'repo-description', + 'repo-homepage', +] } + +master-protection = 'gh api -X POST repos/easyscience/reflectometry-lib/rulesets --input .github/configs/rulesets-master.json' +develop-protection = 'gh api -X POST repos/easyscience/reflectometry-lib/rulesets --input .github/configs/rulesets-develop.json' +gh-pages-protection = 'gh api -X POST repos/easyscience/reflectometry-lib/rulesets --input .github/configs/rulesets-gh-pages.json' +branch-protection = { depends-on = [ + 'master-protection', + 'develop-protection', + 'gh-pages-protection', +] } + +pages-deployment = 'gh api -X POST repos/easyscience/reflectometry-lib/pages --input .github/configs/pages-deployment.json' + +github-labels = 'python tools/update_github_labels.py' + +######################### +# ⚖️ SPDX License Headers +######################### + +license-remove = 'python tools/license_headers.py remove src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' +license-add = 'python tools/license_headers.py add src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' +license-check = 'python tools/license_headers.py check src/ tests/ --exclude-from-pyproject-toml tool.ruff.exclude' + +#################################### +# 🚀 Other Development & Build Tasks +#################################### + +default-build = 'python -m build' +dist-build = 'python -m build --wheel --outdir dist' + +npm-config = 'npm config set registry https://registry.npmjs.org/' +prettier-install = 'npm install --no-save --no-audit --no-fund prettier prettier-plugin-toml' + +clean-pycache = "find . -type d -name '__pycache__' -prune -exec rm -rf '{}' +" + +post-install = { depends-on = [ + 'npm-config', + 'prettier-install', + #'pre-commit-setup', +] } + +########################## +# 🔗 Main Package Shortcut +########################## +easyreflectometry = 'python -m easyreflectometry' + +[dependencies] +ruff = ">=0.15.12,<0.16" +copier = ">=9.15.0,<10" diff --git a/prettierrc.toml b/prettierrc.toml new file mode 100644 index 00000000..b98c86eb --- /dev/null +++ b/prettierrc.toml @@ -0,0 +1,22 @@ +plugins = [ + "prettier-plugin-toml", # use the TOML plugin +] + +endOfLine = 'lf' # change line endings to LF +proseWrap = 'always' # change wrapping in Markdown files +semi = false # remove semicolons +singleQuote = true # use single quotes instead of double quotes +tabWidth = 2 # change tab width to 2 spaces +useTabs = false # use spaces instead of tabs + +printWidth = 79 # wrap lines at 79 characters + +[[overrides]] +files = ["*.md"] +[overrides.options] +printWidth = 72 # wrap Markdown files at 72 characters + +[[overrides]] +files = ["*.yml", "*.yaml"] +[overrides.options] +printWidth = 88 # wrap YAML files at 88 characters diff --git a/pyproject.toml b/pyproject.toml index ec3df724..681c5562 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,164 +1,345 @@ -[build-system] -requires = [ - "hatchling<=1.21.0", - "setuptools-git-versioning", -] -build-backend = "hatchling.build" - -[tool.setuptools-git-versioning] -enabled = true +############################### +# Configuration for the project +############################### [project] -name = "easyreflectometry" -dynamic = ["version"] -description = "A reflectometry python package built on the EasyScience framework." -readme = "README.md" -authors = [{name = "EasyScience contributors"}] -license = { file = "LICENSE" } +name = 'easyreflectometry' +dynamic = ['version'] # Use versioningit to manage the version +description = 'Reflectometry data analysis' +authors = [{ name = 'EasyScience contributors' }] +readme = 'README.md' +license = 'BSD-3-Clause' +license-files = ['LICENSE'] classifiers = [ - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", - "Topic :: Scientific/Engineering", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Development Status :: 3 - Alpha" + 'Intended Audience :: Science/Research', + 'Topic :: Scientific/Engineering', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ] - -requires-python = ">=3.11,<3.14" - +requires-python = '>=3.11' dependencies = [ - "easyscience", - "scipp", - "refnx", - "refl1d>=1.0.0", - "orsopy", - "svglib<1.6 ; platform_system=='Linux' or sys_platform == 'darwin'", - "xhtml2pdf", - "bumps", + 'easyscience', + 'scipp', + 'refnx', + 'refl1d>=1.0.0', + 'orsopy', + 'svglib<1.6 ; platform_system=="Linux" or sys_platform == "darwin"', + 'xhtml2pdf', + 'bumps', + 'pooch', ] [project.optional-dependencies] dev = [ - "build", - "codecov", - "coverage", - "coveralls", - "flake8", - "ipykernel", - "jupyter", - "jupyterlab", - "plopp", - "pooch", - "pytest", - "pytest-cov", - "ruff", - "toml", - "yapf", -] - -docs = [ - "myst_parser", - "nbsphinx", - "plopp", - "sphinx<=8.1.3", - "sphinx_autodoc_typehints", - "sphinx_book_theme", - "sphinx-copybutton", - "toml" + 'GitPython', # Interact with Git repositories + 'build', # Building the package + 'pre-commit', # Pre-commit hooks + 'jinja2', # Templating + 'nbmake', # Building notebooks + 'nbstripout', # Strip output from notebooks + 'nbqa', # Linting and formatting notebooks + 'pytest', # Testing + 'pytest-cov', # Test coverage + 'pytest-xdist', # Enable parallel testing + 'ruff', # Linting and formatting code + 'radon', # Code complexity and maintainability + 'validate-pyproject[all]', # Validate pyproject.toml + 'versioningit', # Automatic versioning from git tags + 'jupytext', # Jupyter notebook text format support + 'jupyterquiz', # Quizzes in Jupyter notebooks + 'plopp', # Plotting in Jupyter notebooks + 'pydoclint', # Docstring linter + 'format-docstring', # Docstring formatter + 'docstripy', # Convert docstrings to other formats + 'interrogate', # Docstring coverage checker + 'copier', # Template management + 'mike', # MkDocs: Versioned documentation support + 'mkdocs', # Static site generator + 'mkdocs-material', # Documentation framework on top of MkDocs + 'mkdocs-autorefs', # MkDocs: Auto-references support + 'mkdocs-jupyter', # MkDocs: Jupyter notebook support + 'mkdocs-plugin-inline-svg', # MkDocs: Inline SVG support + 'mkdocs-markdownextradata-plugin', # MkDocs: Markdown extra data support, such as global variables + 'mkdocstrings-python', # MkDocs: Python docstring support + 'pyyaml', # YAML parser + 'spdx-headers', # SPDX license header validation ] [project.urls] -homepage = "https://docs.easyreflectometry.org" -documentation = "https://docs.easyreflectometry.org" +Documentation = 'https://easyscience.github.io/reflectometry-lib' +'Release Notes' = 'https://github.com/easyscience/reflectometry-lib/releases' +'Source Code' = 'https://github.com/easyscience/reflectometry-lib' +'Issue Tracker' = 'https://github.com/easyscience/reflectometry-lib/issues' -[tool.hatch.version] -path = "src/easyreflectometry/__version__.py" +############################ +# Build system configuration +############################ + +[build-system] +build-backend = 'hatchling.build' +requires = ['hatchling', 'versioningit'] + +############################# +# Configuration for hatchling +############################# + +# 'hatch' -- Build system for Python +# https://hatch.pypa.io/ + +[tool.hatch.build.targets.wheel] +packages = ['src/easyreflectometry'] [tool.hatch.metadata] allow-direct-references = true -[tool.hatch.build.targets.sdist] -packages = ["src"] +[tool.hatch.version] +source = 'versioningit' # Use versioningit to manage the version -[tool.hatch.build.targets.wheel] -packages = ["src/easyreflectometry"] +################################ +# Configuration for versioningit +################################ + +# 'versioningit' -- Versioning from git tags +# https://versioningit.readthedocs.io/ + +# Versioningit generates versions from git tags, so we don't need to +# either specify them statically in pyproject.toml or save them in the +# source code. Do not use {distance} in the version format, as it +# forces a version bump for every commit, which triggers unnecessary +# pixi.lock update without any changes to the source code. + +[tool.versioningit.format] +distance = '{base_version}+dev{distance}' # example: 1.2.3.post4+dev3 +dirty = '{base_version}+dirty{distance}' # example: 0.5.8+dirty3 +distance-dirty = '{base_version}+devdirty{distance}' # example: 0.5.8+devdirty3 + +# Configure how versioningit detects versions from Git +# - 'match' ensures it only considers tags starting with 'v' +# - 'default-tag' is used as a fallback when no matching tag is found +[tool.versioningit.vcs] +method = 'git' +match = ['v*'] +default-tag = 'v999.0.0' + +################################ +# Configuration for interrogate +################################ + +# 'interrogate' -- Docstring coverage checker +# https://interrogate.readthedocs.io/en/latest/ + +[tool.interrogate] +fail-under = 0 # Minimum docstring coverage percentage to pass +verbose = 1 +#exclude = ['src/**/__init__.py'] + +####################################### +# Configuration for coverage/pytest-cov +####################################### + +# 'coverage' -- Code coverage measurement tool +# https://coverage.readthedocs.io/en/latest/ [tool.coverage.run] -source = ["src/easyreflectometry"] +branch = true # Measure branch coverage as well +source = ['src'] # Limit coverage to the source code directory + +[tool.coverage.report] +show_missing = true # Show missing lines +skip_covered = false # Skip files with 100% coverage in the report +fail_under = 0 # Minimum coverage percentage to pass + +########################## +# Configuration for pytest +########################## -[tool.github.info] -organization = 'easyScience' -repo = "easyreflectometry" +# 'pytest' -- Testing framework +# https://docs.pytest.org/en/stable/ + +[tool.pytest.ini_options] +addopts = '--import-mode=importlib' +markers = ['fast: mark test as fast (should be run on every push)'] +testpaths = ['tests'] + +######################## +# Configuration for ruff +######################## + +# 'ruff' -- Python linter and code formatter +# https://docs.astral.sh/ruff/rules/ [tool.ruff] -line-length = 127 -exclude = [ - "docs", -] +exclude = ['tmp'] +indent-width = 4 +# line-length = 99 # See also `max-line-length` in [tool.ruff.lint.pycodestyle] +line-length = 128 # See also `max-line-length` in [tool.ruff.lint.pycodestyle] +preview = true # Enable new rules that are not yet stable, like DOC + +# Formatting options for Ruff [tool.ruff.format] -quote-style = "single" +docstring-code-format = true # Whether to format code snippets in docstrings +docstring-code-line-length = 99 # Line length for code snippets in docstrings +# docstring-code-line-length = 72 # Line length for code snippets in docstrings +indent-style = 'space' # PEP 8 recommends using spaces over tabs +quote-style = 'single' # But double quotes in docstrings (PEP 8, PEP 257) -[tool.ruff.lint.per-file-ignores] -# allow asserts in test files -"*test_*.py" = ["S101"] +# Linting rules to use with Ruff [tool.ruff.lint] select = [ - # flake8 settings from existing CI setup - "E9", "F63", "F7", "F82", - # Code should be polished to fulfill all cases below - # https://docs.astral.sh/ruff/rules/ - # pycodestyle - "E", - # Pyflakes - "F", - # pyupgrade -# "UP", - # flake8-bugbear -# "B", - # flake8-simplify -# "SIM", - # isort - "I", - # flake8-bandit - "S", + # Various rules + #'C90', # https://docs.astral.sh/ruff/rules/#mccabe-c90 + #'D', # https://docs.astral.sh/ruff/rules/#pydocstyle-d + 'F', # https://docs.astral.sh/ruff/rules/#pyflakes-f + #'FLY', # https://docs.astral.sh/ruff/rules/#flynt-fly + #'FURB', # https://docs.astral.sh/ruff/rules/#refurb-furb + 'I', # https://docs.astral.sh/ruff/rules/#isort-i + #'N', # https://docs.astral.sh/ruff/rules/#pep8-naming-n + #'NPY', # https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy + #'PGH', # https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh + #'PERF', # https://docs.astral.sh/ruff/rules/#perflint-perf + #'RUF', # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + #'TRY', # https://docs.astral.sh/ruff/rules/#tryceratops-try + #'UP', # https://docs.astral.sh/ruff/rules/#pyupgrade-up + # pycodestyle (E, W) rules + 'E', # https://docs.astral.sh/ruff/rules/#error-e + 'W', # https://docs.astral.sh/ruff/rules/#warning-w + # Pylint (PL) rules + #'PLC', # https://docs.astral.sh/ruff/rules/#convention-plc + #'PLE', # https://docs.astral.sh/ruff/rules/#error-ple + #'PLR', # https://docs.astral.sh/ruff/rules/#refactor-plr + #'PLW', # https://docs.astral.sh/ruff/rules/#warning-plw + # flake8 rules + #'A', # https://docs.astral.sh/ruff/rules/#flake8-builtins-a + #'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + #'ARG', # https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg + #'ASYNC', # https://docs.astral.sh/ruff/rules/#flake8-async-async + #'B', # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + #'BLE', # https://docs.astral.sh/ruff/rules/#flake8-blind-except-ble + #'C4', # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + #'COM', # https://docs.astral.sh/ruff/rules/#flake8-commas-com + #'DTZ', # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + #'EM', # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + #'FA', # https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa + #'FBT', # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt + #'FIX', # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + #'G', # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g + #'ICN', # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn + #'INP', # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp + #'ISC', # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + #'LOG', # https://docs.astral.sh/ruff/rules/#flake8-logging-log + #'PIE', # https://docs.astral.sh/ruff/rules/#flake8-pie-pie + #'PT', # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt + #'PTH', # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + #'PYI', # https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + #'RET', # https://docs.astral.sh/ruff/rules/#flake8-return-ret + #'RSE', # https://docs.astral.sh/ruff/rules/#flake8-raise-rse + 'S', # https://docs.astral.sh/ruff/rules/#flake8-bandit-s + #'SIM', # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + #'SLF', # https://docs.astral.sh/ruff/rules/#flake8-self-slf + #'SLOT', # https://docs.astral.sh/ruff/rules/#flake8-slots-slot + #'T20', # https://docs.astral.sh/ruff/rules/#flake8-print-t20 + #'TC', # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc + #'TD', # https://docs.astral.sh/ruff/rules/#flake8-todos-td + #'TID', # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid +] + +# Exceptions to the linting rules + +# Ignore specific rules globally +ignore = [ + 'COM812', # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ + # The following is replaced by 'D'/[tool.ruff.lint.pydocstyle] and [tool.pydoclint] 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc + # Disable, as [tool.format_docstring] split one-line docstrings into the canonical multi-line layout + 'D200', # https://docs.astral.sh/ruff/rules/unnecessary-multiline-docstring/ +] + +# Ignore specific rules in certain files or directories +[tool.ruff.lint.per-file-ignores] +'*/__init__.py' = [ + 'F401', # re-exports are intentional in __init__.py +] +'tests/**' = [ + 'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + 'D', # https://docs.astral.sh/ruff/rules/#pydocstyle-d + 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc + 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ + 'S101', # https://docs.astral.sh/ruff/rules/assert/ ] +'docs/**' = [ + 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ + 'T201', # https://docs.astral.sh/ruff/rules/print/ +] + +# Specific options for certain rules + +[tool.ruff.lint.flake8-tidy-imports] +# Disallow all relative imports +ban-relative-imports = 'all' [tool.ruff.lint.isort] +# Forces all from imports to appear on their own line force-single-line = true -[tool.tox] -legacy_tox_ini = """ -[tox] -isolated_build = True -envlist = py{3.11,3.12,3.13} -[gh-actions] -python = - 3.11: py311 - 3.12: py312 - 3.13: py313 -[gh-actions:env] -PLATFORM = - ubuntu-latest: linux - macos-latest: macos - windows-latest: 2022 -[testenv] -passenv = - CI - GITHUB_ACTIONS - GITHUB_ACTION - GITHUB_REF - GITHUB_REPOSITORY - GITHUB_HEAD_REF - GITHUB_RUN_ID - GITHUB_SHA - COVERAGE_FILE -deps = coverage -commands = - pip install -e '.[dev]' - pytest --cov --cov-report=xml -""" +[tool.ruff.lint.mccabe] +# Cyclomatic complexity threshold (default is 10) +max-complexity = 10 + +[tool.ruff.lint.pycodestyle] +# PEP 8 line length guidance: +# https://peps.python.org/pep-0008/#maximum-line-length +# Use 99 characters as the project-wide maximum for regular code lines. +# Use 72 characters for docstrings. +max-line-length = 128 # See also `line-length` in [tool.ruff] +max-doc-length = 128 +# max-doc-length = 72 + +[tool.ruff.lint.pydocstyle] +convention = 'numpy' + +[tool.ruff.lint.pylint] +# Ruff counts `self`/`cls` in max-args; traditional pylint does not. +# Setting 6 here matches pylint's default of 5 (excluding self). +max-args = 6 +max-positional-args = 6 + +############################# +# Configuration for pydoclint +############################# + +# 'pydoclint' -- Docstring linter, a faster alternative to +# 'darglint' or 'darglint2'. +# https://pypi.org/project/pydoclint/ + +# This is a more advanced docstring linter compared to Ruff's built-in +# docstring check rules D or DOC. For example, among many other things, +# it can check that arguments in the docstring, which are used by MkDocs +# and IDEs to render parameter documentation, remain synchronized with +# the parameter declarations in the code (in function's signature). + +[tool.pydoclint] +exclude = '\.' # Temporarily disable pydoclint until we are ready +style = 'numpy' +check-style-mismatch = true +check-arg-defaults = true +allow-init-docstring = true + +#################################### +# Configuration for format-docstring +#################################### + +# 'format-docstring' -- Code formatter for docstrings +# https://github.com/jsh9/format-docstring + +[tool.format_docstring] +exclude = '\.' # Temporarily disable format-docstring until we are ready +docstring_style = 'numpy' +line_length = 72 +fix_rst_backticks = true +verbose = 'default' diff --git a/src/easyreflectometry/__init__.py b/src/easyreflectometry/__init__.py index 6de988ac..8ff945d0 100644 --- a/src/easyreflectometry/__init__.py +++ b/src/easyreflectometry/__init__.py @@ -1,3 +1,8 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""EasyReflectometry library.""" + from importlib import metadata from .project import Project @@ -8,6 +13,6 @@ __version__ = '0.0.0' __all__ = [ - Project, - __version__, + 'Project', + '__version__', ] diff --git a/src/easyreflectometry/__version__.py b/src/easyreflectometry/__version__.py deleted file mode 100644 index bcd8d54e..00000000 --- a/src/easyreflectometry/__version__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '1.6.0' diff --git a/src/easyreflectometry/calculators/__init__.py b/src/easyreflectometry/calculators/__init__.py index 2411a89f..9f7bde45 100644 --- a/src/easyreflectometry/calculators/__init__.py +++ b/src/easyreflectometry/calculators/__init__.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import traceback from .calculator_base import CalculatorBase @@ -28,4 +31,4 @@ traceback.print_exc() print('Warning: refl1d is not installed') -__all__ = [CalculatorBase, CalculatorFactory] + imported_calculators +__all__ = ['CalculatorBase', 'CalculatorFactory'] + [c.__name__ for c in imported_calculators] diff --git a/src/easyreflectometry/calculators/bornagain/calculator.py b/src/easyreflectometry/calculators/bornagain/calculator.py index 06d86986..2ebb2c08 100644 --- a/src/easyreflectometry/calculators/bornagain/calculator.py +++ b/src/easyreflectometry/calculators/bornagain/calculator.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from easyscience.fitting.calculators.interface_factory import ItemContainer @@ -18,9 +20,7 @@ class BornAgain(CalculatorBase): - """ - Calculator for BornAgain - """ + """Calculator for BornAgain.""" name = 'BornAgain' @@ -45,23 +45,26 @@ class BornAgain(CalculatorBase): } def __init__(self): + """Init function.""" super().__init__() self._wrapper = BornAgainWrapper() def reset_storage(self) -> None: - """ - Reset the storage area of the calculator - """ + """Reset the storage area of the calculator.""" self._wrapper.reset_storage() def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemContainer]: - """ - Creation function + """Creation function. - :param model: Object to be created - :type model: Union[Material, Layer, Item, Model] - :return: Item containers of the objects - :rtype: List[ItemContainer] + Parameters + ---------- + model : Material | Layer | Multilayer | Model + Object to be created. + + Returns + ------- + List[ItemContainer] + Item containers of the objects. """ r_list = [] t_ = type(model) @@ -130,52 +133,61 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta return r_list def assign_material_to_layer(self, material_id: int, layer_id: int) -> None: - """ - Assign a material to a layer. - - :param material_name: The material name - :type material_name: str - :param layer_name: The layer name - :type layer_name: str + """Assign a material to a layer. + + Parameters + ---------- + layer_id : int + material_id : int + material_name : str + The material name. + layer_name : str + The layer name. """ self._wrapper.assign_material_to_layer(material_id, layer_id) def add_layer_to_item(self, layer_id: int, item_id: int) -> None: - """ - Add a layer to the item stack - - :param item_id: The item id - :type item_id: int - :param layer_id: The layer id - :type layer_id: int + """Add a layer to the item stack. + + Parameters + ---------- + item_id : int + The item id. + layer_id : int + The layer id. """ self._wrapper.add_layer_to_item(layer_id, item_id) def remove_layer_from_item(self, layer_id: int, item_id: int) -> None: - """ - Remove a layer from an item stack - - :param item_id: The item id - :param layer_id: The layer id + """Remove a layer from an item stack. + + Parameters + ---------- + item_id : int + The item id. + layer_id : int + The layer id. """ self._wrapper.remove_layer_from_item(layer_id, item_id) def add_item_to_model(self, item_id: int) -> None: - """ - Add a layer to the item stack + """Add a layer to the item stack. - :param item_id: The item id - :type item_id: int + Parameters + ---------- + item_id : int + The item id. """ self._wrapper.add_item(item_id) def remove_item_from_model(self, item_id: int) -> None: - """ - Remove a layer from the item stack - - :param item_id: The item id - :type item_id: int - :param layer_id: The layer id - :type layer_id: int + """Remove a layer from the item stack. + + Parameters + ---------- + item_id : int + The item id. + layer_id : int + The layer id. """ self._wrapper.remove_item(item_id) diff --git a/src/easyreflectometry/calculators/bornagain/wrapper.py b/src/easyreflectometry/calculators/bornagain/wrapper.py index 5ad3b985..b0baf9a9 100644 --- a/src/easyreflectometry/calculators/bornagain/wrapper.py +++ b/src/easyreflectometry/calculators/bornagain/wrapper.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import bornagain as ba import numpy as np @@ -14,6 +16,7 @@ class BornAgainWrapper(WrapperBase): def __init__(self): + """Init function.""" super().__init__() self.storage = { 'layer_material': {}, @@ -24,9 +27,7 @@ def __init__(self): } def reset_storage(self): - """ - Reset the storage area to blank. - """ + """Reset the storage area to blank.""" super().reset_storage() self.storage = { 'layer_material': {}, @@ -37,20 +38,23 @@ def reset_storage(self): } def create_material(self, name): - """ - Create a material using SLD. + """Create a material using SLD. - :param name: The name of the material - :type name: str + Parameters + ---------- + name : str + The name of the material. """ self.storage['material'][name] = ba.MaterialBySLD(str(name), 0.0, 0.0) def update_material(self, name, **kwargs): - """ - Update a material. + """Update a material. - :param name: The name of the material - :type name: str + Parameters + ---------- + **kwargs : + name : str + The name of the material. """ current_value = self.storage['material'][name].materialData() real = current_value.real @@ -64,40 +68,48 @@ def update_material(self, name, **kwargs): self.storage['material'][name] = ba.MaterialBySLD(str(name), real, imag) def get_material_value(self, name, key): - """ - A function to get a given material value - - :param name: The material name - :type name: str - :param key: The given value keys - :type name: str - :return: The desired value - :rtype: float + """A function to get a given material value. + + Parameters + ---------- + name : str + The material name. + key : str + The given value keys. + + Returns + ------- + float + The desired value. """ current_value = self.storage['material'][name].materialData() return getattr(current_value, key) / 1e-6 def create_layer(self, name): - """ - Create a layer using Slab. + """Create a layer using Slab. - :param name: The name of the layer - :type name: str + Parameters + ---------- + name : str + The name of the layer. """ self.storage['layer'][name] = ba.Layer(ba.MaterialBySLD('A', 0, 0)) self.storage['roughness'][name] = ba.LayerRoughness() def update_layer(self, name, **kwargs): - """ - Update a layer in a given item. + """Update a layer in a given item. - :param name: The layer name - :type name: str + Parameters + ---------- + **kwargs : + name : str + The layer name. """ if 'thickness' in kwargs.keys(): thickness = kwargs['thickness'] self.storage['layer'][name] = ba.Layer( - self.storage['material'][self.storage['layer_material'][name]], thickness * ba.angstrom + self.storage['material'][self.storage['layer_material'][name]], + thickness * ba.angstrom, ) if 'sigma' in kwargs.keys(): sigma = kwargs['sigma'] @@ -105,15 +117,19 @@ def update_layer(self, name, **kwargs): self.storage['roughness'][name].setSigma(sigma * ba.angstrom) def get_layer_value(self, name, key): - """ - A function to get a given layer value - - :param name: The layer name - :type name: str - :param key: The given value keys - :type name: str - :return: The desired value - :rtype: float + """A function to get a given layer value. + + Parameters + ---------- + name : str + The layer name. + key : str + The given value keys. + + Returns + ------- + float + The desired value. """ layer = self.storage['layer'][name] roughness = self.storage['roughness'][name] @@ -123,43 +139,48 @@ def get_layer_value(self, name, key): return roughness.getSigma() / ba.angstrom def create_item(self, name): - """ - Create an item. + """Create an item. - :param name: The name of the item - :type name: str + Parameters + ---------- + name : str + The name of the item. """ self.storage['item'][name] = [] self.storage['item_repeats'][name] = 1 def update_item(self, name, **kwargs): - """ - Update a layer. + """Update a layer. - :param name: The item name - :type name: str + Parameters + ---------- + **kwargs : + name : str + The item name. """ if 'repeats' in kwargs.keys(): self.storage['item_repeats'][name] = kwargs['repeats'] def get_item_value(self, name, key): - """ - A function to get a given item value - - :param name: The item name - :type name: str - :param key: The given value keys - :type name: str - :return: The desired value - :rtype: float + """A function to get a given item value. + + Parameters + ---------- + name : str + The item name. + key : str + The given value keys. + + Returns + ------- + float + The desired value. """ if key == 'repeats': return self.storage['item_repeats'][name] def create_model(self): - """ - Create a model for analysis - """ + """Create a model for analysis.""" self.storage['model'] = ba.Multilayer() self.storage['model'].setRoughnessModel(ba.RoughnessModel.NEVOT_CROCE) self.storage['model_items'] = [] @@ -168,65 +189,72 @@ def create_model(self): self.storage['model_parameters']['resolution'] = 0 def update_model(self, name, **kwargs): - """ - Update the non-structural parameters of the model - """ + """Update the non-structural parameters of the model.""" model = self.storage[name + '_parameters'] for key in kwargs.keys(): model[key] = kwargs[key] def get_model_value(self, name, key): - """ - A function to get a given model value + """A function to get a given model value. + + Parameters + ---------- + name : + key : str + The given value keys. - :param key: The given value keys - :type name: str - :return: The desired value - :rtype: float + Returns + ------- + float + The desired value. """ model = self.storage[name + '_parameters'] return model[key] def assign_material_to_layer(self, material_name, layer_name): - """ - Assign a material to a layer. + """Assign a material to a layer. - :param material_name: The material name - :type material_name: str - :param layer_name: The layer name - :type layer_name: str + Parameters + ---------- + material_name : str + The material name. + layer_name : str + The layer name. """ self.storage['layer_material'][layer_name] = material_name def add_layer_to_item(self, layer_name, item_name): - """ - Create a layer from the material of the same name, in a given item. + """Create a layer from the material of the same name, in a given item. - :param layer_name: The layer name - :type layer_name: int - :param item_name: The item name - :type item_name: int + Parameters + ---------- + layer_name : int + The layer name. + item_name : int + The item name. """ item = self.storage['item'][item_name] item.append(layer_name) def add_item(self, item_name): - """ - Add an item to the model. + """Add an item to the model. - :param item_name: items to add to model - :type item_name: str + Parameters + ---------- + item_name : str + Items to add to model. """ self.storage['model_items'].append(item_name) def remove_layer_from_item(self, layer_name, item_name): - """ - Remove a layer in a given item. + """Remove a layer in a given item. - :param layer_name: The layer name - :type layer_name: int - :param item_name: The item name - :type item_name: int + Parameters + ---------- + layer_name : int + The layer name. + item_name : int + The item name. """ layers_idx = self.storage['item'][item_name].index(layer_name) del self.storage['layer'][layer_name] @@ -234,11 +262,12 @@ def remove_layer_from_item(self, layer_name, item_name): del self.storage['layer_material'][layer_name] def remove_item(self, item_name): - """ - Remove a given item. + """Remove a given item. - :param item_name: The item name - :type item_name: int + Parameters + ---------- + item_name : int + The item name. """ item_idx = self.storage['model_items'].index(item_name) del self.storage['model_items'][item_idx] @@ -250,9 +279,17 @@ def remove_item(self, item_name): def calculate(self, q_array: np.ndarray) -> np.ndarray: """For a given q array calculate the corresponding reflectivity. - :param q_array: array of data points to be calculated - :param model_name: the model name - :return: reflectivity calculated at q + Parameters + ---------- + q_array : np.ndarray + Array of data points to be calculated. + model_name : + The model name. + + Returns + ------- + np.ndarray + Reflectivity calculated at q. """ # 3.5 sigma to sync with refnx n_sig = 3.5 @@ -261,7 +298,8 @@ def calculate(self, q_array: np.ndarray) -> np.ndarray: scan = ba.QSpecScan(q_array / ba.angstrom) scan.setAbsoluteQResolution( - distr, q_array / ba.angstrom * (self.storage['model_parameters']['resolution'] * 0.5 / 100) + distr, + q_array / ba.angstrom * (self.storage['model_parameters']['resolution'] * 0.5 / 100), ) simulation = ba.SpecularSimulation() @@ -286,13 +324,14 @@ def calculate(self, q_array: np.ndarray) -> np.ndarray: ) def sld_profile(self) -> np.ndarray: - """ - Return the scattering length density profile. + """Return the scattering length density profile. This is borrowed from the refnx implementation of the scattering length density. - :return: z and sld(z) - :rtype: tuple[np.ndarray, np.ndarray] + Returns + ------- + tuple[np.ndarray, np.ndarray] + Z and sld(z). """ number_of_layers = 0 for i in self.storage['model_items']: @@ -331,6 +370,7 @@ def sld_profile(self) -> np.ndarray: # use erf for roughness function, but step if the roughness is zero def step(z, scale=1, loc=0): + """Step function.""" new_z = z - loc f = np.ones_like(new_z) * 0.5 f[new_z <= -scale] = 0 diff --git a/src/easyreflectometry/calculators/calculator_base.py b/src/easyreflectometry/calculators/calculator_base.py index 7d1314cd..e2a92804 100644 --- a/src/easyreflectometry/calculators/calculator_base.py +++ b/src/easyreflectometry/calculators/calculator_base.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from __future__ import annotations from abc import ABCMeta @@ -19,9 +22,7 @@ class CalculatorBase(SerializerComponent, metaclass=ABCMeta): - """ - This class is a template and defines all properties that a calculator should have. - """ + """This class is a template and defines all properties that a calculator should have.""" _calculators: list[CalculatorBase] = [] # class variable to store all calculators _material_link: dict[str, str] @@ -30,27 +31,36 @@ class CalculatorBase(SerializerComponent, metaclass=ABCMeta): _model_link: dict[str, str] def __init_subclass__(cls, is_abstract: bool = False, **kwargs) -> None: - r"""Initialise all subclasses so that they can be created in the factory - - :param is_abstract: Is this a subclass which shouldn't be dded - :param kwargs: key word arguments + r"""Initialise all subclasses so that they can be created in the factory. + + Parameters + ---------- + cls : + is_abstract : bool, optional + Is this a subclass which shouldn't be dded. By default, False. + **kwargs : + Key word arguments. """ super().__init_subclass__(**kwargs) if not is_abstract: cls._calculators.append(cls) def __init__(self): + """Init function.""" self._namespace = {} self._wrapper: WrapperBase def reset_storage(self) -> None: - """Reset the storage area of the calculator""" + r"""Reset the storage area of the calculator.""" self._wrapper.reset_storage() def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemContainer]: - """Creation function + """Creation function. - :param model: Object to be created + Parameters + ---------- + model : Material | Layer | Multilayer | Model + Object to be created. """ r_list = [] t_ = type(model) @@ -122,72 +132,106 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta def assign_material_to_layer(self, material_id: str, layer_id: str) -> None: """Assign a material to a layer. - :param material_id: The material name - :param layer_id: The layer name + Parameters + ---------- + material_id : str + The material name. + layer_id : str + The layer name. """ self._wrapper.assign_material_to_layer(material_id, layer_id) def add_layer_to_item(self, layer_id: str, item_id: str) -> None: - """Add a layer to the item stack - - :param item_id: The item id - :param layer_id: The layer id + """Add a layer to the item stack. + + Parameters + ---------- + item_id : str + The item id. + layer_id : str + The layer id. """ self._wrapper.add_layer_to_item(layer_id, item_id) def remove_layer_from_item(self, layer_id: str, item_id: str) -> None: - """Remove a layer from an item stack - - :param item_id: The item id - :param layer_id: The layer id + """Remove a layer from an item stack. + + Parameters + ---------- + item_id : str + The item id. + layer_id : str + The layer id. """ self._wrapper.remove_layer_from_item(layer_id, item_id) def add_item_to_model(self, item_id: str, model_id: str) -> None: - """Add a layer to the item stack - - :param item_id: The item id - :param model_id: The model id + """Add a layer to the item stack. + + Parameters + ---------- + item_id : str + The item id. + model_id : str + The model id. """ self._wrapper.add_item(item_id, model_id) def remove_item_from_model(self, item_id: str, model_id: str) -> None: - """Remove an item from the model - - :param item_id: The item id - :param model_id: The model id + """Remove an item from the model. + + Parameters + ---------- + item_id : str + The item id. + model_id : str + The model id. """ self._wrapper.remove_item(item_id, model_id) def reflectity_profile(self, x_array: np.ndarray, model_id: str) -> np.ndarray: """Determines the reflectivity profile for the given range and model. - :param x_array: points to be calculated at - :param model_id: The model id + Parameters + ---------- + x_array : np.ndarray + Points to be calculated at. + model_id : str + The model id. """ return self._wrapper.calculate(x_array, model_id) def sld_profile(self, model_id: str) -> tuple[np.ndarray, np.ndarray]: - """ - Return the scattering length density profile. + """Return the scattering length density profile. - :param model_id: The model id - :return: z and sld(z) + Parameters + ---------- + model_id : str + The model id. + + Returns + ------- + tuple[np.ndarray, np.ndarray] + z and sld(z). """ return self._wrapper.sld_profile(model_id) def set_resolution_function(self, resolution_function: Callable[[np.array], np.array]) -> None: + """Set resolution function.""" return self._wrapper.set_resolution_function(resolution_function) @property def include_magnetism(self): + """Include magnetism.""" return self._wrapper.magnetism @include_magnetism.setter def include_magnetism(self, magnetism: bool): - """ - Set the magnetism flag for the calculator + """Set the magnetism flag for the calculator. - :param magnetism: True if the calculator should include magnetism + Parameters + ---------- + magnetism : bool + True if the calculator should include magnetism. """ self._wrapper.magnetism = magnetism diff --git a/src/easyreflectometry/calculators/factory.py b/src/easyreflectometry/calculators/factory.py index 15e996fd..c3e1479c 100644 --- a/src/easyreflectometry/calculators/factory.py +++ b/src/easyreflectometry/calculators/factory.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + __author__ = 'github.com/wardsimon' from typing import Callable @@ -8,16 +11,20 @@ class CalculatorFactory(InterfaceFactoryTemplate): def __init__(self): + """Init function.""" super().__init__(interface_list=CalculatorBase._calculators) def reset_storage(self) -> None: + """Reset storage.""" return self().reset_storage() def sld_profile(self, model_id: str) -> tuple: + """Sld profile.""" return self().sld_profile(model_id) @property def fit_func(self) -> Callable: + """Fit func.""" """ Pass through to the underlying interfaces fitting function. @@ -32,6 +39,7 @@ def fit_func(self) -> Callable: #""" def __fit_func(*args, **kwargs): + """Fit func.""" return self().reflectity_profile(*args, **kwargs) return __fit_func diff --git a/src/easyreflectometry/calculators/refl1d/calculator.py b/src/easyreflectometry/calculators/refl1d/calculator.py index a472b7b5..2f5068de 100644 --- a/src/easyreflectometry/calculators/refl1d/calculator.py +++ b/src/easyreflectometry/calculators/refl1d/calculator.py @@ -1,13 +1,13 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from ..calculator_base import CalculatorBase from .wrapper import Refl1dWrapper class Refl1d(CalculatorBase): - """ - Calculator for refl1 - """ + """Calculator for refl1.""" name = 'refl1d' @@ -31,5 +31,6 @@ class Refl1d(CalculatorBase): } def __init__(self): + """Init function.""" super().__init__() self._wrapper = Refl1dWrapper() diff --git a/src/easyreflectometry/calculators/refl1d/wrapper.py b/src/easyreflectometry/calculators/refl1d/wrapper.py index e47cf052..7a07c917 100644 --- a/src/easyreflectometry/calculators/refl1d/wrapper.py +++ b/src/easyreflectometry/calculators/refl1d/wrapper.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Tuple @@ -17,18 +19,22 @@ class Refl1dWrapper(WrapperBase): def create_material(self, name: str): - """ - Create a material using SLD. + """Create a material using SLD. - :param name: The name of the material + Parameters + ---------- + name : str + The name of the material. """ self.storage['material'][name] = names.SLD(str(name)) def create_layer(self, name: str): - """ - Create a layer using Slab. + """Create a layer using Slab. - :param name: The name of the layer + Parameters + ---------- + name : str + The name of the layer. """ if self._magnetism: magnetism = names.Magnetism(rhoM=0.0, thetaM=0.0) @@ -37,10 +43,12 @@ def create_layer(self, name: str): self.storage['layer'][name] = names.Slab(name=str(name), magnetism=magnetism) def create_item(self, name: str): - """ - Create an item using Repeat. + """Create an item using Repeat. - :param name: The name of the item + Parameters + ---------- + name : str + The name of the item. """ self.storage['item'][name] = Repeat(names.Stack(names.Slab(names.SLD(), thickness=0, interface=0)), name=str(name)) del self.storage['item'][name].stack[0] @@ -48,8 +56,11 @@ def create_item(self, name: str): def update_layer(self, name: str, **kwargs): """Update a layer in a given item. - :param name: The layer name. - :param kwargs: + Parameters + ---------- + name : str + The layer name. + **kwargs : """ kwargs_no_magnetism = {k: v for k, v in kwargs.items() if k != 'magnetism_rhoM' and k != 'magnetism_thetaM'} super().update_layer(name, **kwargs_no_magnetism) @@ -58,10 +69,14 @@ def update_layer(self, name: str, **kwargs): self.storage['layer'][name].magnetism = magnetism def get_layer_value(self, name: str, key: str) -> float: - """A function to get a given layer value - - :param name: The layer name - :param key: The given value keys + """A function to get a given layer value. + + Parameters + ---------- + name : str + The layer name. + key : str + The given value keys. """ if key in ['magnetism_rhoM', 'magnetism_thetaM']: return getattr( @@ -70,78 +85,105 @@ def get_layer_value(self, name: str, key: str) -> float: return super().get_layer_value(name, key) def create_model(self, name: str): - """ - Create a model for analysis + """Create a model for analysis. - :param name: Name for the model + Parameters + ---------- + name : str + Name for the model. """ self.storage['model'][name] = {'scale': 1, 'bkg': 0, 'items': []} def update_model(self, name: str, **kwargs): - """ - Update the non-structural parameters of the model + """Update the non-structural parameters of the model. - :param name: Name of the model + Parameters + ---------- + **kwargs : + name : str + Name of the model. """ model = self.storage['model'][name] for key in kwargs.keys(): model[key] = kwargs[key] def get_model_value(self, name: str, key: str) -> float: - """ - A function to get a given model value - - :param name: Name of the model - :param key: The given value keys - :return: The desired value + """A function to get a given model value. + + Parameters + ---------- + name : str + Name of the model. + key : str + The given value keys. + + Returns + ------- + float + The desired value. """ model = self.storage['model'][name] return model[key] def assign_material_to_layer(self, material_name: str, layer_name: str): - """ - Assign a material to a layer. - - :param material_name: The material name - :param layer_name: The layer name + """Assign a material to a layer. + + Parameters + ---------- + material_name : str + The material name. + layer_name : str + The layer name. """ self.storage['layer'][layer_name].material = self.storage['material'][material_name] def add_layer_to_item(self, layer_name: str, item_name: str): - """ - Create a layer from the material of the same name, in a given item. - - :param layer_name: The layer name - :param item_name: The item name + """Create a layer from the material of the same name, in a given item. + + Parameters + ---------- + layer_name : str + The layer name. + item_name : str + The item name. """ item = self.storage['item'][item_name] item.stack.add(self.storage['layer'][layer_name]) def add_item(self, item_name: str, model_name: str): - """ - Add an item to the model. - - :param item_name: items to add to model - :param model_name: name for the model + """Add an item to the model. + + Parameters + ---------- + item_name : str + Items to add to model. + model_name : str + Name for the model. """ self.storage['model'][model_name]['items'].append(self.storage['item'][item_name]) def remove_layer_from_item(self, layer_name: str, item_name: str): - """ - Remove a layer in a given item. - - :param layer_name: The layer name - :param item_name: The item name + """Remove a layer in a given item. + + Parameters + ---------- + layer_name : str + The layer name. + item_name : str + The item name. """ layer_idx = list(self.storage['item'][item_name].stack).index(self.storage['layer'][layer_name]) del self.storage['item'][item_name].stack[layer_idx] def remove_item(self, item_name: str, model_name: str): - """ - Remove a given item. - - :param item_name: The item name - :param model_name: The model name + """Remove a given item. + + Parameters + ---------- + item_name : str + The item name. + model_name : str + The model name. """ item_idx = self.storage['model'][model_name]['items'].index(self.storage['item'][item_name]) del self.storage['model'][model_name]['items'][item_idx] @@ -150,9 +192,17 @@ def remove_item(self, item_name: str, model_name: str): def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: """For a given q array calculate the corresponding reflectivity. - :param q_array: array of data points to be calculated - :param model_name: the model name - :return: reflectivity calculated at q + Parameters + ---------- + q_array : np.ndarray + Array of data points to be calculated. + model_name : str + The model name. + + Returns + ------- + np.ndarray + Reflectivity calculated at q. """ sample = _build_sample(self.storage, model_name) dq_array = self._resolution_function.smearing(q_array) @@ -197,11 +247,17 @@ def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: return reflectivity def sld_profile(self, model_name: str) -> Tuple[np.ndarray, np.ndarray]: - """ - Return the scattering length density profile. + """Return the scattering length density profile. + + Parameters + ---------- + model_name : str + The model name. + + Returns + ------- - :param model_name: the model name - :return: z and sld(z) + Z and sld(z). """ sample = _build_sample(self.storage, model_name) probe = _get_probe( @@ -216,6 +272,7 @@ def sld_profile(self, model_name: str) -> Tuple[np.ndarray, np.ndarray]: def _get_oversampling_q(q_array: np.ndarray, dq_array: np.ndarray, oversampling_factor: int) -> np.ndarray: + """Get oversampling q.""" argmin = np.argmin(q_array) # index of the smallest q element argmax = np.argmax(q_array) # index of the largest q element return np.linspace( @@ -233,6 +290,7 @@ def _get_probe( oversampling_factor: int = 1, magnetism: bool = False, ) -> names.QProbe: + """Get probe.""" probe = names.QProbe( Q=q_array, dQ=dq_array, @@ -258,6 +316,7 @@ def _get_polarized_probe( oversampling_factor: int = 1, all_polarizations: bool = False, ) -> names.PolarizedNeutronQProbe: + """Get polarized probe.""" four_probes = [] for i in range(4): if i == 0 or all_polarizations: @@ -281,6 +340,7 @@ def _get_polarized_probe( def _build_sample(storage: dict, model_name: str) -> names.Stack: + """Build sample.""" sample = names.Stack() # -1 to reverse the order for i in storage['model'][model_name]['items'][::-1]: diff --git a/src/easyreflectometry/calculators/refnx/calculator.py b/src/easyreflectometry/calculators/refnx/calculator.py index 2a5b45b0..1362bad2 100644 --- a/src/easyreflectometry/calculators/refnx/calculator.py +++ b/src/easyreflectometry/calculators/refnx/calculator.py @@ -1,13 +1,13 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from ..calculator_base import CalculatorBase from .wrapper import RefnxWrapper class Refnx(CalculatorBase): - """ - Calculator for refnx - """ + """Calculator for refnx.""" name = 'refnx' @@ -31,5 +31,6 @@ class Refnx(CalculatorBase): } def __init__(self): + """Init function.""" super().__init__() self._wrapper = RefnxWrapper() diff --git a/src/easyreflectometry/calculators/refnx/wrapper.py b/src/easyreflectometry/calculators/refnx/wrapper.py index d2290877..3742d727 100644 --- a/src/easyreflectometry/calculators/refnx/wrapper.py +++ b/src/easyreflectometry/calculators/refnx/wrapper.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Tuple @@ -13,53 +15,68 @@ class RefnxWrapper(WrapperBase): @property def include_magnetism(self) -> bool: + """Include magnetism.""" return self._magnetism @include_magnetism.setter def include_magnetism(self, magnetism: bool) -> None: """Set the magnetism flag. - :param magnetism: The magnetism flag + Parameters + ---------- + magnetism : bool + The magnetism flag. """ raise NotImplementedError('Magnetism is not supported by refnx') def create_material(self, name: str): - """ - Create a material using SLD. + """Create a material using SLD. - :param name: The name of the material + Parameters + ---------- + name : str + The name of the material. """ self.storage['material'][name] = reflect.SLD(0, name=name) def create_layer(self, name: str): - """ - Create a layer using Slab. + """Create a layer using Slab. - :param name: The name of the layer + Parameters + ---------- + name : str + The name of the layer. """ self.storage['layer'][name] = reflect.Slab(0, 0, 0, name=name) def create_item(self, name: str): - """ - Create an item using Stack. + """Create an item using Stack. - :param name: The name of the item + Parameters + ---------- + name : str + The name of the item. """ self.storage['item'][name] = reflect.Stack(name=name) def create_model(self, name: str): - """ - Create a model for analysis + """Create a model for analysis. - :param name: Name for the model + Parameters + ---------- + name : str + Name for the model. """ self.storage['model'][name] = reflect.ReflectModel(reflect.Structure()) def update_model(self, name: str, **kwargs): - """ - Update the non-structural parameters of the model + """Update the non-structural parameters of the model. - :param name: Name for the model + Parameters + ---------- + **kwargs : + name : str + Name for the model. """ model = self.storage['model'][name] for key in kwargs.keys(): @@ -67,61 +84,83 @@ def update_model(self, name: str, **kwargs): setattr(item, 'value', kwargs[key]) def get_model_value(self, name: str, key: str) -> float: - """ - A function to get a given model value + """A function to get a given model value. + + Parameters + ---------- + name : str + Name for the model. + key : str + The given value keys. - :param name: Name for the model - :param key: The given value keys - :return: The desired value + Returns + ------- + float + The desired value. """ model = self.storage['model'][name] item = getattr(model, key) return getattr(item, 'value') def assign_material_to_layer(self, material_name: str, layer_name: str): - """ - Assign a material to a layer. + """Assign a material to a layer. - :param material_name: The material name - :param layer_name: The layer name + Parameters + ---------- + material_name : str + The material name. + layer_name : str + The layer name. """ self.storage['layer'][layer_name].sld = self.storage['material'][material_name] def add_layer_to_item(self, layer_name: str, item_name: str): - """ - Create a layer from the material of the same name, in a given item. + """Create a layer from the material of the same name, in a given item. - :param layer_name: The layer name - :param item_name: The item name + Parameters + ---------- + layer_name : str + The layer name. + item_name : str + The item name. """ item = self.storage['item'][item_name] item.append(self.storage['layer'][layer_name]) def add_item(self, item_name: str, model_name: str): - """ - Add an item to the model. + """Add an item to the model. - :param item_name: items to add to model - :param model_name: Name for the model + Parameters + ---------- + item_name : str + Items to add to model. + model_name : str + Name for the model. """ self.storage['model'][model_name].structure.components.append(self.storage['item'][item_name]) def remove_layer_from_item(self, layer_name: str, item_name: str): - """ - Remove a layer in a given item. + """Remove a layer in a given item. - :param layer_name: The layer name - :param item_name: The item name + Parameters + ---------- + layer_name : str + The layer name. + item_name : str + The item name. """ layer_idx = self.storage['item'][item_name].components.index(self.storage['layer'][layer_name]) del self.storage['item'][item_name].components[layer_idx] def remove_item(self, item_name: str, model_name: str): - """ - Remove a given item. + """Remove a given item. - :param item_name: The item name - :param model_name: Name of the model + Parameters + ---------- + item_name : str + The item name. + model_name : str + Name of the model. """ item_idx = self.storage['model'][model_name].structure.components.index(self.storage['item'][item_name]) del self.storage['model'][model_name].structure.components[item_idx] @@ -130,9 +169,17 @@ def remove_item(self, item_name: str, model_name: str): def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: """For a given q array calculate the corresponding reflectivity. - :param q_array: array of data points to be calculated - :param model_name: the model name - :return: reflectivity calculated at q + Parameters + ---------- + q_array : np.ndarray + Array of data points to be calculated. + model_name : str + The model name. + + Returns + ------- + np.ndarray + Reflectivity calculated at q. """ structure = _remove_unecessary_stacks(self.storage['model'][model_name].structure) model = reflect.ReflectModel( @@ -151,22 +198,33 @@ def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: return model(x=q_array, x_err=dq_vector) def sld_profile(self, model_name: str) -> Tuple[np.ndarray, np.ndarray]: - """ - Return the scattering length density profile. + """Return the scattering length density profile. - :param model_name: Name for the model - :return: z and sld(z) + Parameters + ---------- + model_name : str + Name for the model. + + Returns + ------- + + Z and sld(z). """ return _remove_unecessary_stacks(self.storage['model'][model_name].structure).sld_profile() def _remove_unecessary_stacks(current_structure: reflect.Structure) -> reflect.Structure: - """ - Removed unnecessary reflect.Stack objects from the structure. + """Removed unnecessary reflect.Stack objects from the structure. + + Parameters + ---------- + current_structure : reflect.Structure + The current structure. - :param current_structure: The current structure - :return: The structre without the unnecessary Stacks - :rtype: reflect.structure + Returns + ------- + reflect.structure + The structre without the unnecessary Stacks. """ structure = [] for i in current_structure.components: diff --git a/src/easyreflectometry/calculators/wrapper_base.py b/src/easyreflectometry/calculators/wrapper_base.py index 0755ff64..dc53ceca 100644 --- a/src/easyreflectometry/calculators/wrapper_base.py +++ b/src/easyreflectometry/calculators/wrapper_base.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from abc import abstractmethod import numpy as np @@ -31,7 +34,10 @@ def reset_storage(self): def create_material(self, name: str): """Create a material using SLD. - :param name: The name of the material + Parameters + ---------- + name : str + The name of the material. """ ... @@ -39,7 +45,10 @@ def create_material(self, name: str): def create_layer(self, name: str): """Create a layer using Slab. - :param name: The name of the layer + Parameters + ---------- + name : str + The name of the layer. """ ... @@ -47,34 +56,46 @@ def create_layer(self, name: str): def create_item(self, name: str): """Create an item using Stack. - :param name: The name of the item + Parameters + ---------- + name : str + The name of the item. """ ... @abstractmethod def create_model(self, name: str): - """Create a model for analysis + """Create a model for analysis. - :param name: Name for the model + Parameters + ---------- + name : str + Name for the model. """ ... @abstractmethod def update_model(self, name: str, **kwargs): - """Update the non-structural parameters of the model - - :param name: Name for the model - :param kwargs: + """Update the non-structural parameters of the model. + Parameters + ---------- + name : str + Name for the model. + **kwargs : """ ... @abstractmethod def get_model_value(self, name: str, key: str) -> float: - """A function to get a given model value - - :param name: Name for the model - :param key: The given value keys + """A function to get a given model value. + + Parameters + ---------- + name : str + Name for the model. + key : str + The given value keys. """ ... @@ -82,8 +103,12 @@ def get_model_value(self, name: str, key: str) -> float: def assign_material_to_layer(self, material_name: str, layer_name: str): """Assign a material to a layer. - :param material_name: The material name - :param layer_name: The layer name + Parameters + ---------- + material_name : str + The material name. + layer_name : str + The layer name. """ ... @@ -91,8 +116,12 @@ def assign_material_to_layer(self, material_name: str, layer_name: str): def add_layer_to_item(self, layer_name: str, item_name: str): """Create a layer from the material of the same name, in a given item. - :param layer_name: The layer name - :param item_name: The item name + Parameters + ---------- + layer_name : str + The layer name. + item_name : str + The item name. """ ... @@ -100,8 +129,12 @@ def add_layer_to_item(self, layer_name: str, item_name: str): def add_item(self, item_name: str, model_name: str): """Add an item to the model. - :param item_name: items to add to model - :param model_name: Name for the model + Parameters + ---------- + item_name : str + Items to add to model. + model_name : str + Name for the model. """ ... @@ -109,8 +142,12 @@ def add_item(self, item_name: str, model_name: str): def remove_layer_from_item(self, layer_name: str, item_name: str): """Remove a layer in a given item. - :param layer_name: The layer name - :param item_name: The item name + Parameters + ---------- + layer_name : str + The layer name. + item_name : str + The item name. """ ... @@ -118,8 +155,12 @@ def remove_layer_from_item(self, layer_name: str, item_name: str): def remove_item(self, item_name: str, model_name: str): """Remove a given item. - :param item_name: The item name - :param model_name: Name of the model + Parameters + ---------- + item_name : str + The item name. + model_name : str + Name of the model. """ ... @@ -127,9 +168,17 @@ def remove_item(self, item_name: str, model_name: str): def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: """For a given q array calculate the corresponding reflectivity. - :param q_array: array of data points to be calculated - :param model_name: the model name - :return: reflectivity calculated at q + Parameters + ---------- + q_array : np.ndarray + Array of data points to be calculated. + model_name : str + The model name. + + Returns + ------- + np.ndarray + Reflectivity calculated at q. """ ... @@ -137,15 +186,27 @@ def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: def sld_profile(self, model_name: str) -> tuple[np.ndarray, np.ndarray]: """Return the scattering length density profile. - :param model_name: Name for the model - :return: z and sld(z) + Parameters + ---------- + model_name : str + Name for the model. + + Returns + ------- + tuple[np.ndarray, np.ndarray] + Z and sld(z). """ ... def update_material(self, name: str, **kwargs): """Update a material. - :param name: The name of the material + Parameters + ---------- + name : str + The name of the material. + **kwargs : + Key-value pairs of attributes to update. """ material = self.storage['material'][name] for key in kwargs.keys(): @@ -153,11 +214,19 @@ def update_material(self, name: str, **kwargs): setattr(item, 'value', kwargs[key]) def get_material_value(self, name: str, key: str) -> float: - """A function to get a given material value - - :param name: The material name - :param key: The given value keys - :return: The desired value + """A function to get a given material value. + + Parameters + ---------- + name : str + The material name. + key : str + The given value keys. + + Returns + ------- + float + The desired value. """ material = self.storage['material'][name] item = getattr(material, key) @@ -166,8 +235,11 @@ def get_material_value(self, name: str, key: str) -> float: def update_layer(self, name: str, **kwargs): """Update a layer in a given item. - :param name: The layer name. - :param kwargs: + Parameters + ---------- + name : str + The layer name. + **kwargs : """ layer = self.storage['layer'][name] for key in kwargs.keys(): @@ -175,10 +247,14 @@ def update_layer(self, name: str, **kwargs): setattr(ii, 'value', kwargs[key]) def get_layer_value(self, name: str, key: str) -> float: - """A function to get a given layer value - - :param name: The layer name - :param key: The given value keys + """A function to get a given layer value. + + Parameters + ---------- + name : str + The layer name. + key : str + The given value keys. """ layer = self.storage['layer'][name] ii = getattr(layer, key) @@ -187,7 +263,11 @@ def get_layer_value(self, name: str, key: str) -> float: def update_item(self, name: str, **kwargs): """Update a layer. - :param name: The item name + Parameters + ---------- + **kwargs : + name : str + The item name. """ item = self.storage['item'][name] for key in kwargs.keys(): @@ -195,11 +275,19 @@ def update_item(self, name: str, **kwargs): setattr(ii, 'value', kwargs[key]) def get_item_value(self, name: str, key: str) -> float: - """A function to get a given item value - - :param name: The item name - :param key: The given value keys - :return: The desired value + """A function to get a given item value. + + Parameters + ---------- + name : str + The item name. + key : str + The given value keys. + + Returns + ------- + float + The desired value. """ item = self.storage['item'][name] item = getattr(item, key) @@ -208,18 +296,25 @@ def get_item_value(self, name: str, key: str) -> float: def set_resolution_function(self, resolution_function: ResolutionFunction) -> None: """Set the resolution function for the calculator. - :param resolution_function: The resolution function + Parameters + ---------- + resolution_function : ResolutionFunction + The resolution function. """ self._resolution_function = resolution_function @property def magnetism(self) -> bool: + """Magnetism function.""" return self._magnetism @magnetism.setter def magnetism(self, magnetism: bool) -> None: """Set the magnetism flag. - :param magnetism: The magnetism flag + Parameters + ---------- + magnetism : bool + The magnetism flag. """ self._magnetism = magnetism diff --git a/src/easyreflectometry/data/__init__.py b/src/easyreflectometry/data/__init__.py index 194f0d31..0d058120 100644 --- a/src/easyreflectometry/data/__init__.py +++ b/src/easyreflectometry/data/__init__.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from .data_store import DataSet1D from .data_store import ProjectData from .measurement import load diff --git a/src/easyreflectometry/data/data_store.py b/src/easyreflectometry/data/data_store.py index 948382d7..b0cf001f 100644 --- a/src/easyreflectometry/data/data_store.py +++ b/src/easyreflectometry/data/data_store.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + __author__ = 'github.com/wardsimon' from collections.abc import Sequence @@ -16,6 +19,7 @@ class ProjectData(SerializerComponent): def __init__(self, name='DataStore', exp_data=None, sim_data=None): + """Init function.""" self.name = name if exp_data is None: exp_data = DataStore(name='Exp Datastore') @@ -27,31 +31,39 @@ def __init__(self, name='DataStore', exp_data=None, sim_data=None): class DataStore(Sequence, SerializerComponent): def __init__(self, *args, name='DataStore'): + """Init function.""" self.name = name self.items = list(args) self.show_legend = False def __getitem__(self, i: int) -> T: + """Getitem function.""" return self.items.__getitem__(i) def __len__(self) -> int: + """Len function.""" return len(self.items) def __setitem__(self, key, value): + """Setitem function.""" self.items[key] = value def __delitem__(self, key): + """Delitem function.""" del self.items[key] def append(self, *args): + """Append function.""" self.items.append(*args) def as_dict(self, skip: list = []) -> dict: + """As dict.""" this_dict = super(DataStore, self).as_dict(self, skip=skip) this_dict['items'] = [item.as_dict() for item in self.items if hasattr(item, 'as_dict')] @classmethod def from_dict(cls, d): + """From dict.""" items = d['items'] del d['items'] obj = cls.from_dict(d) @@ -61,10 +73,12 @@ def from_dict(cls, d): @property def experiments(self): + """Experiments function.""" return [self[idx] for idx in range(len(self)) if self[idx].is_experiment] @property def simulations(self): + """Simulations function.""" return [self[idx] for idx in range(len(self)) if self[idx].is_simulation] @@ -81,6 +95,7 @@ def __init__( y_label: str = 'y', auto_background: bool = True, ): + """Init function.""" self._model = model if y is not None and model is not None and auto_background: self._model.background = max(np.min(y), 1e-10) @@ -119,22 +134,28 @@ def __init__( @property def model(self) -> 'Model': # delay type checking until runtime (quotes) + """Model function.""" return self._model @model.setter def model(self, new_model: 'Model') -> None: + """Model function.""" self._model = new_model @property def is_experiment(self) -> bool: + """Is experiment.""" return self._model is not None @property def is_simulation(self) -> bool: + """Is simulation.""" return self._model is None def data_points(self) -> tuple[float, float, float, float]: + """Data points.""" return zip(self.x, self.y, self.ye, self.xe) def __repr__(self) -> str: + """Repr function.""" return "1D DataStore of '{:s}' Vs '{:s}' with {} data points".format(self.x_label, self.y_label, len(self.x)) diff --git a/src/easyreflectometry/data/measurement.py b/src/easyreflectometry/data/measurement.py index df4064b6..12117162 100644 --- a/src/easyreflectometry/data/measurement.py +++ b/src/easyreflectometry/data/measurement.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import os from typing import TextIO @@ -14,7 +16,10 @@ def load(fname: Union[TextIO, str]) -> sc.DataGroup: """Load data from an ORSO .ort file. - :param fname: The file to be read. + Parameters + ---------- + fname : Union[TextIO, str] + The file to be read. """ try: return load_data_from_orso_file(fname) @@ -40,6 +45,7 @@ def load_as_dataset(fname: Union[TextIO, str]) -> DataSet1D: def extract_orso_title(data_group: sc.DataGroup, data_name: str) -> str | None: + """Extract orso title.""" try: header = data_group['attrs'][data_name]['orso_header'] title = header.values.get('data_source', {}).get('experiment', {}).get('title') @@ -54,7 +60,10 @@ def extract_orso_title(data_group: sc.DataGroup, data_name: str) -> str | None: def _load_txt(fname: Union[TextIO, str]) -> sc.DataGroup: """Load data from a simple txt file. - :param fname: The path for the file to be read. + Parameters + ---------- + fname : Union[TextIO, str] + The path for the file to be read. """ # fname can have either a space or a comma as delimiter # Determine the delimiter used in the file diff --git a/src/easyreflectometry/fitting.py b/src/easyreflectometry/fitting.py index 0750beb5..78f74e68 100644 --- a/src/easyreflectometry/fitting.py +++ b/src/easyreflectometry/fitting.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import warnings @@ -18,11 +20,20 @@ def _validate_objective(objective: str) -> str: """Validate and resolve the objective string. - :param objective: The objective mode string. - :type objective: str - :return: Resolved objective string ('auto' becomes 'hybrid'). - :rtype: str - :raises ValueError: If the objective is not one of the valid options. + Parameters + ---------- + objective : str + The objective mode string. + + Raises + ------ + ValueError : + If the objective is not one of the valid options. + + Returns + ------- + str + Resolved objective string ('auto' becomes 'hybrid'). """ if objective not in _VALID_OBJECTIVES: raise ValueError(f'Unknown objective {objective!r}. Valid options: {_VALID_OBJECTIVES}') @@ -46,17 +57,22 @@ def _prepare_fit_arrays( Note: ``variances`` here means σ² (the scipp convention), not σ. - :param x_vals: Independent variable values. - :type x_vals: np.ndarray - :param y_vals: Observed dependent variable values. - :type y_vals: np.ndarray - :param variances: Variance (σ²) of each observed point. - :type variances: np.ndarray - :param objective: One of 'legacy_mask', 'hybrid', 'mighell'. - :type objective: str - :return: Tuple of (x_out, y_eff, weights, stats) where stats is a dict - with keys 'valid', 'mighell_substituted', 'masked'. - :rtype: tuple[np.ndarray, np.ndarray, np.ndarray, dict] + Parameters + ---------- + x_vals : np.ndarray + Independent variable values. + y_vals : np.ndarray + Observed dependent variable values. + variances : np.ndarray + Variance (σ²) of each observed point. + objective : str + One of 'legacy_mask', 'hybrid', 'mighell'. + + Returns + ------- + tuple[np.ndarray, np.ndarray, np.ndarray, dict] + Tuple of (x_out, y_eff, weights, stats) where stats is a dict + with keys 'valid', 'mighell_substituted', 'masked'. """ n = len(y_vals) zero_mask = variances <= 0.0 @@ -71,7 +87,12 @@ def _prepare_fit_arrays( weights = 1.0 / np.sqrt(variances[valid]) else: weights = np.array([]) - stats = {'valid': n_valid, 'mighell_substituted': 0, 'masked': n_zero, 'transformed_all_points': False} + stats = { + 'valid': n_valid, + 'mighell_substituted': 0, + 'masked': n_zero, + 'transformed_all_points': False, + } return x_out, y_eff, weights, stats # hybrid or mighell @@ -143,18 +164,24 @@ def __init__(self, *args: Model, objective: str = 'hybrid'): which will populate the :py:class:`sc.DataGroup` appropriately after the fitting is performed. - :param args: Reflectometry model(s). - :param objective: Zero-variance handling strategy. One of + Parameters + ---------- + *args : Model + Reflectometry model(s). + objective : str, optional + Zero-variance handling strategy. One of ``'hybrid'`` (default, Mighell for zero-variance, WLS otherwise), ``'mighell'`` (Mighell transform for all points), ``'legacy_mask'`` (drop zero-variance points), - ``'auto'`` (alias for ``'hybrid'``). - :type objective: str + ``'auto'`` (alias for ``'hybrid'``). By default, 'hybrid'. """ # This lets the unique_name be passed with the fit_func. def func_wrapper(func, unique_name): + """Func wrapper.""" + def wrapped(*args, **kwargs): + """Wrapped function.""" return func(*args, unique_name, **kwargs) return wrapped @@ -169,21 +196,20 @@ def wrapped(*args, **kwargs): def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> sc.DataGroup: """Perform the fitting and populate the DataGroups with the result. - :param data: DataGroup to be fitted to and populated. - :type data: sc.DataGroup - :param id: Unused parameter kept for backward compatibility. - :type id: int - :param objective: Per-call override for the zero-variance objective. - If ``None``, uses the instance default set at construction. - :type objective: str or None - :return: A new DataGroup with fitted model curves, SLD profiles, and fit statistics. - :rtype: sc.DataGroup - - :note: Under the ``mighell`` objective all points are transformed, - so ``reduced_chi`` is not a classical chi-square statistic. - Under ``hybrid``, only zero-variance points are transformed; - when they are a small fraction of the data the chi-square - remains approximately classical. + Parameters + ---------- + data : sc.DataGroup + DataGroup to be fitted to and populated. + id : int, optional + Unused parameter kept for backward compatibility. By default, 0. + objective : str | None, optional + Per-call override for the zero-variance objective. + If ``None``, uses the instance default set at construction. By default, None. + + Returns + ------- + sc.DataGroup + A new DataGroup with fitted model curves, SLD profiles, and fit statistics. """ obj = _validate_objective(objective) if objective is not None else self._objective @@ -236,7 +262,9 @@ def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> if 'attrs' in new_data: new_data['attrs'][f'R_{id}_model'] = {'model': sc.scalar(self._models[i].as_dict())} new_data['coords'][f'z_{id}'] = sc.array( - dims=[f'z_{id}'], values=sld_profile[0], unit=(1 / new_data['coords'][f'Qz_{id}'].unit).unit + dims=[f'z_{id}'], + values=sld_profile[0], + unit=(1 / new_data['coords'][f'Qz_{id}'].unit).unit, ) original = original_arrays[i] sigma_classical = np.sqrt(np.clip(original['variances'], 0.0, None)) @@ -246,15 +274,13 @@ def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> objective_chi2 = float(result[i].chi2) objective_reduced_chi = _fit_result_reduced_chi(result[i], np.size(result[i].x)) - self._classical_fit_metrics.append( - { - 'classical_chi2': classical_chi2, - 'classical_reduced_chi': classical_reduced_chi, - 'objective_chi2': objective_chi2, - 'objective_reduced_chi': objective_reduced_chi, - 'n_classical_points': n_classical_points, - } - ) + self._classical_fit_metrics.append({ + 'classical_chi2': classical_chi2, + 'classical_reduced_chi': classical_reduced_chi, + 'objective_chi2': objective_chi2, + 'objective_reduced_chi': objective_reduced_chi, + 'n_classical_points': n_classical_points, + }) new_data['objective_chi2'] = objective_chi2 new_data['objective_reduced_chi'] = objective_reduced_chi @@ -267,14 +293,19 @@ def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) -> FitResults: """Perform fitting on a single 1D dataset. - :param data: The 1D dataset to fit. Note that ``data.ye`` stores + Parameters + ---------- + data : DataSet1D + The 1D dataset to fit. Note that ``data.ye`` stores variances (σ²), not standard deviations. - :type data: DataSet1D - :param objective: Per-call override for the zero-variance objective. - If ``None``, uses the instance default set at construction. - :type objective: str or None - :return: Fit results from the minimizer. - :rtype: FitResults + objective : str | None, optional + Per-call override for the zero-variance objective. + If ``None``, uses the instance default set at construction. By default, None. + + Returns + ------- + FitResults + Fit results from the minimizer. """ obj = _validate_objective(objective) if objective is not None else self._objective @@ -372,20 +403,27 @@ def objective_reduced_chi(self) -> float | None: return self.reduced_chi def switch_minimizer(self, minimizer: AvailableMinimizers) -> None: - """ - Switch the minimizer for the fitting. + """Switch the minimizer for the fitting. - :param minimizer: Minimizer to be switched to + Parameters + ---------- + minimizer : AvailableMinimizers + Minimizer to be switched to. """ self.easy_science_multi_fitter.switch_minimizer(minimizer) def _flatten_list(this_list: list) -> list: - """ - Flatten nested lists. + """Flatten nested lists. - :param this_list: List to be flattened + Parameters + ---------- + this_list : list + List to be flattened. - :return: Flattened list + Returns + ------- + list + Flattened list. """ return np.array([item for sublist in this_list for item in sublist]) diff --git a/src/easyreflectometry/limits.py b/src/easyreflectometry/limits.py index 691f86e3..001bba64 100644 --- a/src/easyreflectometry/limits.py +++ b/src/easyreflectometry/limits.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import numpy as np from easyscience.variable import Parameter @@ -9,10 +12,12 @@ def apply_default_limits(parameter: Parameter, kind: str) -> None: """Apply default min/max to a parameter if current bounds are infinite. - :param parameter: The parameter to adjust. - :type parameter: Parameter - :param kind: One of 'thickness', 'roughness', 'sld', 'isld', 'scale'. - :type kind: str + Parameters + ---------- + parameter : Parameter + The parameter to adjust. + kind : str + One of 'thickness', 'roughness', 'sld', 'isld', 'scale'. """ if not parameter.independent: return diff --git a/src/easyreflectometry/main.py b/src/easyreflectometry/main.py index 043d72c1..2f7737e0 100644 --- a/src/easyreflectometry/main.py +++ b/src/easyreflectometry/main.py @@ -1,7 +1,11 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from easyreflectometry.calculators import CalculatorFactory def main(): + """Main function.""" factory = CalculatorFactory() print(f'Available calculators: {factory.available_interfaces}') diff --git a/src/easyreflectometry/model/__init__.py b/src/easyreflectometry/model/__init__.py index 6246b2d8..698b5a0c 100644 --- a/src/easyreflectometry/model/__init__.py +++ b/src/easyreflectometry/model/__init__.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from .model import Model from .model_collection import ModelCollection from .resolution_functions import LinearSpline diff --git a/src/easyreflectometry/model/model.py b/src/easyreflectometry/model/model.py index 7f651fa2..b1aedf85 100644 --- a/src/easyreflectometry/model/model.py +++ b/src/easyreflectometry/model/model.py @@ -1,6 +1,7 @@ -from __future__ import annotations +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause -__author__ = 'github.com/arm61' +from __future__ import annotations import copy from numbers import Number @@ -43,11 +44,23 @@ }, } -COLORS = ['#0173B2', '#DE8F05', '#029E73', '#D55E00', '#CC78BC', '#CA9161', '#FBAFE4', '#949494', '#ECE133', '#56B4E9'] +COLORS = [ + '#0173B2', + '#DE8F05', + '#029E73', + '#D55E00', + '#CC78BC', + '#CA9161', + '#FBAFE4', + '#949494', + '#ECE133', + '#56B4E9', +] class Model(BaseObj): """Model is the class that represents the experiment. + It is used to store the information about the experiment and to perform the calculations. """ @@ -70,13 +83,24 @@ def __init__( ): """Constructor. - :param sample: The sample being modelled. - :param scale: Scaling factor of profile. - :param background: Linear background magnitude. - :param name: Name of the model, defaults to 'Model'. - :param resolution_function: Resolution function, defaults to PercentageFwhm. - :param interface: Calculator interface, defaults to `None`. - + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + color : str, optional + By default, COLORS[0]. + sample : Union[Sample, None], optional + The sample being modelled. By default, None. + scale : Union[Parameter, Number, None], optional + Scaling factor of profile. By default, None. + background : Union[Parameter, Number, None], optional + Linear background magnitude. By default, None. + name : str, optional + Name of the model. By default, 'Model'. + resolution_function : Union[ResolutionFunction, None], optional + Resolution function. By default, None. + interface : + Calculator interface. By default, None. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) @@ -107,7 +131,10 @@ def __init__( def add_assemblies(self, *assemblies: list[BaseAssembly]) -> None: """Add assemblies to the model sample. - :param assemblies: Assemblies to add to model sample. + Parameters + ---------- + *assemblies : list[BaseAssembly] + Assemblies to add to model sample. """ if not assemblies: self.sample.add_assembly() @@ -125,7 +152,11 @@ def add_assemblies(self, *assemblies: list[BaseAssembly]) -> None: def duplicate_assembly(self, index: int) -> None: """Duplicate a given item or layer in a sample. - :param idx: Index of the item or layer to duplicate + Parameters + ---------- + index : int + idx : + Index of the item or layer to duplicate. """ self.sample.duplicate_assembly(index) if self.interface is not None: @@ -134,7 +165,11 @@ def duplicate_assembly(self, index: int) -> None: def remove_assembly(self, index: int) -> None: """Remove an assembly from the model. - :param idx: Index of the item to remove. + Parameters + ---------- + index : int + idx : + Index of the item to remove. """ assembly_unique_name = self.sample[index].unique_name self.sample.remove_assembly(index) @@ -150,8 +185,10 @@ def is_default(self) -> bool: def is_default(self, value: bool) -> None: """Set whether this model is a default placeholder. - :param value: True if the model is a default placeholder. - :type value: bool + Parameters + ---------- + value : bool + True if the model is a default placeholder. """ self._is_default = value @@ -169,9 +206,7 @@ def resolution_function(self, resolution_function: ResolutionFunction) -> None: @property def interface(self): - """ - Get the current interface of the object - """ + """Get the current interface of the object.""" return self._interface @interface.setter @@ -209,9 +244,13 @@ def __repr__(self) -> str: def as_dict(self, skip: Optional[list[str]] = None) -> dict: """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : Optional[list[str]], optional + List of keys to skip. By default, None. """ if skip is None: skip = [] @@ -233,12 +272,7 @@ def as_orso(self) -> dict: @classmethod def from_dict(cls, passed_dict: dict) -> Model: - """ - Create a Model from a dictionary. - - :param this_dict: dictionary of the Model - :return: Model - """ + """Create a Model from a dictionary.""" # Causes circular import if imported at the top from easyreflectometry.calculators import CalculatorFactory diff --git a/src/easyreflectometry/model/model_collection.py b/src/easyreflectometry/model/model_collection.py index b3c0bd2d..1a1b7e88 100644 --- a/src/easyreflectometry/model/model_collection.py +++ b/src/easyreflectometry/model/model_collection.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from __future__ import annotations from typing import List @@ -12,6 +15,7 @@ # Needs to be a function, elements are added to the global_object.map def DEFAULT_ELEMENTS(interface): + """Default elements.""" return (Model(interface),) @@ -26,6 +30,7 @@ def __init__( next_color_index: Optional[int] = None, **kwargs, ): + """Init function.""" if not models: if populate_if_none: models = DEFAULT_ELEMENTS(interface) @@ -49,7 +54,10 @@ def __init__( def add_model(self, model: Optional[Model] = None): """Add a model to the collection. - :param model: Model to add. + Parameters + ---------- + model : Optional[Model], optional + Model to add. By default, None. """ if model is None: model = Model(name='Model', interface=self.interface, color=self._current_color()) @@ -58,7 +66,10 @@ def add_model(self, model: Optional[Model] = None): def duplicate_model(self, index: int): """Duplicate a model in the collection. - :param index: Model to duplicate. + Parameters + ---------- + index : int + Model to duplicate. """ to_be_duplicated = self[index] duplicate = Model.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) @@ -66,6 +77,7 @@ def duplicate_model(self, index: int): self.append(duplicate) def as_dict(self, skip: List[str] | None = None) -> dict: + """As dict.""" this_dict = super().as_dict(skip=skip) this_dict['populate_if_none'] = self.populate_if_none this_dict['next_color_index'] = self._next_color_index @@ -73,11 +85,7 @@ def as_dict(self, skip: List[str] | None = None) -> dict: @classmethod def from_dict(cls, this_dict: dict) -> ModelCollection: - """ - Create an instance of a collection from a dictionary. - - :param data: The dictionary for the collection - """ + """Create an instance of a collection from a dictionary.""" collection_dict = this_dict.copy() # We need to call from_dict on the base class to get the models dict_data = collection_dict.pop('data') @@ -102,14 +110,17 @@ def from_dict(cls, this_dict: dict) -> ModelCollection: return collection def append(self, model: Model) -> None: # type: ignore[override] + """Append function.""" self._append_internal(model, advance=True) def _append_internal(self, model: Model, advance: bool) -> None: + """Append internal.""" super().append(model) if advance: self._advance_color_index() def _advance_color_index(self) -> None: + """Advance color index.""" if not COLORS: self._next_color_index = 0 return @@ -119,6 +130,7 @@ def _advance_color_index(self) -> None: self._next_color_index = (self._next_color_index + 1) % len(COLORS) def _current_color(self) -> str: + """Current color.""" if not COLORS: raise ValueError('No colors defined for models.') if self._next_color_index is None: diff --git a/src/easyreflectometry/model/resolution_functions.py b/src/easyreflectometry/model/resolution_functions.py index 2a6e5c8c..ee0933e2 100644 --- a/src/easyreflectometry/model/resolution_functions.py +++ b/src/easyreflectometry/model/resolution_functions.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """Resolution functions for the resolution of the experiment. When a percentage is provided we assume that the resolution is a Gaussian distribution with a FWHM of the percentage of the q value. @@ -26,51 +29,68 @@ def as_dict(self, skip: Optional[List[str]] = None) -> dict: ... @classmethod def from_dict(cls, data: dict) -> ResolutionFunction: + """Smearing function.""" if data['smearing'] == 'PercentageFwhm': return PercentageFwhm(data['constant']) if data['smearing'] == 'LinearSpline': return LinearSpline(data['q_data_points'], data['fwhm_values']) if data['smearing'] == 'Pointwise': - return Pointwise([data['q_data_points'], data['R_data_points'], data['sQz_data_points']]) + return Pointwise([ + data['q_data_points'], + data['R_data_points'], + data['sQz_data_points'], + ]) raise ValueError('Unknown resolution function type') class PercentageFwhm(ResolutionFunction): def __init__(self, constant: Union[None, float] = None): + """Init function.""" if constant is None: constant = DEFAULT_RESOLUTION_FWHM_PERCENTAGE self.constant = constant def smearing(self, q: Union[np.array, float]) -> np.array: + """Smearing function.""" return np.ones(np.array(q).size) * self.constant def as_dict( self, skip: Optional[List[str]] = None ) -> dict[str, str]: # skip is kept for consistency of the as_dict signature + """As dict.""" return {'smearing': 'PercentageFwhm', 'constant': self.constant} class LinearSpline(ResolutionFunction): def __init__(self, q_data_points: np.array, fwhm_values: np.array): + """Init function.""" self.q_data_points = q_data_points self.fwhm_values = fwhm_values def smearing(self, q: Union[np.array, float]) -> np.array: + """Smearing function.""" return np.interp(q, self.q_data_points, self.fwhm_values) def as_dict( self, skip: Optional[List[str]] = None ) -> dict[str, str]: # skip is kept for consistency of the as_dict signature - return {'smearing': 'LinearSpline', 'q_data_points': list(self.q_data_points), 'fwhm_values': list(self.fwhm_values)} + """As dict.""" + return { + 'smearing': 'LinearSpline', + 'q_data_points': list(self.q_data_points), + 'fwhm_values': list(self.fwhm_values), + } # add pointwise smearing funtion class Pointwise(ResolutionFunction): def __init__(self, q_data_points: list[np.ndarray]): + """Init function.""" self.q_data_points = q_data_points self.q = None def smearing(self, q: Union[np.ndarray, float] = None) -> np.ndarray: + """Smearing function.""" Qz = self.q_data_points[0] R = self.q_data_points[1] sQz = self.q_data_points[2] @@ -87,6 +107,7 @@ def smearing(self, q: Union[np.ndarray, float] = None) -> np.ndarray: def as_dict( self, skip: Optional[List[str]] = None ) -> dict[str, str]: # skip is kept for consistency of the as_dict signature + """As dict.""" return { 'smearing': 'Pointwise', 'q_data_points': list(self.q_data_points[0]), @@ -95,6 +116,7 @@ def as_dict( } def gaussian_smearing(self, qt, Qz, R, sQz): + """Gaussian smearing.""" weights = np.exp(-0.5 * ((qt - Qz) / sQz) ** 2) if np.sum(weights) == 0 or not np.isfinite(np.sum(weights)): return np.sum(R) @@ -102,9 +124,7 @@ def gaussian_smearing(self, qt, Qz, R, sQz): return np.sum(R * weights) / np.sum(weights) def apply_smooth_smearing(self, Qz, R, sQzs): - """ - Apply smooth resolution smearing using convolution with Gaussian kernel. - """ + """Apply smooth resolution smearing using convolution with Gaussian kernel.""" if self.q is None: R_smeared = np.zeros_like(Qz) else: diff --git a/src/easyreflectometry/orso_utils.py b/src/easyreflectometry/orso_utils.py index 494ed248..aa320933 100644 --- a/src/easyreflectometry/orso_utils.py +++ b/src/easyreflectometry/orso_utils.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import logging import warnings @@ -49,8 +52,7 @@ def load_data_from_orso_file(fname: str) -> sc.DataGroup: def load_orso_model(orso_data) -> Sample: - """ - Load a model from an ORSO file and return a Sample object. + """Load a model from an ORSO file and return a Sample object. The ORSO file .ort contains information about the sample, saved as a simple "stack" string, e.g. 'air | m1 | SiO2 | Si'. @@ -61,11 +63,20 @@ def load_orso_model(orso_data) -> Sample: - Middle layers -> 'Loaded layer' Multilayer assembly (parameters enabled) - Last layer -> Subphase assembly (thickness=0 fixed, roughness enabled) - :param orso_data: Parsed ORSO dataset list (as returned by ``orso.load_orso``). - :type orso_data: list - :return: An EasyReflectometry Sample object. - :rtype: Sample - :raises ValueError: If ORSO layers could not be resolved or fewer than 2 layers. + Parameters + ---------- + orso_data : list + Parsed ORSO dataset list (as returned by ``orso.load_orso``). + + Raises + ------ + ValueError : + If ORSO layers could not be resolved or fewer than 2 layers. + + Returns + ------- + Sample + An EasyReflectometry Sample object. """ # Extract stack string and layer definitions from ORSO sample model sample_model = orso_data[0].info.data_source.sample.model @@ -134,7 +145,7 @@ def load_orso_model(orso_data) -> Sample: def _convert_orso_layer_to_erl(layer): - """Helper function to convert an ORSO layer to an EasyReflectometry layer""" + r"""Helper function to convert an ORSO layer to an EasyReflectometry laye.""" material = layer.material # Prefer original_name for material name, fall back to formula if available m_name = layer.original_name if layer.original_name is not None else material.formula @@ -157,7 +168,7 @@ def _get_sld_values(material, material_name): Note: ORSO stores SLD in absolute units (A^-2), but the internal representation uses 10^-6 A^-2. When reading directly from ORSO, we multiply by 1e6 to convert. - When calculating from mass density, MaterialDensity already returns the correct units. + When calculating from mass density, MaterialDensity already returns the correct units.. """ if material.sld is None and material.mass_density is not None: # Calculate SLD from mass density @@ -196,10 +207,15 @@ def _get_sld_values(material, material_name): def load_orso_data(orso_data) -> DataSet1D: """Convert parsed ORSO dataset objects into a scipp DataGroup. - :param orso_data: Parsed ORSO dataset list (as returned by ``orso.load_orso``). - :type orso_data: list - :return: A scipp DataGroup with data, coords, and attrs. - :rtype: sc.DataGroup + Parameters + ---------- + orso_data : list + Parsed ORSO dataset list (as returned by ``orso.load_orso``). + + Returns + ------- + sc.DataGroup + A scipp DataGroup with data, coords, and attrs. """ data = {} coords = {} diff --git a/src/easyreflectometry/plot.py b/src/easyreflectometry/plot.py index a88a89bf..3a0aaa26 100644 --- a/src/easyreflectometry/plot.py +++ b/src/easyreflectometry/plot.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import matplotlib.pyplot as plt import scipp as sc @@ -8,10 +10,12 @@ def plot(data: sc.DataGroup) -> None: - """ - A general plotting function for easyreflectometry. + """A general plotting function for easyreflectometry. - :param data: the DataGroup to be plotted. + Parameters + ---------- + data : sc.DataGroup + The DataGroup to be plotted. """ if len([i for i in list(data.keys()) if 'SLD' in i]) == 0: plot_sld = False @@ -41,7 +45,14 @@ def plot(data: sc.DataGroup) -> None: ) plot_model_data.data *= sc.scalar(10.0**i, unit=plot_model_data.unit) plot_model_data.coords[f'Qz_{refl_num}'].variances = None - sc.plot(plot_model_data, ax=ax1, norm='log', linestyle='--', color=color_cycle[i], marker='') + sc.plot( + plot_model_data, + ax=ax1, + norm='log', + linestyle='--', + color=color_cycle[i], + marker='', + ) except KeyError: pass ax1.autoscale(True) diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 7e1ab3c9..5127ad16 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import datetime import json import logging @@ -43,6 +46,7 @@ class Project: def __init__(self): + """Init function.""" self._info = self._default_info() self._path_project_parent = Path(os.path.expanduser('~')) self._models = ModelCollection(populate_if_none=False, unique_name='project_models') @@ -68,6 +72,7 @@ def __init__(self): self._with_experiments = False def reset(self): + """Reset function.""" del self._models del self._materials global_object.map._clear() @@ -140,40 +145,48 @@ def _sync_layer_parameter_state(self, parameter: Parameter, kind: str, disabled_ @property def q_min(self): + """Q min.""" if self._q_min is None: return Q_MIN return self._q_min @q_min.setter def q_min(self, value: float) -> None: + """Q min.""" self._q_min = value @property def q_max(self): + """Q max.""" if self._q_max is None: return Q_MAX return self._q_max @q_max.setter def q_max(self, value: float) -> None: + """Q max.""" self._q_max = value @property def q_resolution(self): + """Q resolution.""" if self._q_resolution is None: return Q_RESOLUTION return self._q_resolution @q_resolution.setter def q_resolution(self, value: int) -> None: + """Q resolution.""" self._q_resolution = value @property def current_material_index(self) -> Optional[int]: + """Current material index.""" return self._current_material_index @current_material_index.setter def current_material_index(self, value: int) -> None: + """Current material index.""" if value < 0 or value >= len(self._materials): raise ValueError(f'Index {value} out of range') if self._current_material_index != value: @@ -181,10 +194,12 @@ def current_material_index(self, value: int) -> None: @property def current_model_index(self) -> Optional[int]: + """Current model index.""" return self._current_model_index @current_model_index.setter def current_model_index(self, value: int) -> None: + """Current model index.""" if value < 0 or value >= len(self._models): raise ValueError(f'Index {value} out of range') if self._current_model_index != value: @@ -194,10 +209,12 @@ def current_model_index(self, value: int) -> None: @property def current_assembly_index(self) -> Optional[int]: + """Current assembly index.""" return self._current_assembly_index @current_assembly_index.setter def current_assembly_index(self, value: int) -> None: + """Current assembly index.""" if value < 0 or value >= len(self._models[self._current_model_index].sample): raise ValueError(f'Index {value} out of range') if self._current_assembly_index != value: @@ -206,10 +223,12 @@ def current_assembly_index(self, value: int) -> None: @property def current_layer_index(self) -> Optional[int]: + """Current layer index.""" return self._current_layer_index @current_layer_index.setter def current_layer_index(self, value: int) -> None: + """Current layer index.""" if value < 0 or value >= len(self._models[self._current_model_index].sample[self._current_assembly_index].layers): raise ValueError(f'Index {value} out of range') if self._current_layer_index != value: @@ -217,10 +236,12 @@ def current_layer_index(self, value: int) -> None: @property def current_experiment_index(self) -> Optional[int]: + """Current experiment index.""" return self._current_experiment_index @current_experiment_index.setter def current_experiment_index(self, value: int) -> None: + """Current experiment index.""" if value < 0 or value >= len(self._experiments): raise ValueError(f'Index {value} out of range') if self._current_experiment_index != value: @@ -230,21 +251,26 @@ def current_experiment_index(self, value: int) -> None: @property def created(self) -> bool: + """Created function.""" return self._created @property def path(self): + """Path function.""" return self._path_project_parent / self._info['name'] def set_path_project_parent(self, path: Union[Path, str]): + """Set path project parent.""" self._path_project_parent = Path(path) @property def models(self) -> ModelCollection: + """Models function.""" return self._models @models.setter def models(self, models: ModelCollection) -> None: + """Models function.""" self._replace_collection(models, self._models) # Use setter to update indicies for current model, assembly and layer self.current_model_index = 0 @@ -255,6 +281,7 @@ def models(self, models: ModelCollection) -> None: @property def fitter(self) -> MultiFitter: + """Fitter function.""" if len(self._models): if (self._fitter is None) or (self._fitter_model_index != self._current_model_index): self._fitter = MultiFitter(self._models[self._current_model_index]) @@ -264,10 +291,12 @@ def fitter(self) -> MultiFitter: @property def calculator(self) -> str: + """Calculator function.""" return self._calculator.current_interface_name @calculator.setter def calculator(self, calculator: str) -> None: + """Calculator function.""" if calculator == self._calculator.current_interface_name: return @@ -282,47 +311,61 @@ def calculator(self, calculator: str) -> None: @property def minimizer(self) -> AvailableMinimizers: + """Minimizer function.""" if self._fitter is not None: return self._fitter.easy_science_multi_fitter.minimizer.enum return self._minimizer_selection @minimizer.setter def minimizer(self, minimizer: AvailableMinimizers) -> None: + """Minimizer function.""" old_name = getattr(self._minimizer_selection, 'name', str(self._minimizer_selection)) new_name = getattr(minimizer, 'name', str(minimizer)) - logger.info('Minimizer changed from %s to %s (fitter active: %s)', old_name, new_name, self._fitter is not None) + logger.info( + 'Minimizer changed from %s to %s (fitter active: %s)', + old_name, + new_name, + self._fitter is not None, + ) self._minimizer_selection = minimizer if self._fitter is not None: self._fitter.easy_science_multi_fitter.switch_minimizer(minimizer) @property def experiments(self) -> Dict[int, DataSet1D]: + """Experiments function.""" return self._experiments @experiments.setter def experiments(self, experiments: Dict[int, DataSet1D]) -> None: + """Experiments function.""" self._experiments = experiments @property def path_json(self): + """Path json.""" return self.path / 'project.json' def get_index_air(self) -> int: + """Get index air.""" if 'Air' not in [material.name for material in self._materials]: self._materials.add_material(Material(name='Air', sld=0.0, isld=0.0)) return [material.name for material in self._materials].index('Air') def get_index_si(self) -> int: + """Get index si.""" if 'Si' not in [material.name for material in self._materials]: self._materials.add_material(Material(name='Si', sld=2.07, isld=0.0)) return [material.name for material in self._materials].index('Si') def get_index_sio2(self) -> int: + """Get index sio2.""" if 'SiO2' not in [material.name for material in self._materials]: self._materials.add_material(Material(name='SiO2', sld=3.47, isld=0.0)) return [material.name for material in self._materials].index('SiO2') def get_index_d2o(self) -> int: + """Get index d2o.""" if 'D2O' not in [material.name for material in self._materials]: self._materials.add_material(Material(name='D2O', sld=6.36, isld=0.0)) return [material.name for material in self._materials].index('D2O') @@ -351,10 +394,15 @@ def set_sample_from_orso(self, sample: Sample) -> None: This is a convenience helper for the ORSO import pipeline where a complete :class:`~easyreflectometry.sample.Sample` is constructed elsewhere. - :param sample: Sample to set as the project's (single) model. - :type sample: easyreflectometry.sample.Sample - :return: ``None``. - :rtype: None + Parameters + ---------- + sample : Sample + Sample to set as the project's (single) model. + + Returns + ------- + None + ``None``. """ model = Model(sample=sample) self.models = ModelCollection([model]) @@ -369,10 +417,15 @@ def add_sample_from_orso(self, sample: Sample) -> None: After adding the model, :attr:`current_model_index` is updated to point to the newly added model. - :param sample: Sample to add as a new model. - :type sample: easyreflectometry.sample.Sample - :return: ``None``. - :rtype: None + Parameters + ---------- + sample : Sample + Sample to add as a new model. + + Returns + ------- + None + ``None``. """ if sample is None: raise ValueError('The ORSO file does not contain a valid sample model definition.') @@ -393,10 +446,15 @@ def replace_models_from_orso(self, sample: Sample) -> None: model is created from *sample*, assigned to the project's calculator, and the material collection is rebuilt from the new model only. - :param sample: Sample to set as the project's only model. - :type sample: easyreflectometry.sample.Sample - :return: ``None``. - :rtype: None + Parameters + ---------- + sample : Sample + Sample to set as the project's only model. + + Returns + ------- + None + ``None``. """ if sample is None: raise ValueError('The ORSO file does not contain a valid sample model definition.') @@ -427,11 +485,18 @@ def _apply_experiment_metadata( ) -> None: """Set experiment name from ORSO title and configure the resolution function. - :param path: Path to the experiment data file. - :param experiment: The loaded experiment dataset to configure. - :param fallback_name: Name to use when no ORSO title is available. - :param data_group: Pre-loaded scipp DataGroup (avoids reloading the file). - :param data_key: Specific dataset key to use for title extraction (e.g. ``'R_1'``). + Parameters + ---------- + path : Union[Path, str] + Path to the experiment data file. + experiment : DataSet1D + The loaded experiment dataset to configure. + fallback_name : str + Name to use when no ORSO title is available. + data_group : + Pre-loaded scipp DataGroup (avoids reloading the file). By default, None. + data_key : Optional[str], optional + Specific dataset key to use for title extraction (e.g. ``'R_1'``). By default, None. """ # Prefer ORSO title when available (keeps UI descriptive) title = None @@ -456,8 +521,12 @@ def _apply_resolution_function( ) -> None: """Set the resolution function on *model* based on variance data in *experiment*. - :param experiment: The experiment whose variance data drives the choice. - :param model: The model whose resolution function is set. + Parameters + ---------- + experiment : DataSet1D + The experiment whose variance data drives the choice. + model : Model + The model whose resolution function is set. """ model.resolution_function = PercentageFwhm(5.0) @@ -468,6 +537,7 @@ def _auto_set_background(experiment: DataSet1D) -> None: experiment.model.background = max(np.min(experiment.y), 1e-10) def load_new_experiment(self, path: Union[Path, str]) -> None: + """Load new experiment.""" new_experiment = load_as_dataset(str(path)) new_index = len(self._experiments) @@ -485,8 +555,15 @@ def load_new_experiment(self, path: Union[Path, str]) -> None: def count_datasets_in_file(self, path: Union[Path, str]) -> int: """Return the number of datasets contained in the file at *path*. - :param path: Path to the data file. - :return: Number of datasets found; 1 if the file cannot be introspected. + Parameters + ---------- + path : Union[Path, str] + Path to the data file. + + Returns + ------- + int + Number of datasets found; 1 if the file cannot be introspected. """ try: data_group = load_data_from_orso_file(str(path)) @@ -502,8 +579,15 @@ def load_all_experiments_from_file(self, path: Union[Path, str]) -> int: currently selected. Falls back to :meth:`load_new_experiment` for single-dataset files or on any loading error. - :param path: Path to the data file. - :return: Number of experiments that were added. + Parameters + ---------- + path : Union[Path, str] + Path to the data file. + + Returns + ------- + int + Number of experiments that were added. """ try: data_group = load_data_from_orso_file(str(path)) @@ -547,6 +631,7 @@ def load_all_experiments_from_file(self, path: Union[Path, str]) -> int: return len(data_keys) def load_experiment_for_model_at_index(self, path: Union[Path, str], index: Optional[int] = 0) -> None: + """Load experiment for model at index.""" experiment = load_as_dataset(str(path)) self._apply_experiment_metadata(path, experiment, f'Experiment {index}') @@ -557,6 +642,7 @@ def load_experiment_for_model_at_index(self, path: Union[Path, str], index: Opti self._apply_resolution_function(experiment, self._models[index]) def sld_data_for_model_at_index(self, index: int = 0) -> DataSet1D: + """Sld data for model at index.""" self.models[index].interface = self._calculator sld = self.models[index].interface().sld_profile(self._models[index].unique_name) return DataSet1D( @@ -566,6 +652,7 @@ def sld_data_for_model_at_index(self, index: int = 0) -> DataSet1D: ) def sample_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.array] = None) -> DataSet1D: + """Sample data for model at index.""" original_resolution_function = self.models[index].resolution_function self.models[index].resolution_function = PercentageFwhm(0) reflectivity_data = self.model_data_for_model_at_index(index, q_range) @@ -574,6 +661,7 @@ def sample_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.ar return reflectivity_data def model_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.array] = None) -> DataSet1D: + """Model data for model at index.""" if q_range is None: q_range = np.linspace(self.q_min, self.q_max, self.q_resolution) self.models[index].interface = self._calculator @@ -585,18 +673,38 @@ def model_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.arr ) def experimental_data_for_model_at_index(self, index: int = 0) -> DataSet1D: + """Experimental data for model at index.""" if index in self._experiments.keys(): return self._experiments[index] else: raise IndexError(f'No experiment data for model at index {index}') def default_model(self): + """Default model.""" self._replace_collection(MaterialCollection(interface=self._calculator), self._materials) layers = [ - Layer(material=self._materials[0], thickness=0.0, roughness=0.0, name='Vacuum Layer', interface=self._calculator), - Layer(material=self._materials[1], thickness=100.0, roughness=3.0, name='D2O Layer', interface=self._calculator), - Layer(material=self._materials[2], thickness=0.0, roughness=1.2, name='Si Layer', interface=self._calculator), + Layer( + material=self._materials[0], + thickness=0.0, + roughness=0.0, + name='Vacuum Layer', + interface=self._calculator, + ), + Layer( + material=self._materials[1], + thickness=100.0, + roughness=3.0, + name='D2O Layer', + interface=self._calculator, + ), + Layer( + material=self._materials[2], + thickness=0.0, + roughness=1.2, + name='Si Layer', + interface=self._calculator, + ), ] assemblies = [ Multilayer(layers[0], name='Superphase', interface=self._calculator), @@ -611,10 +719,15 @@ def default_model(self): def is_default_model(self, index: int) -> bool: """Check if the model at the given index is a default model. - :param index: Index of the model to check. - :type index: int - :return: True if the model was created as a default placeholder. - :rtype: bool + Parameters + ---------- + index : int + Index of the model to check. + + Returns + ------- + bool + True if the model was created as a default placeholder. """ if index < 0 or index >= len(self._models): return False @@ -630,10 +743,17 @@ def remove_model_at_index(self, index: int) -> None: Adjusts the current model index if necessary. - :param index: Index of the model to remove. - :type index: int - :raises IndexError: If the index is out of range. - :raises ValueError: If trying to remove the last remaining model. + Parameters + ---------- + index : int + Index of the model to remove. + + Raises + ------ + IndexError : + If the index is out of range. + ValueError : + If trying to remove the last remaining model. """ if index < 0 or index >= len(self._models): raise IndexError(f'Model index {index} out of range') @@ -668,18 +788,21 @@ def remove_model_at_index(self, index: int) -> None: self._current_layer_index = 0 def add_material(self, material: MaterialCollection) -> None: + """Add material.""" if material in self._materials: print(f'WARNING: Material {material} is already in material collection') else: self._materials.append(material) def remove_material(self, index: int) -> None: + """Remove material.""" if self._materials[index] in self._get_materials_in_models(): print(f'ERROR: Material {self._materials[index]} is used in models') else: self._materials.pop(index) def _default_info(self): + """Default info.""" return dict( name='DefaultEasyReflectometryProject', short_description='Reflectometry, 1D', @@ -687,6 +810,7 @@ def _default_info(self): ) def create(self): + """Create function.""" if not os.path.exists(self.path): os.makedirs(self.path) os.makedirs(self.path / 'experiments') @@ -696,6 +820,7 @@ def create(self): print(f'ERROR: Directory {self.path} already exists') def save_as_json(self, overwrite=False): + """Save as json.""" if self.path_json.exists() and overwrite: print(f'File already exists {self.path_json}. Overwriting...') self.path_json.unlink() @@ -708,6 +833,7 @@ def save_as_json(self, overwrite=False): print(exception) def load_from_json(self, path: Optional[Union[Path, str]] = None): + """Load from json.""" if path is None: path = self.path_json path = Path(path) @@ -722,6 +848,7 @@ def load_from_json(self, path: Optional[Union[Path, str]] = None): print(f'ERROR: File {path} does not exist') def as_dict(self, include_materials_not_in_model=False): + """As dict.""" project_dict = {} project_dict['info'] = self._info project_dict['with_experiments'] = self._with_experiments @@ -743,6 +870,7 @@ def as_dict(self, include_materials_not_in_model=False): return project_dict def _as_dict_add_materials_not_in_model_dict(self, project_dict: dict): + """As dict add materials not in model dict.""" materials_not_in_model = [] for material in self._materials: if material not in self._get_materials_in_models(): @@ -751,18 +879,24 @@ def _as_dict_add_materials_not_in_model_dict(self, project_dict: dict): project_dict['materials_not_in_model'] = MaterialCollection(materials_not_in_model).as_dict(skip=['interface']) def _as_dict_add_experiments(self, project_dict: dict): + """As dict add experiments.""" project_dict['experiments'] = {} project_dict['experiments_models'] = {} project_dict['experiments_names'] = {} for key, experiment in self._experiments.items(): - project_dict['experiments'][key] = [list(experiment.x), list(experiment.y), list(experiment.ye)] + project_dict['experiments'][key] = [ + list(experiment.x), + list(experiment.y), + list(experiment.ye), + ] if experiment.xe is not None: project_dict['experiments'][key].append(list(experiment.xe)) project_dict['experiments_models'][key] = experiment.model.name project_dict['experiments_names'][key] = experiment.name def from_dict(self, project_dict: dict): + """From dict.""" keys = list(project_dict.keys()) self._info = project_dict['info'] self._with_experiments = project_dict['with_experiments'] @@ -786,6 +920,7 @@ def from_dict(self, project_dict: dict): resolve_all_parameter_dependencies(self) def _from_dict_extract_experiments(self, project_dict: dict) -> Dict[int, DataSet1D]: + """From dict extract experiments.""" experiments = {} for key in project_dict['experiments'].keys(): experiments[int(key)] = DataSet1D( @@ -800,6 +935,7 @@ def _from_dict_extract_experiments(self, project_dict: dict) -> Dict[int, DataSe return experiments def _get_materials_in_models(self) -> MaterialCollection: + """Get materials in models.""" materials_in_model = MaterialCollection(populate_if_none=False) for model in self._models: for assembly in model.sample: @@ -808,6 +944,7 @@ def _get_materials_in_models(self) -> MaterialCollection: return materials_in_model def _replace_collection(self, src_collection: BaseCollection, dst_collection: BaseCollection) -> None: + """Replace collection.""" # Clear the destination collection for i in range(len(dst_collection)): dst_collection.pop(0) @@ -816,4 +953,5 @@ def _replace_collection(self, src_collection: BaseCollection, dst_collection: Ba dst_collection.append(element) def _timestamp_modification(self): + """Timestamp modification.""" self._info['modified'] = datetime.datetime.now().strftime('%d.%m.%Y %H:%M') diff --git a/src/easyreflectometry/sample/__init__.py b/src/easyreflectometry/sample/__init__.py index 4991b975..e6f347ad 100644 --- a/src/easyreflectometry/sample/__init__.py +++ b/src/easyreflectometry/sample/__init__.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from .assemblies.base_assembly import BaseAssembly from .assemblies.bilayer import Bilayer from .assemblies.gradient_layer import GradientLayer diff --git a/src/easyreflectometry/sample/assemblies/__init__.py b/src/easyreflectometry/sample/assemblies/__init__.py new file mode 100644 index 00000000..4e798e20 --- /dev/null +++ b/src/easyreflectometry/sample/assemblies/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easyreflectometry/sample/assemblies/base_assembly.py b/src/easyreflectometry/sample/assemblies/base_assembly.py index 68486805..39cb2b18 100644 --- a/src/easyreflectometry/sample/assemblies/base_assembly.py +++ b/src/easyreflectometry/sample/assemblies/base_assembly.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Any from typing import Optional @@ -8,6 +11,7 @@ class BaseAssembly(BaseCore): """Assembly of layers. + The front layer (front_layer) is the layer the neutron beam starts in, it has an index of 0. The back layer (back_layer) is the final layer from which the unreflected neutron beam is transmitted, its index number depends on the number of finite layers in the system, but it might be accessed at index -1. @@ -38,6 +42,7 @@ def __init__( @property def type(self) -> str: """Get type of the assembly. + Needed by the GUI. """ return self._type @@ -53,7 +58,10 @@ def front_layer(self) -> Optional[Layer]: def front_layer(self, layer: Layer) -> None: """Set the front layer in the assembly. - :param layer: Layer to set as the front layer. + Parameters + ---------- + layer : Layer + Layer to set as the front layer. """ if len(self.layers) == 0: self.layers.append(layer) @@ -72,7 +80,10 @@ def back_layer(self) -> Optional[Layer]: def back_layer(self, layer: Layer) -> None: """Set the back layer in the assembly. - :param layer: Layer to set as the back layer. + Parameters + ---------- + layer : Layer + Layer to set as the back layer. """ if len(self.layers) == 0: @@ -83,18 +94,14 @@ def back_layer(self, layer: Layer) -> None: self.layers[-1] = layer def _setup_thickness_constraints(self) -> None: - """ - Setup thickness constraint, front layer is the deciding layer - """ + """Setup thickness constraint, front layer is the deciding layer.""" independent_param = self.front_layer.thickness for i in range(1, len(self.layers)): self.layers[i].thickness.make_dependent_on(dependency_expression='a', dependency_map={'a': independent_param}) self._thickness_constraints_setup = True def _enable_thickness_constraints(self): - """ - Enable the thickness constraint. - """ + """Enable the thickness constraint.""" if self._thickness_constraints_setup: # Make sure that the thickness constraint is enabled self._setup_thickness_constraints() @@ -103,9 +110,7 @@ def _enable_thickness_constraints(self): raise Exception('Thickness constraints not setup') def _disable_thickness_constraints(self): - """ - Disable the thickness constraint. - """ + """Disable the thickness constraint.""" if self._thickness_constraints_setup: for i in range(1, len(self.layers)): self.layers[i].thickness.make_independent() @@ -113,25 +118,19 @@ def _disable_thickness_constraints(self): raise Exception('Thickness constraints not setup') def _setup_roughness_constraints(self) -> None: - """ - Setup roughness constraint, front layer is the deciding layer - """ + """Setup roughness constraint, front layer is the deciding layer.""" independent_parameter = self.front_layer.roughness for i in range(1, len(self.layers)): self.layers[i].roughness.make_dependent_on(dependency_expression='a', dependency_map={'a': independent_parameter}) self._roughness_constraints_setup = True def _enable_roughness_constraints(self): - """ - Enable the roughness constraint. - """ + """Enable the roughness constraint.""" independent_parameter = self.front_layer.roughness for i in range(1, len(self.layers)): self.layers[i].roughness.make_dependent_on(dependency_expression='a', dependency_map={'a': independent_parameter}) def _disable_roughness_constraints(self): - """ - Disable the roughness constraint. - """ + """Disable the roughness constraint.""" for i in range(1, len(self.layers)): self.layers[i].roughness.make_independent() diff --git a/src/easyreflectometry/sample/assemblies/bilayer.py b/src/easyreflectometry/sample/assemblies/bilayer.py index 21c428f6..5409d359 100644 --- a/src/easyreflectometry/sample/assemblies/bilayer.py +++ b/src/easyreflectometry/sample/assemblies/bilayer.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from __future__ import annotations from typing import Any @@ -64,19 +67,29 @@ def __init__( ): """Constructor. - :param front_head_layer: Layer representing the front head part of the bilayer. - :param front_tail_layer: Layer representing the front tail part of the bilayer. + Parameters + ---------- + front_head_layer : LayerAreaPerMolecule | None, optional + Layer representing the front head part of the bilayer. By default, None. + front_tail_layer : LayerAreaPerMolecule | None, optional + Layer representing the front tail part of the bilayer. A back tail layer is created internally with its thickness, area per molecule, - and solvent fraction constrained to match this layer. - :param back_head_layer: Layer representing the back head part of the bilayer. - :param name: Name for bilayer, defaults to 'EasyBilayer'. - :param unique_name: Unique name for internal object tracking, defaults to `None`. - :param constrain_heads: When `True`, the back head layer thickness and area per + and solvent fraction constrained to match this layer. By default, None. + back_head_layer : LayerAreaPerMolecule | None, optional + Layer representing the back head part of the bilayer. By default, None. + name : str, optional + Name for bilayer. By default, 'EasyBilayer'. + unique_name : str | None, optional + Unique name for internal object tracking. By default, None. + constrain_heads : bool, optional + When `True`, the back head layer thickness and area per molecule are constrained to match the front head layer. Solvent fraction - (hydration) remains independent on each side. Defaults to `True`. - :param conformal_roughness: When `True`, all four layer interfaces share - the same roughness value, controlled by the front head layer. Defaults to `True`. - :param interface: Calculator interface, defaults to `None`. + (hydration) remains independent on each side. By default, True. + conformal_roughness : bool, optional + When `True`, all four layer interfaces share + the same roughness value, controlled by the front head layer. By default, True. + interface : Any, optional + Calculator interface. By default, None. """ # Generate unique name for nested objects if unique_name is None: @@ -154,10 +167,19 @@ def _create_default_head_layer( ) -> LayerAreaPerMolecule: """Create a default head layer with DPPC head group parameters. - :param unique_name: Base unique name for internal object tracking. - :param name_suffix: Suffix for layer name ('Front' or 'Back'). - :param interface: Calculator interface, defaults to `None`. - :return: A new LayerAreaPerMolecule for the head group. + Parameters + ---------- + unique_name : str + Base unique name for internal object tracking. + name_suffix : str + Suffix for layer name ('Front' or 'Back'). + interface : Any, optional + Calculator interface. By default, None. + + Returns + ------- + LayerAreaPerMolecule + A new LayerAreaPerMolecule for the head group. """ solvent = Material( sld=DEFAULTS['solvent']['sld'], @@ -185,9 +207,17 @@ def _create_default_tail_layer( ) -> LayerAreaPerMolecule: """Create a default tail layer with DPPC tail group parameters. - :param unique_name: Base unique name for internal object tracking. - :param interface: Calculator interface, defaults to `None`. - :return: A new LayerAreaPerMolecule for the tail group. + Parameters + ---------- + unique_name : str + Base unique name for internal object tracking. + interface : Any, optional + Calculator interface. By default, None. + + Returns + ------- + LayerAreaPerMolecule + A new LayerAreaPerMolecule for the tail group. """ solvent = Material( sld=DEFAULTS['solvent']['sld'], @@ -216,10 +246,19 @@ def _create_back_tail_layer( ) -> LayerAreaPerMolecule: """Create a back tail layer with initial values copied from the front tail layer. - :param front_tail_layer: The front tail layer to copy initial values from. - :param unique_name: Base unique name for internal object tracking. - :param interface: Calculator interface, defaults to `None`. - :return: A new LayerAreaPerMolecule for the back tail. + Parameters + ---------- + front_tail_layer : LayerAreaPerMolecule + The front tail layer to copy initial values from. + unique_name : str + Base unique name for internal object tracking. + interface : Any, optional + Calculator interface. By default, None. + + Returns + ------- + LayerAreaPerMolecule + A new LayerAreaPerMolecule for the back tail. """ solvent = Material( sld=DEFAULTS['solvent']['sld'], @@ -312,7 +351,10 @@ def constrain_heads(self, status: bool) -> None: are constrained to match the front head layer. Solvent fraction (hydration) remains independent. - :param status: Boolean for the constraint status. + Parameters + ---------- + status : bool + Boolean for the constraint status. """ if status: self._enable_head_constraints() @@ -354,7 +396,10 @@ def conformal_roughness(self, status: bool) -> None: When enabled, all layers share the same roughness parameter (controlled by the front head layer). - :param status: Boolean for the constraint status. + Parameters + ---------- + status : bool + Boolean for the constraint status. """ if status: self._setup_roughness_constraints() @@ -367,7 +412,10 @@ def conformal_roughness(self, status: bool) -> None: def constrain_solvent_roughness(self, solvent_roughness: Parameter) -> None: """Add the constraint to the solvent roughness. - :param solvent_roughness: The solvent roughness parameter. + Parameters + ---------- + solvent_roughness : Parameter + The solvent roughness parameter. """ if not self.conformal_roughness: raise ValueError('Roughness must be conformal to use this function.') @@ -395,16 +443,28 @@ def constrain_multiple_contrast( Makes this bilayer's parameters dependent on another_contrast's parameters, so that changes to another_contrast propagate to this bilayer. - :param another_contrast: The bilayer to constrain to. - :param front_head_thickness: Constrain front head thickness. - :param back_head_thickness: Constrain back head thickness. - :param tail_thickness: Constrain tail thickness. - :param front_head_area_per_molecule: Constrain front head area per molecule. - :param back_head_area_per_molecule: Constrain back head area per molecule. - :param tail_area_per_molecule: Constrain tail area per molecule. - :param front_head_fraction: Constrain front head solvent fraction. - :param back_head_fraction: Constrain back head solvent fraction. - :param tail_fraction: Constrain tail solvent fraction. + Parameters + ---------- + another_contrast : Bilayer + The bilayer to constrain to. + front_head_thickness : bool, optional + Constrain front head thickness. By default, True. + back_head_thickness : bool, optional + Constrain back head thickness. By default, True. + tail_thickness : bool, optional + Constrain tail thickness. By default, True. + front_head_area_per_molecule : bool, optional + Constrain front head area per molecule. By default, True. + back_head_area_per_molecule : bool, optional + Constrain back head area per molecule. By default, True. + tail_area_per_molecule : bool, optional + Constrain tail area per molecule. By default, True. + front_head_fraction : bool, optional + Constrain front head solvent fraction. By default, True. + back_head_fraction : bool, optional + Constrain back head solvent fraction. By default, True. + tail_fraction : bool, optional + Constrain tail solvent fraction. By default, True. """ if front_head_thickness: self.front_head_layer.thickness.make_dependent_on( @@ -479,7 +539,10 @@ def as_dict(self, skip: list[str] | None = None) -> dict: The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : list[str] | None, optional + List of keys to skip. By default, None. """ this_dict = super().as_dict(skip=skip) this_dict['front_head_layer'] = self.front_head_layer.as_dict(skip=skip) diff --git a/src/easyreflectometry/sample/assemblies/gradient_layer.py b/src/easyreflectometry/sample/assemblies/gradient_layer.py index 38771e80..1e08bb2e 100644 --- a/src/easyreflectometry/sample/assemblies/gradient_layer.py +++ b/src/easyreflectometry/sample/assemblies/gradient_layer.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Optional from easyscience import global_object @@ -11,8 +14,8 @@ class GradientLayer(BaseAssembly): """A set of discrete gradient layers changing from the front to the back material. - The front layer is where the neutron beam starts in, it has an index of 0. + The front layer is where the neutron beam starts in, it has an index of 0. """ def __init__( @@ -28,13 +31,24 @@ def __init__( ): """Constructor. - :param front_material: Material of front of the layer - :param back_material: Material of back of the layer - :param thickness: Thicknkess of the layer - :param roughness: Roughness of the layer - :param discretisation_elements: Number of discrete layers - :param name: Name for gradient layer, defaults to 'EasyGradienLayer'. - :param interface: Calculator interface, defaults to `None`. + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + front_material : Optional[Material], optional + Material of front of the layer. By default, None. + back_material : Optional[Material], optional + Material of back of the layer. By default, None. + thickness : Optional[float], optional + Thicknkess of the layer. By default, 2.0. + roughness : Optional[float], optional + Roughness of the layer. By default, 0.2. + discretisation_elements : int, optional + Number of discrete layers. By default, 10. + name : str, optional + Name for gradient layer. By default, 'EasyGradienLayer'. + interface : + Calculator interface. By default, None. """ if front_material is None: @@ -82,7 +96,10 @@ def thickness(self) -> float: def thickness(self, thickness: float) -> None: """Set the thickness of the gradient layer. - :param thickness: Thickness of the gradient layer in Angstroms. + Parameters + ---------- + thickness : float + Thickness of the gradient layer in Angstroms. """ self.front_layer.thickness.value = thickness / self._discretisation_elements @@ -95,7 +112,10 @@ def roughness(self) -> float: def roughness(self, roughness: float) -> None: """Set the roughness of the gradient layer. - :param roughness: Roughness of the gradient layer in Angstroms. + Parameters + ---------- + roughness : float + Roughness of the gradient layer in Angstroms. """ self.front_layer.roughness.value = roughness @@ -103,7 +123,7 @@ def roughness(self, roughness: float) -> None: def _dict_repr(self) -> dict[str, str]: """A simplified dict representation.""" return { - 'thickness': float(self.thickness), # Conversion to float is necessary to prevent property reference in dict + 'thickness': float(self.thickness), # Conversion to float is necessary to prevent property reference in dict 'discretisation_elements': int(self._discretisation_elements), # Same as above 'back_layer': self.back_layer._dict_repr, 'front_layer': self.front_layer._dict_repr, @@ -111,9 +131,13 @@ def _dict_repr(self) -> dict[str, str]: def as_dict(self, skip: Optional[list[str]] = None) -> dict: """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : Optional[list[str]], optional + List of keys to skip. By default, None. """ this_dict = super().as_dict(skip=skip) # Determined in __init__ @@ -126,6 +150,7 @@ def _linear_gradient( back_value: float, discretisation_elements: int, ) -> list[float]: + """Linear gradient.""" discrete_step = (back_value - front_value) / discretisation_elements if discrete_step != 0: # Both front and back values are included @@ -141,6 +166,7 @@ def _prepare_gradient_layers( discretisation_elements: int, interface=None, ) -> LayerCollection: + """Prepare gradient layers.""" gradient_sld = _linear_gradient( front_value=front_material.sld.value, back_value=back_material.sld.value, diff --git a/src/easyreflectometry/sample/assemblies/multilayer.py b/src/easyreflectometry/sample/assemblies/multilayer.py index 360db10a..9144b3a8 100644 --- a/src/easyreflectometry/sample/assemblies/multilayer.py +++ b/src/easyreflectometry/sample/assemblies/multilayer.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from __future__ import annotations from typing import Optional @@ -10,6 +13,7 @@ class Multilayer(BaseAssembly): """A multi layer is build from a single or a list of `Layer` or `LayerCollection`. + The multi layer will arrange the layers as slabs, allowing the reflectometry to be determined from them. The front layer is where the neutron beam starts in, it has an index of 0. @@ -30,10 +34,20 @@ def __init__( ): """Constructor. - :param layers: The layers that make up the multi-layer. - :param name: Name for multi layer, defaults to 'EasyMultilayer'. - :param interface: Calculator interface, defaults to `None`. - :param type: Type of the constructed instance, defaults to 'Multi-layer' + Parameters + ---------- + populate_if_none : Optional[bool], optional + By default, True. + unique_name : Optional[str], optional + By default, None. + layers : Union[Layer, list[Layer], LayerCollection, None], optional + The layers that make up the multi-layer. By default, None. + name : str, optional + Name for multi layer. By default, 'EasyMultilayer'. + interface : + Calculator interface. By default, None. + type : str, optional + Type of the constructed instance. By default, 'Multi-layer'. """ if layers is None: if populate_if_none: @@ -53,7 +67,10 @@ def __init__( def add_layer(self, *layers: tuple[Layer]) -> None: """Add a layer to the multi layer. - :param layers: Layers to add to the multi layer. + Parameters + ---------- + *layers : tuple[Layer] + Layers to add to the multi layer. """ for arg in layers: if issubclass(arg.__class__, Layer): @@ -64,8 +81,10 @@ def add_layer(self, *layers: tuple[Layer]) -> None: def duplicate_layer(self, idx: int) -> None: """Duplicate a given layer. - :param idx: index of layer to duplicate. - :type idx: int + Parameters + ---------- + idx : int + Index of layer to duplicate. """ to_duplicate = self.layers[idx] duplicate_layer = Layer( @@ -79,7 +98,10 @@ def duplicate_layer(self, idx: int) -> None: def remove_layer(self, idx: int) -> None: """Remove a layer from the item. - :param idx: index of layer to remove + Parameters + ---------- + idx : int + Index of layer to remove. """ if self.interface is not None: self.interface().remove_layer_from_item(self.layers[idx].unique_name, self.unique_name) @@ -93,11 +115,6 @@ def _dict_repr(self) -> dict: @classmethod def from_dict(cls, data: dict) -> Multilayer: - """ - Create a Multilayer from a dictionary. - - :param data: dictionary of the Multilayer - :return: Multilayer - """ + """Create a Multilayer from a dictionary.""" multilayer = super().from_dict(data) return multilayer diff --git a/src/easyreflectometry/sample/assemblies/repeating_multilayer.py b/src/easyreflectometry/sample/assemblies/repeating_multilayer.py index 7c4ecbcc..cb396836 100644 --- a/src/easyreflectometry/sample/assemblies/repeating_multilayer.py +++ b/src/easyreflectometry/sample/assemblies/repeating_multilayer.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Optional from typing import Union @@ -22,8 +25,7 @@ class RepeatingMultilayer(Multilayer): - """ - A repeating multi layer is build from a `Multilayer` and which it repeats + """A repeating multi layer is build from a `Multilayer` and which it repeats for a given number of times. This enables a computational efficiency in many reflectometry engines as the operation can be performed for a single `Multilayer` and cheaply combined for the appropriate number of @@ -46,10 +48,20 @@ def __init__( ): """Constructor. - :param layers: The layers that make up the multi-layer that will be repeated. - :param repetitions: Number of repetitions of the given series of layers - :param name: Name for the repeating multi layer, defaults to 'EasyRepeatingMultilayer'. - :param interface: Calculator interface, defaults to `None`. + Parameters + ---------- + populate_if_none : bool, optional + By default, True. + unique_name : Optional[str], optional + By default, None. + layers : Union[LayerCollection, Layer, list[Layer], None], optional + The layers that make up the multi-layer that will be repeated. By default, None. + repetitions : Union[Parameter, int, None], optional + Number of repetitions of the given series of layers. By default, None. + name : str, optional + Name for the repeating multi layer. By default, 'EasyRepeatingMultilayer'. + interface : + Calculator interface. By default, None. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) diff --git a/src/easyreflectometry/sample/assemblies/surfactant_layer.py b/src/easyreflectometry/sample/assemblies/surfactant_layer.py index 6cbd2c6b..81c9c36c 100644 --- a/src/easyreflectometry/sample/assemblies/surfactant_layer.py +++ b/src/easyreflectometry/sample/assemblies/surfactant_layer.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from __future__ import annotations from typing import Optional @@ -37,12 +40,22 @@ def __init__( ): """Constructor. - :param tail_layer: Layer representing the tail part of the surfactant layer. - :param head_layer: Layer representing the head part of the surfactant layer. - :param name: Name for surfactant layer, defaults to 'EasySurfactantLayer'. - :param constrain_area_per_molecule: Constrain the area per molecule, defaults to `False`. - :param conformal_roughness: Constrain the roughness to be the same for both layers, defaults to `False`. - :param interface: Calculator interface, defaults to `None`. + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + tail_layer : Optional[LayerAreaPerMolecule], optional + Layer representing the tail part of the surfactant layer. By default, None. + head_layer : Optional[LayerAreaPerMolecule], optional + Layer representing the head part of the surfactant layer. By default, None. + name : str, optional + Name for surfactant layer. By default, 'EasySurfactantLayer'. + constrain_area_per_molecule : bool, optional + Constrain the area per molecule. By default, False. + conformal_roughness : bool, optional + Constrain the roughness to be the same for both layers. By default, False. + interface : + Calculator interface. By default, None. """ # We need to generate a unique name to create the nested objects if unique_name is None: @@ -138,7 +151,10 @@ def constrain_area_per_molecule(self, status: bool): """Set the status for the area per molecule constraint such that the head and tail layers have the same area per molecule. - :param status: Boolean description the wanted of the constraint. + Parameters + ---------- + status : bool + Boolean description the wanted of the constraint. """ if status: independent_param = self.tail_layer._area_per_molecule @@ -158,7 +174,10 @@ def conformal_roughness(self) -> bool: def conformal_roughness(self, status: bool): """Set the status for the roughness to be the same for both layers. - :param status: Boolean description the wanted of the constraint. + Parameters + ---------- + status : bool + Boolean description the wanted of the constraint. """ if status: self._enable_roughness_constraints() @@ -170,7 +189,10 @@ def conformal_roughness(self, status: bool): def constrain_solvent_roughness(self, solvent_roughness: Parameter): """Add the constraint to the solvent roughness. - :param solvent_roughness: The solvent roughness parameter. + Parameters + ---------- + solvent_roughness : Parameter + The solvent roughness parameter. """ if not self.conformal_roughness: raise ValueError('Roughness must be conformal to use this function.') @@ -189,7 +211,22 @@ def constrain_multiple_contrast( ): """Constrain structural parameters between surfactant layer objects. - :param another_contrast: The surfactant layer to constrain + Parameters + ---------- + tail_layer_fraction : bool, optional + By default, True. + head_layer_fraction : bool, optional + By default, True. + tail_layer_area_per_molecule : bool, optional + By default, True. + head_layer_area_per_molecule : bool, optional + By default, True. + tail_layer_thickness : bool, optional + By default, True. + head_layer_thickness : bool, optional + By default, True. + another_contrast : SurfactantLayer + The surfactant layer to constrain. """ if head_layer_thickness: self.head_layer.thickness.make_dependent_on( @@ -241,9 +278,13 @@ def _dict_repr(self) -> dict: def as_dict(self, skip: Optional[list[str]] = None) -> dict: """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : Optional[list[str]], optional + List of keys to skip. By default, None. """ this_dict = super().as_dict(skip=skip) this_dict['tail_layer'] = self.tail_layer.as_dict(skip=skip) diff --git a/src/easyreflectometry/sample/base_core.py b/src/easyreflectometry/sample/base_core.py index b4c3f3ed..4cf600a6 100644 --- a/src/easyreflectometry/sample/base_core.py +++ b/src/easyreflectometry/sample/base_core.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from abc import abstractmethod from easyscience import ObjBase as BaseObj @@ -12,6 +15,7 @@ def __init__( interface, **kwargs, ): + """Init function.""" super().__init__(name=name, **kwargs) # Updates interface using property in base object @@ -21,11 +25,12 @@ def __init__( def _dict_repr(self) -> dict[str, str]: ... def __repr__(self) -> str: - """ - String representation of the layer. + """String representation of the layer. - :return: a string representation of the layer - :rtype: str + Returns + ------- + str + A string representation of the layer. """ return yaml_dump(self._dict_repr) diff --git a/src/easyreflectometry/sample/collections/__init__.py b/src/easyreflectometry/sample/collections/__init__.py new file mode 100644 index 00000000..4e798e20 --- /dev/null +++ b/src/easyreflectometry/sample/collections/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easyreflectometry/sample/collections/base_collection.py b/src/easyreflectometry/sample/collections/base_collection.py index 53d16b51..28d494a5 100644 --- a/src/easyreflectometry/sample/collections/base_collection.py +++ b/src/easyreflectometry/sample/collections/base_collection.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import List from typing import Optional @@ -16,6 +19,7 @@ def __init__( unique_name: Optional[str] = None, **kwargs, ): + """Init function.""" if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) @@ -27,24 +31,33 @@ def __init__( self.populate_if_none = False def __repr__(self) -> str: - """ - String representation of the collection. + """String representation of the collection. - :return: a string representation of the collection + Returns + ------- + str + A string representation of the collection. """ return yaml_dump(self._dict_repr) @property def names(self) -> list: - """ - :returns: list of names for the elements in the collection. + """Names function. + + Returns + ------- + s : list + List of names for the elements in the collection. """ return [i.name for i in self] def move_up(self, index: int): """Move the element at the given index up in the collection. - :param index: Index of the element to move up. + Parameters + ---------- + index : int + Index of the element to move up. """ if index == 0: return @@ -53,34 +66,43 @@ def move_up(self, index: int): def move_down(self, index: int): """Move the element at the given index down in the collection. - :param index: Index of the element to move down. + Parameters + ---------- + index : int + Index of the element to move down. """ if index == len(self) - 1: return self.insert(index + 1, self.pop(index)) def remove(self, index: int): - """ - Remove an element from the elements. + """Remove an element from the elements. - :param index: Index of the element to remove + Parameters + ---------- + index : int + Index of the element to remove. """ self.pop(index) @property def _dict_repr(self) -> dict: - """ - A simplified dict representation. + """A simplified dict representation. - :return: Simple dictionary + Returns + ------- + dict + Simple dictionary. """ return {self.name: [i._dict_repr for i in self]} def as_dict(self, skip: Optional[List[str]] = None) -> dict: - """ - Create a dictionary representation of the collection. + """Create a dictionary representation of the collection. - :return: A dictionary representation of the collection + Returns + ------- + dict + A dictionary representation of the collection. """ if skip is None: skip = [] @@ -92,4 +114,5 @@ def as_dict(self, skip: Optional[List[str]] = None) -> dict: return this_dict def __deepcopy__(self, memo): + """Deepcopy function.""" return self.from_dict(self.as_dict(skip=['unique_name'])) diff --git a/src/easyreflectometry/sample/collections/layer_collection.py b/src/easyreflectometry/sample/collections/layer_collection.py index 0761f861..99424f72 100644 --- a/src/easyreflectometry/sample/collections/layer_collection.py +++ b/src/easyreflectometry/sample/collections/layer_collection.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Optional @@ -16,6 +18,7 @@ def __init__( populate_if_none: bool = True, # Needed to match as_dict signature from BaseCollection **kwargs, ): + """Init function.""" if not layers: layers = [] @@ -24,7 +27,10 @@ def __init__( def add_layer(self, layer: Optional[Layer] = None): """Add a layer to the collection. - :param layer: Layer to add. + Parameters + ---------- + layer : Optional[Layer], optional + Layer to add. By default, None. """ if layer is None: layer = Layer( @@ -36,7 +42,11 @@ def add_layer(self, layer: Optional[Layer] = None): def duplicate_layer(self, index: int): """Duplicate a layer in the collection. - :param layer: Assembly to add. + Parameters + ---------- + index : int + layer : + Assembly to add. """ to_be_duplicated = self[index] duplicate = Layer.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) diff --git a/src/easyreflectometry/sample/collections/material_collection.py b/src/easyreflectometry/sample/collections/material_collection.py index a97f89d9..d7b5af3b 100644 --- a/src/easyreflectometry/sample/collections/material_collection.py +++ b/src/easyreflectometry/sample/collections/material_collection.py @@ -1,4 +1,7 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + from typing import Optional from typing import Tuple @@ -8,6 +11,7 @@ # Needs to be a function, elements are added to the global_object.map def DEFAULT_ELEMENTS(interface): + """Default elements.""" return ( Material(sld=0.0, isld=0.0, name='Air', interface=interface), Material(sld=6.335, isld=0.0, name='D2O', interface=interface), @@ -25,6 +29,7 @@ def __init__( populate_if_none: bool = True, **kwargs, ): + """Init function.""" if not materials: # Empty tuple if no materials are provided if populate_if_none: materials = DEFAULT_ELEMENTS(interface) @@ -42,7 +47,10 @@ def __init__( def add_material(self, material: Optional[Material] = None): """Add a material to the collection. - :param material: Material to add. + Parameters + ---------- + material : Optional[Material], optional + Material to add. By default, None. """ if material is None: material = Material(sld=0.0, isld=0.0, name='Material added') @@ -52,7 +60,11 @@ def add_material(self, material: Optional[Material] = None): def duplicate_material(self, index: int): """Duplicate a material in the collection. - :param material: Assembly to add. + Parameters + ---------- + index : int + material : + Assembly to add. """ to_be_duplicated = self[index] duplicate = Material.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) diff --git a/src/easyreflectometry/sample/collections/sample.py b/src/easyreflectometry/sample/collections/sample.py index 65c2a76b..1bbdb156 100644 --- a/src/easyreflectometry/sample/collections/sample.py +++ b/src/easyreflectometry/sample/collections/sample.py @@ -1,6 +1,7 @@ -from __future__ import annotations +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause -__author__ = 'github.com/arm61' +from __future__ import annotations from typing import List from typing import Optional @@ -15,7 +16,7 @@ # Needs to be a function, elements are added to the global_object.map def DEFAULT_ELEMENTS(interface): - """:meta private:""" + """:meta private:.""" return ( Multilayer(interface=interface), Multilayer(interface=interface), @@ -36,9 +37,20 @@ def __init__( ): """Constructor. - :param args: The assemblies in the sample. - :param name: Name of the sample, defaults to 'EasySample'. - :param interface: Calculator interface, defaults to `None`. + Parameters + ---------- + **kwargs : + populate_if_none : bool, optional + By default, True. + unique_name : Optional[str], optional + By default, None. + *assemblies : Optional[List[BaseAssembly]] + args : + The assemblies in the sample. + name : str, optional + Name of the sample. By default, 'EasySample'. + interface : + Calculator interface. By default, None. """ if not assemblies: if populate_if_none: @@ -54,7 +66,10 @@ def __init__( def add_assembly(self, assembly: Optional[BaseAssembly] = None): """Add an assembly to the sample. - :param assembly: Assembly to add. + Parameters + ---------- + assembly : Optional[BaseAssembly], optional + Assembly to add. By default, None. """ if assembly is None: assembly = Multilayer( @@ -66,7 +81,11 @@ def add_assembly(self, assembly: Optional[BaseAssembly] = None): def duplicate_assembly(self, index: int): """Add an assembly to the sample. - :param assembly: Assembly to add. + Parameters + ---------- + index : int + assembly : + Assembly to add. """ to_be_duplicated = self[index] if isinstance(to_be_duplicated, Multilayer): @@ -81,21 +100,30 @@ def duplicate_assembly(self, index: int): def move_up(self, index: int): """Move the assembly at the given index up in the sample. - :param index: Index of the assembly to move up. + Parameters + ---------- + index : int + Index of the assembly to move up. """ super().move_up(index) def move_down(self, index: int): """Move the assembly at the given index down in the sample. - :param index: Index of the assembly to move down. + Parameters + ---------- + index : int + Index of the assembly to move down. """ super().move_down(index) def remove_assembly(self, index: int): """Remove the assembly at the given index from the sample. - :param index: Index of the assembly to remove. + Parameters + ---------- + index : int + Index of the assembly to remove. """ self.pop(index) @@ -116,9 +144,13 @@ def subphase(self) -> Layer: # Representation def as_dict(self, skip: Optional[List[str]] = None) -> dict: """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : Optional[List[str]], optional + List of keys to skip. By default, None. """ this_dict = super().as_dict(skip=skip) this_dict['populate_if_none'] = self.populate_if_none diff --git a/src/easyreflectometry/sample/elements/__init__.py b/src/easyreflectometry/sample/elements/__init__.py new file mode 100644 index 00000000..4e798e20 --- /dev/null +++ b/src/easyreflectometry/sample/elements/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easyreflectometry/sample/elements/layers/__init__.py b/src/easyreflectometry/sample/elements/layers/__init__.py new file mode 100644 index 00000000..4e798e20 --- /dev/null +++ b/src/easyreflectometry/sample/elements/layers/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easyreflectometry/sample/elements/layers/layer.py b/src/easyreflectometry/sample/elements/layers/layer.py index 513b18f9..fa58dc15 100644 --- a/src/easyreflectometry/sample/elements/layers/layer.py +++ b/src/easyreflectometry/sample/elements/layers/layer.py @@ -1,4 +1,7 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + from typing import Optional from typing import Union @@ -53,11 +56,20 @@ def __init__( ): """Constructor. - :param material: The material for the layer. - :param thickness: Layer thickness in Angstrom. - :param roughness: Upper roughness on the layer in Angstrom. - :param name: Name of the layer, defaults to 'EasyLayer' - :param interface: Interface object, defaults to `None` + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + material : Union[Material, None], optional + The material for the layer. By default, None. + thickness : Union[Parameter, float, None], optional + Layer thickness in Angstrom. By default, None. + roughness : Union[Parameter, float, None], optional + Upper roughness on the layer in Angstrom. By default, None. + name : str, optional + Name of the layer. By default, 'EasyLayer'. + interface : + Interface object. By default, None. """ if material is None: material = Material(interface=interface) @@ -95,7 +107,10 @@ def __init__( def assign_material(self, material: Material) -> None: """Assign a material to the layer interface. - :param material: The material to assign to the layer. + Parameters + ---------- + material : Material + The material to assign to the layer. """ self.material = material if self.interface is not None: diff --git a/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py b/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py index dbda1473..9053aa95 100644 --- a/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py +++ b/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Optional from typing import Union @@ -50,7 +53,6 @@ class LayerAreaPerMolecule(Layer): """The `LayerAreaPerMolecule` class allows a layer to be defined in terms of some molecular formula an area per molecule, and a solvent. - """ # Added in __init__ @@ -78,14 +80,26 @@ def __init__( ): """Constructor. - :param molecular_formula: Formula for the molecule in the layer. - :param thickness: Layer thickness in Angstrom. - :param solvent: Solvent containing the molecule. - :param solvent_fraction: Fraction of solvent in layer. Fx solvation or surface coverage. - :param area_per_molecule: Area per molecule in the layer - :param roughness: Upper roughness on the layer in Angstrom. - :param name: Name of the layer, defaults to "EasyLayerAreaPerMolecule" - :param interface: Interface object, defaults to `None` + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + molecular_formula : Union[str, None], optional + Formula for the molecule in the layer. By default, None. + thickness : Union[Parameter, float, None], optional + Layer thickness in Angstrom. By default, None. + solvent : Union[Material, None], optional + Solvent containing the molecule. By default, None. + solvent_fraction : Union[Parameter, float, None], optional + Fraction of solvent in layer. Fx solvation or surface coverage. By default, None. + area_per_molecule : Union[Parameter, float, None], optional + Area per molecule in the layer. By default, None. + roughness : Union[Parameter, float, None], optional + Upper roughness on the layer in Angstrom. By default, None. + name : str, optional + Name of the layer. By default, 'EasyLayerAreaPerMolecule'. + interface : + Interface object. By default, None. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) @@ -190,7 +204,10 @@ def area_per_molecule(self) -> float: def area_per_molecule(self, new_area_per_molecule: float) -> None: """Set the area per molecule. - :param new_area_per_molecule: New area per molecule. + Parameters + ---------- + new_area_per_molecule : float + New area per molecule. """ if new_area_per_molecule < 0: raise ValueError('new_area_per_molecule must be greater than 0.0.') @@ -210,7 +227,10 @@ def solvent(self) -> Material: def solvent(self, new_solvent: Material) -> None: """Set the solvent material. - :param new_solvent: New solvent material. + Parameters + ---------- + new_solvent : Material + New solvent material. """ self.material.solvent = new_solvent @@ -222,6 +242,7 @@ def solvent_fraction_parameter(self) -> float: @property def solvent_fraction(self) -> float: """Get the fraction of the layer occupied by the solvent. + This could be a result of either water solvating the molecule, or incomplete surface coverage of the molecules. """ return self.material.solvent_fraction @@ -229,9 +250,13 @@ def solvent_fraction(self) -> float: @solvent_fraction.setter def solvent_fraction(self, solvent_fraction: float) -> None: """Set the fraction of the layer occupied by the solvent. + This could be a result of either water solvating the molecule, or incomplete surface coverage of the molecules. - :param solvent_fraction: Fraction of layer described by the solvent. + Parameters + ---------- + solvent_fraction : float + Fraction of layer described by the solvent. """ self.material.solvent_fraction = solvent_fraction @@ -244,7 +269,10 @@ def molecular_formula(self) -> str: def molecular_formula(self, formula_string: str) -> None: """Set the formula of the molecule in the material. - :param formula_string: String that defines the molecular formula. + Parameters + ---------- + formula_string : str + String that defines the molecular formula. """ self._molecular_formula = formula_string scattering_length = neutron_scattering_length(formula_string) @@ -257,7 +285,10 @@ def molecular_formula(self, formula_string: str) -> None: @property def _dict_repr(self) -> dict[str, str]: - """Dictionary representation of the `area_per_molecule` object. Produces a simple dictionary""" + """Dictionary representation of the `area_per_molecule` object. + + Produces a simple dictionary. + """ dict_repr = super()._dict_repr dict_repr['molecular_formula'] = self._molecular_formula dict_repr['area_per_molecule'] = f'{self.area_per_molecule:.2f} {self._area_per_molecule.unit}' @@ -265,9 +296,13 @@ def _dict_repr(self) -> dict[str, str]: def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, str]: """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : Optional[list[str]], optional + List of keys to skip. By default, None. """ this_dict = super().as_dict(skip=skip) this_dict['solvent_fraction'] = self.material._fraction.as_dict(skip=skip) diff --git a/src/easyreflectometry/sample/elements/materials/__init__.py b/src/easyreflectometry/sample/elements/materials/__init__.py new file mode 100644 index 00000000..4e798e20 --- /dev/null +++ b/src/easyreflectometry/sample/elements/materials/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause diff --git a/src/easyreflectometry/sample/elements/materials/material.py b/src/easyreflectometry/sample/elements/materials/material.py index 8c030031..42091bd6 100644 --- a/src/easyreflectometry/sample/elements/materials/material.py +++ b/src/easyreflectometry/sample/elements/materials/material.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Optional from typing import Union @@ -49,10 +51,18 @@ def __init__( ): """Constructor. - :param sld: Real scattering length density. - :param isld: Imaginary scattering length density. - :param name: Name of the material, defaults to 'EasyMaterial'. - :param interface: Calculator interface, defaults to `None`. + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + sld : Union[Parameter, float, None], optional + Real scattering length density. By default, None. + isld : Union[Parameter, float, None], optional + Imaginary scattering length density. By default, None. + name : str, optional + Name of the material. By default, 'EasyMaterial'. + interface : + Calculator interface. By default, None. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) diff --git a/src/easyreflectometry/sample/elements/materials/material_density.py b/src/easyreflectometry/sample/elements/materials/material_density.py index 85a3bf1b..b85bda98 100644 --- a/src/easyreflectometry/sample/elements/materials/material_density.py +++ b/src/easyreflectometry/sample/elements/materials/material_density.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Optional from typing import Union @@ -54,10 +57,18 @@ def __init__( ): """Constructor. - :param chemical_structure: Chemical formula for the material. - :param density: Mass density for the material. - :param name: Identifier, defaults to `EasyMaterialDensity`. - :param interface: Interface object, defaults to `None`. + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + chemical_structure : Union[str, None], optional + Chemical formula for the material. By default, None. + density : Union[Parameter, float, None], optional + Mass density for the material. By default, None. + name : str, optional + Identifier. By default, 'EasyMaterialDensity'. + interface : + Interface object. By default, None. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) @@ -130,7 +141,10 @@ def chemical_structure(self) -> str: def chemical_structure(self, structure_string: str) -> None: """Set the chemical structure string. - :param structure_string: String that defines the chemical structure. + Parameters + ---------- + structure_string : str + String that defines the chemical structure. """ self._chemical_structure = structure_string scattering_length = neutron_scattering_length(structure_string) @@ -147,9 +161,13 @@ def _dict_repr(self) -> dict[str, str]: def as_dict(self, skip: list = []) -> dict[str, str]: """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : list, optional + List of keys to skip. By default, []. """ this_dict = super().as_dict(skip=skip) # From Material diff --git a/src/easyreflectometry/sample/elements/materials/material_mixture.py b/src/easyreflectometry/sample/elements/materials/material_mixture.py index a54f3d9d..c999093e 100644 --- a/src/easyreflectometry/sample/elements/materials/material_mixture.py +++ b/src/easyreflectometry/sample/elements/materials/material_mixture.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Optional from typing import Union @@ -41,11 +44,20 @@ def __init__( ): """Constructor. - :param material_a: The first material. - :param material_b: The second material. - :param fraction: The fraction of material_b in material_a. - :param name: Name of the material, defaults to None that causes the name to be constructed. - :param interface: Calculator interface, defaults to `None`. + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + material_a : Union[Material, None], optional + The first material. By default, None. + material_b : Union[Material, None], optional + The second material. By default, None. + fraction : Union[Parameter, float, None], optional + The fraction of material_b in material_a. By default, None. + name : Union[str, None], optional + Name of the material. By default, None. + interface : + Calculator interface. By default, None. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) @@ -102,22 +114,34 @@ def __init__( self.interface = interface def _get_linkable_attributes(self): + """Get linkable attributes.""" return [self._sld, self._isld] @property def sld(self) -> float: + """Sld function.""" return self._sld.value @property def isld(self) -> float: + """Isld function.""" return self._isld.value def _materials_constraints(self): + """Materials constraints.""" dependency_expression = 'a * (1 - p) + b * p' - dependency_map = {'a': self._material_a.sld, 'b': self._material_b.sld, 'p': self._fraction} + dependency_map = { + 'a': self._material_a.sld, + 'b': self._material_b.sld, + 'p': self._fraction, + } self._sld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) - dependency_map = {'a': self._material_a.isld, 'b': self._material_b.isld, 'p': self._fraction} + dependency_map = { + 'a': self._material_a.isld, + 'b': self._material_b.isld, + 'p': self._fraction, + } self._isld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) @property @@ -129,7 +153,10 @@ def fraction(self) -> float: def fraction(self, fraction: float) -> None: """Setter for fraction of material_b. - :param fraction: The fraction of material_b in material_a. + Parameters + ---------- + fraction : float + The fraction of material_b in material_a. """ if not isinstance(fraction, float): raise ValueError('fraction must be a float') @@ -142,9 +169,12 @@ def material_a(self) -> Material: @material_a.setter def material_a(self, new_material_a: Material) -> None: - """Setter for material_a + """Setter for material_a. - :param new_material_a: New Material for material_a + Parameters + ---------- + new_material_a : Material + New Material for material_a. """ self._material_a = new_material_a self._materials_constraints() @@ -159,9 +189,12 @@ def material_b(self) -> Material: @material_b.setter def material_b(self, new_material_b: Material) -> None: - """Setter for material_b + """Setter for material_b. - :param new_material_b: New Materialfor material_b + Parameters + ---------- + new_material_b : Material + New Materialfor material_b. """ self._material_b = new_material_b self._materials_constraints() @@ -170,6 +203,7 @@ def material_b(self, new_material_b: Material) -> None: self._update_name() def _update_name(self) -> None: + """Update name.""" self.name = self._material_a.name + '/' + self._material_b.name # Representation @@ -188,9 +222,13 @@ def _dict_repr(self) -> dict[str, str]: def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, str]: """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : Optional[list[str]], optional + List of keys to skip. By default, None. """ this_dict = super().as_dict(skip=skip) this_dict['material_a'] = self._material_a.as_dict(skip=skip) diff --git a/src/easyreflectometry/sample/elements/materials/material_solvated.py b/src/easyreflectometry/sample/elements/materials/material_solvated.py index 563e3550..454904ba 100644 --- a/src/easyreflectometry/sample/elements/materials/material_solvated.py +++ b/src/easyreflectometry/sample/elements/materials/material_solvated.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from typing import Optional from typing import Union @@ -33,11 +36,20 @@ def __init__( ): """Constructor. - :param material: The material being solvated. - :param solvent: The solvent material. - :param solvent_fraction: Fraction of solvent in layer. E.g. solvation or surface coverage. - :param name: Name of the material, defaults to None that causes the name to be constructed. - :param interface: Calculator interface, defaults to `None`. + Parameters + ---------- + unique_name : Optional[str], optional + By default, None. + material : Union[Material, None], optional + The material being solvated. By default, None. + solvent : Union[Material, None], optional + The solvent material. By default, None. + solvent_fraction : Union[Parameter, float, None], optional + Fraction of solvent in layer. E.g. solvation or surface coverage. By default, None. + name : + Name of the material. By default, None. + interface : + Calculator interface. By default, None. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) @@ -74,7 +86,10 @@ def material(self) -> Material: def material(self, new_material: Material) -> None: """Set the material. - :param new_material: Matrerial to be useed. + Parameters + ---------- + new_material : Material + Matrerial to be useed. """ self.material_a = new_material @@ -87,7 +102,10 @@ def solvent(self) -> Material: def solvent(self, new_solvent: Material) -> None: """Set the solvent. - :param new_solvent: Solvent to be used. + Parameters + ---------- + new_solvent : Material + Solvent to be used. """ self.material_b = new_solvent @@ -99,6 +117,7 @@ def solvent_fraction_parameter(self) -> Parameter: @property def solvent_fraction(self) -> float: """Get the fraction of layer described by the solvent. + This might be fraction of: Solvation where solvent is within the layer Patches of solvent in the layer where no material is present. @@ -108,11 +127,15 @@ def solvent_fraction(self) -> float: @solvent_fraction.setter def solvent_fraction(self, solvent_fraction: float) -> None: """Set the fraction of layer covered by the material. + This might be fraction of: Solvation where solvent is within the layer Patches of solvent in the layer where no material is present. - :param solvent_fraction : Fraction of layer described by the solvent. + Parameters + ---------- + solvent_fraction : float + Fraction of layer described by the solvent. """ try: self.fraction = solvent_fraction @@ -122,6 +145,7 @@ def solvent_fraction(self, solvent_fraction: float) -> None: raise ValueError('solvent_fraction must be a float between 0 and 1') def _update_name(self) -> None: + """Update name.""" self.name = self._material_a.name + ' in ' + self._material_b.name # Representation @@ -140,9 +164,13 @@ def _dict_repr(self) -> dict[str, str]: def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, str]: """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the parameters in __init__ - :param skip: List of keys to skip, defaults to `None`. + Parameters + ---------- + skip : Optional[list[str]], optional + List of keys to skip. By default, None. """ this_dict = super().as_dict(skip=skip) this_dict['material'] = self.material.as_dict(skip=skip) diff --git a/src/easyreflectometry/special/calculations.py b/src/easyreflectometry/special/calculations.py index f7ea9068..07844d3c 100644 --- a/src/easyreflectometry/special/calculations.py +++ b/src/easyreflectometry/special/calculations.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import periodictable as pt @@ -6,23 +8,37 @@ def weighted_average(a: float, b: float, p: float) -> float: - """ - Determine the weighted average for a and b, where p is the weight. - - :param a: First value - :param b: Second value - :param p: Weight - :return: Weighted average + """Determine the weighted average for a and b, where p is the weight. + + Parameters + ---------- + a : float + First value. + b : float + Second value. + p : float + Weight. + + Returns + ------- + float + Weighted average. """ return a * (1 - p) + b * p def neutron_scattering_length(formula: str) -> complex: - """ - Determine the neutron scattering length for a chemical formula. + """Determine the neutron scattering length for a chemical formula. + + Parameters + ---------- + formula : str + Chemical formula. - :param formula: Chemical formula. - :return: Real and imaginary descriptors for the scattering length in angstrom. + Returns + ------- + complex + Real and imaginary descriptors for the scattering length in angstrom. """ formula_as_dict = parse_formula(formula) scattering_length = 0 + 0j @@ -37,11 +53,17 @@ def neutron_scattering_length(formula: str) -> complex: def molecular_weight(formula: str) -> float: - """ - Determine the molecular weight for a chemical formula. + """Determine the molecular weight for a chemical formula. - :param formula: Chemical formula - :return: Molecular weight of the material in kilograms. + Parameters + ---------- + formula : str + Chemical formula. + + Returns + ------- + float + Molecular weight of the material in kilograms. """ formula_as_dict = parse_formula(formula) mw = 0 @@ -55,25 +77,41 @@ def area_per_molecule_to_scattering_length_density( thickness: float, area_per_molecule: float, ) -> float: - """ - Find the scattering length density for a given area per molecule. - - :param scattering_length: Scattering length of component, in angstrom. - :param thickness: Thickness of component, in angstrom. - :param area_per_molecule: Area per molecule, in angstrom^2. - :return: Scattering length density of layer in e-6 1/angstrom^2. + """Find the scattering length density for a given area per molecule. + + Parameters + ---------- + scattering_length : float + Scattering length of component, in angstrom. + thickness : float + Thickness of component, in angstrom. + area_per_molecule : float + Area per molecule, in angstrom^2. + + Returns + ------- + float + Scattering length density of layer in e-6 1/angstrom^2. """ return scattering_length / (thickness * area_per_molecule) * 1e6 def density_to_sld(scattering_length: float, molecular_weight: float, density: float) -> float: - """ - Find the scattering length density from the mass density of a material. - - :param scattering_length: Scattering length of component, in angstrom. - :param molecular_weight: Molecular weight of component, in u. - :param density: Mass density of the component, in gram centimeter^-3. - :return: Scattering length density of layer in e-6 1/angstrom^2. + """Find the scattering length density from the mass density of a material. + + Parameters + ---------- + scattering_length : float + Scattering length of component, in angstrom. + molecular_weight : float + Molecular weight of component, in u. + density : float + Mass density of the component, in gram centimeter^-3. + + Returns + ------- + float + Scattering length density of layer in e-6 1/angstrom^2. """ # 0.602214076 is avogadros constant times 1e-24 return 0.602214076e6 * density * scattering_length / molecular_weight diff --git a/src/easyreflectometry/special/parsing.py b/src/easyreflectometry/special/parsing.py index 0d52195e..fd0cd576 100644 --- a/src/easyreflectometry/special/parsing.py +++ b/src/easyreflectometry/special/parsing.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import re from typing import Tuple @@ -9,9 +11,17 @@ def _dictify(tuples: Tuple[Tuple[str, str]]) -> dict: - """ - :param tuples: tuples of tuples with atom and occurance. - :return: Dict of atoms and occurance. + """Dictify function. + + Parameters + ---------- + tuples : Tuple[Tuple[str, str]] + Tuples of tuples with atom and occurance. + + Returns + ------- + dict + Dict of atoms and occurance. """ res = dict() for atom, n in tuples: @@ -23,19 +33,37 @@ def _dictify(tuples: Tuple[Tuple[str, str]]) -> dict: def _fuse(mol1: dict, mol2: dict, w: int = 1) -> dict: - """ - :param mol1: First dict to fuse - :param mol2: Second dict to fuse - :param w: Weight for dicts - :return: Fused dictionaries + """Fuse function. + + Parameters + ---------- + mol1 : dict + First dict to fuse. + mol2 : dict + Second dict to fuse. + w : int, optional + Weight for dicts. By default, 1. + + Returns + ------- + dict + Fused dictionaries. """ return {atom: (mol1.get(atom, 0) + mol2.get(atom, 0)) * w for atom in set(mol1) | set(mol2)} def _parse(formula: str) -> Tuple[dict, int]: - """ - :param formula: Chemical formula as a string - :return: Tuple containing; formula as a dictwith occurences + """Parse function. + + Parameters + ---------- + formula : str + Chemical formula as a string. + + Returns + ------- + + Tuple containing; formula as a dictwith occurences of each atom and an iterator. """ token_list = [] @@ -73,8 +101,16 @@ def _parse(formula: str) -> Tuple[dict, int]: def parse_formula(formula: str) -> dict: - """ - :param formula: Chemical formula as a string - :return: Formula as a dict with occurences of each atom. + """Parse formula. + + Parameters + ---------- + formula : str + Chemical formula as a string. + + Returns + ------- + dict + Formula as a dict with occurences of each atom. """ return _parse(formula)[0] diff --git a/src/easyreflectometry/summary/__init__.py b/src/easyreflectometry/summary/__init__.py index af9d5fa4..7f333368 100644 --- a/src/easyreflectometry/summary/__init__.py +++ b/src/easyreflectometry/summary/__init__.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from .summary import Summary -__all__ = [Summary] +__all__ = ['Summary'] diff --git a/src/easyreflectometry/summary/html_templates.py b/src/easyreflectometry/summary/html_templates.py index dc8afc0e..9b770c01 100644 --- a/src/easyreflectometry/summary/html_templates.py +++ b/src/easyreflectometry/summary/html_templates.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + HTML_TEMPLATE = """ @@ -39,7 +42,7 @@ experiments_section - +

Refinement

@@ -49,7 +52,7 @@ figures_section - + """ @@ -75,18 +78,18 @@ HTML_PARAMETER_HEADER_TEMPLATE = """ - parameter_name + parameter_name parameter_value - parameter_unit + parameter_unit parameter_error """ HTML_PARAMETER_TEMPLATE = """ - parameter_name + parameter_name parameter_value - parameter_unit + parameter_unit parameter_error """ diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index 34da2974..394c9d78 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import matplotlib.pyplot as plt import numpy as np from easyscience import global_object @@ -16,9 +19,11 @@ class Summary: def __init__(self, project: Project): + """Init function.""" self._project = project def compile_html_summary(self, figures: bool = False) -> str: + """Compile html summary.""" html = HTML_TEMPLATE html = html.replace('project_information_section', self._project_information_section()) @@ -40,11 +45,13 @@ def compile_html_summary(self, figures: bool = False) -> str: return html def save_html_summary(self, filename: str) -> None: + """Save html summary.""" html = self.compile_html_summary(figures=True) with open(filename, 'w') as f: f.write(html) def save_pdf_summary(self, filename: str) -> None: + """Save pdf summary.""" html = self.compile_html_summary(figures=True) with open(filename, 'w+b') as result_file: @@ -57,6 +64,7 @@ def save_pdf_summary(self, filename: str) -> None: print('An error occured when generating PDF summary!') def save_sld_plot(self, filename: str) -> None: + """Save sld plot.""" fig = plt.figure() ax = fig.add_subplot(1, 1, 1) @@ -70,6 +78,7 @@ def save_sld_plot(self, filename: str) -> None: plt.close() def save_fit_experiment_plot(self, filename: str) -> None: + """Save fit experiment plot.""" fig = plt.figure() ax = fig.add_subplot(1, 1, 1) legends = [] @@ -92,6 +101,7 @@ def save_fit_experiment_plot(self, filename: str) -> None: plt.close() def _project_information_section(self) -> str: + """Project information section.""" html_project = HTML_PROJECT_INFORMATION_TEMPLATE name = self._project._info['name'] @@ -102,6 +112,7 @@ def _project_information_section(self) -> str: return html_project def _sample_section(self) -> str: + """Sample section.""" html_parameters = [] html_parameter = HTML_PARAMETER_HEADER_TEMPLATE @@ -137,6 +148,7 @@ def _sample_section(self) -> str: return html_parameters_str def _experiments_section(self) -> str: + """Experiments section.""" html_experiments = [] for idx, experiment in self._project.experiments.items(): @@ -163,6 +175,7 @@ def _experiments_section(self) -> str: return html_experiments_str def _refinement_section(self) -> str: + """Refinement section.""" html_refinement = HTML_REFINEMENT_TEMPLATE # Get parameters directly from the model @@ -186,6 +199,7 @@ def _refinement_section(self) -> str: return html_refinement def _figures_section(self) -> None: + """Figures section.""" html_figures = HTML_FIGURES_TEMPLATE path_sld = self._project.path / 'sld_plot.jpg' path_fit_experiment = self._project.path / 'fit_experiment_plot.jpg' diff --git a/src/easyreflectometry/utils.py b/src/easyreflectometry/utils.py index 43be7821..43ac58d8 100644 --- a/src/easyreflectometry/utils.py +++ b/src/easyreflectometry/utils.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from copy import deepcopy from numbers import Number from typing import Optional @@ -14,15 +17,16 @@ def get_as_parameter( default_dict: dict, unique_name_prefix: Optional[str] = None, ) -> Parameter: - """ - This function creates a parameter for the variable `name`. A parameter has a value and metadata. + """This function creates a parameter for the variable `name`. + + A parameter has a value and metadata. If the value already is a parameter, it is returned. - If the value is a number, a parameter is created with this value and metadata from the dictionary. - If the value is None, a parameter is created with the default value and metadata from the dictionary. + If the value is a number, a parameter is created with this value and metadata from the dictionary. + If the value is None, a parameter is created with the default value and metadata from the dictionary. - param value: The value to use for the parameter. If None, the default value in the dictionary is used. - param name: The name of the parameter - param default_dict: Dictionary with entry for `name` containing the default value and metadata for the parameter + param value: The value to use for the parameter. If None, the default value in the dictionary is used. + param name: The name of the parameter + param default_dict: Dictionary with entry for `name` containing the default value and metadata for the parameter """ # This is a parameter, return it if isinstance(value, Parameter): @@ -51,17 +55,17 @@ def get_as_parameter( def yaml_dump(dict_repr: dict) -> str: + """Yaml dump.""" return yaml.dump(dict_repr, sort_keys=False, allow_unicode=True) def collect_unique_names_from_dict(structure_dict: dict, unique_names: Optional[list[str]] = None) -> list[str]: - """ - This function returns a list with the 'unique_name' found the input dictionary. - """ + """This function returns a list with the 'unique_name' found the input dictionary.""" if unique_names is None: unique_names = [] def _collect(item): + """Collect function.""" if isinstance(item, dict): if 'unique_name' in item: unique_names.append(item['unique_name']) @@ -76,12 +80,15 @@ def _collect(item): def count_free_parameters(project) -> int: + """Count free parameters.""" return sum(1 for parameter in project.parameters if parameter.free) def count_fixed_parameters(project) -> int: + """Count fixed parameters.""" return sum(1 for parameter in project.parameters if not parameter.free) def count_parameter_user_constraints(project) -> int: + """Count parameter user constraints.""" return sum(1 for parameter in project.parameters if not parameter.independent) diff --git a/tests/calculators/bornagain/test_bornagain_calculator.py b/tests/calculators/bornagain/test_bornagain_calculator.py index e16cdc70..6a7c1aa9 100644 --- a/tests/calculators/bornagain/test_bornagain_calculator.py +++ b/tests/calculators/bornagain/test_bornagain_calculator.py @@ -1,9 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for BornAgain calculator. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' # import os # import unittest diff --git a/tests/calculators/bornagain/test_bornagain_wrapper.py b/tests/calculators/bornagain/test_bornagain_wrapper.py index 84b2a2d7..c52c084c 100644 --- a/tests/calculators/bornagain/test_bornagain_wrapper.py +++ b/tests/calculators/bornagain/test_bornagain_wrapper.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for the BornAgain wrapper. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - # import unittest # import numpy as np diff --git a/tests/calculators/refl1d/test_refl1d_calculator.py b/tests/calculators/refl1d/test_refl1d_calculator.py index ba8c8d35..50de2db4 100644 --- a/tests/calculators/refl1d/test_refl1d_calculator.py +++ b/tests/calculators/refl1d/test_refl1d_calculator.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Refnx calculator. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - import unittest import numpy as np diff --git a/tests/calculators/refl1d/test_refl1d_wrapper.py b/tests/calculators/refl1d/test_refl1d_wrapper.py index e19dfe42..57c43ee8 100644 --- a/tests/calculators/refl1d/test_refl1d_wrapper.py +++ b/tests/calculators/refl1d/test_refl1d_wrapper.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Refl1d wrapper. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - import unittest from unittest.mock import MagicMock from unittest.mock import patch diff --git a/tests/calculators/refnx/test_refnx_calculator.py b/tests/calculators/refnx/test_refnx_calculator.py index 27283c10..baeb9296 100644 --- a/tests/calculators/refnx/test_refnx_calculator.py +++ b/tests/calculators/refnx/test_refnx_calculator.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Refnx calculator. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - import unittest import numpy as np diff --git a/tests/calculators/refnx/test_refnx_wrapper.py b/tests/calculators/refnx/test_refnx_wrapper.py index 74b50ec9..f9d2a4cc 100644 --- a/tests/calculators/refnx/test_refnx_wrapper.py +++ b/tests/calculators/refnx/test_refnx_wrapper.py @@ -1,11 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Refnx wrapper. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - - import unittest import numpy as np @@ -290,7 +289,7 @@ def test_sld_profile(self): assert_almost_equal(p.sld_profile('MyModel')[1][0], 0) assert_almost_equal(p.sld_profile('MyModel')[1][-1], 4) - ### Tests from https://github.com/reflectivity/analysis/tree/master/validation/test/unpolarised + # Tests from https://github.com/reflectivity/analysis/tree/master/validation/test/unpolarised def test_calculate_github_test0(self): p = RefnxWrapper() p.create_material('Material1') @@ -327,15 +326,13 @@ def test_calculate_github_test0(self): p.add_item('Item4', 'MyModel') p.set_resolution_function(PercentageFwhm(0)) p.update_model('MyModel', bkg=0) - q = np.array( - [ - 5.000000000000000104e-03, - 3.717499999999999971e-02, - 5.449999999999999983e-02, - 1.005349999999999994e-01, - 2.955650000000000222e-01, - ] - ) + q = np.array([ + 5.000000000000000104e-03, + 3.717499999999999971e-02, + 5.449999999999999983e-02, + 1.005349999999999994e-01, + 2.955650000000000222e-01, + ]) expected = [ 9.665000503913141472e-01, 3.486325360684768590e-04, @@ -365,15 +362,13 @@ def test_calculate_github_test2(self): p.add_item('Item2', 'MyModel') p.set_resolution_function(PercentageFwhm(0)) p.update_model('MyModel', bkg=0) - q = np.array( - [ - 5.000000000000000104e-03, - 7.564500000000000390e-02, - 1.433050000000000157e-01, - 2.368350000000000177e-01, - 5.920499999999999652e-01, - ] - ) + q = np.array([ + 5.000000000000000104e-03, + 7.564500000000000390e-02, + 1.433050000000000157e-01, + 2.368350000000000177e-01, + 5.920499999999999652e-01, + ]) expected = [ 1.000000000000000222e00, 1.964576414578978456e-04, diff --git a/tests/data/test_data_store.py b/tests/data/test_data_store.py index ea66f8ff..66ba9b04 100644 --- a/tests/data/test_data_store.py +++ b/tests/data/test_data_store.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from unittest.mock import Mock import numpy as np @@ -29,7 +32,13 @@ def test_constructor_default_values(self): def test_constructor_with_values(self): # When data = DataSet1D( - x=[1, 2, 3], y=[4, 5, 6], ye=[7, 8, 9], xe=[10, 11, 12], x_label='label_x', y_label='label_y', name='MyDataSet1D' + x=[1, 2, 3], + y=[4, 5, 6], + ye=[7, 8, 9], + xe=[10, 11, 12], + x_label='label_x', + y_label='label_y', + name='MyDataSet1D', ) # Then diff --git a/tests/functional/test_dummy.py b/tests/functional/test_dummy.py new file mode 100644 index 00000000..6927fe89 --- /dev/null +++ b/tests/functional/test_dummy.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +def test_dummy(): + calculated = 2 + 2 + expected = 4 + assert calculated == expected diff --git a/tests/integration/fitting/test_dummy.py b/tests/integration/fitting/test_dummy.py new file mode 100644 index 00000000..2256189c --- /dev/null +++ b/tests/integration/fitting/test_dummy.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +@pytest.mark.fast +def test_dummy_fast(): + calculated = 2 + 2 + expected = 4 + assert calculated == expected + + +def test_dummy_slow(): + calculated = sum(i * j for i in range(10000) for j in range(10000)) + expected = 2499500025000000 + assert calculated == expected diff --git a/tests/integration/scipp-analysis/test_dummy.py b/tests/integration/scipp-analysis/test_dummy.py new file mode 100644 index 00000000..2256189c --- /dev/null +++ b/tests/integration/scipp-analysis/test_dummy.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +import pytest + + +@pytest.mark.fast +def test_dummy_fast(): + calculated = 2 + 2 + expected = 4 + assert calculated == expected + + +def test_dummy_slow(): + calculated = sum(i * j for i in range(10000) for j in range(10000)) + expected = 2499500025000000 + assert calculated == expected diff --git a/tests/model/test_model.py b/tests/model/test_model.py index 49ce60fc..2d623512 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Model class. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - import unittest from unittest.mock import MagicMock diff --git a/tests/model/test_model_collection.py b/tests/model/test_model_collection.py index cc98534d..4a6fd521 100644 --- a/tests/model/test_model_collection.py +++ b/tests/model/test_model_collection.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from easyscience import global_object from easyreflectometry.model.model import COLORS @@ -113,7 +116,10 @@ def test_as_dict(self): dict_repr = collection.as_dict() # Expect - assert dict_repr['data'][0]['resolution_function'] == {'smearing': 'PercentageFwhm', 'constant': 5.0} + assert dict_repr['data'][0]['resolution_function'] == { + 'smearing': 'PercentageFwhm', + 'constant': 5.0, + } def test_dict_round_trip(self): # When diff --git a/tests/model/test_resolution_functions.py b/tests/model/test_resolution_functions.py index 6ff8afd1..e28a99f2 100644 --- a/tests/model/test_resolution_functions.py +++ b/tests/model/test_resolution_functions.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import unittest import numpy as np @@ -64,7 +67,11 @@ def test_as_dict(self): resolution_function = LinearSpline(q_data_points=[0, 10], fwhm_values=[5, 10]) # Then Expect - resolution_function.as_dict() == {'smearing': 'LinearSpline', 'q_data_points': [0, 10], 'fwhm_values': [5, 10]} + resolution_function.as_dict() == { + 'smearing': 'LinearSpline', + 'q_data_points': [0, 10], + 'fwhm_values': [5, 10], + } def test_dict_round_trip(self): # When @@ -90,7 +97,8 @@ def test_constructor(self): # Then Expect assert np.allclose( - np.array(resolution_function.smearing()), np.array([2.51664683, 2.84038734, 3.2460762, 3.6796519, 4.07869271]) + np.array(resolution_function.smearing()), + np.array([2.51664683, 2.84038734, 3.2460762, 3.6796519, 4.07869271]), ) def test_as_dict(self): diff --git a/tests/package_test.py b/tests/package_test.py index 4d77705c..f18f84f8 100644 --- a/tests/package_test.py +++ b/tests/package_test.py @@ -1,5 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2024 Easyscience contributors (https://github.com/EasyScience) + import easyreflectometry as pkg diff --git a/tests/sample/assemblies/test_base_assembly.py b/tests/sample/assemblies/test_base_assembly.py index 178f6382..482cf669 100644 --- a/tests/sample/assemblies/test_base_assembly.py +++ b/tests/sample/assemblies/test_base_assembly.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for BaseAssembly class module """ diff --git a/tests/sample/assemblies/test_bilayer.py b/tests/sample/assemblies/test_bilayer.py index b96e6a78..03949ee7 100644 --- a/tests/sample/assemblies/test_bilayer.py +++ b/tests/sample/assemblies/test_bilayer.py @@ -1,9 +1,11 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Bilayer class module """ __author__ = 'github.com/easyscience' -__version__ = '0.0.1' from easyscience import global_object diff --git a/tests/sample/assemblies/test_gradient_layer.py b/tests/sample/assemblies/test_gradient_layer.py index 3b88df47..7efda4a9 100644 --- a/tests/sample/assemblies/test_gradient_layer.py +++ b/tests/sample/assemblies/test_gradient_layer.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for GradientLayer class module """ @@ -168,7 +171,11 @@ def test_prepare_gradient_layers(monkeypatch): mock_LayerCollection = MagicMock() mock_Material = MagicMock(return_value='Material_from_mock') mock_linear_gradient = MagicMock(return_value=[1.0, 2.0, 3.0]) - monkeypatch.setattr(easyreflectometry.sample.assemblies.gradient_layer, '_linear_gradient', mock_linear_gradient) + monkeypatch.setattr( + easyreflectometry.sample.assemblies.gradient_layer, + '_linear_gradient', + mock_linear_gradient, + ) monkeypatch.setattr(easyreflectometry.sample.assemblies.gradient_layer, 'Layer', mock_Layer) monkeypatch.setattr(easyreflectometry.sample.assemblies.gradient_layer, 'Material', mock_Material) monkeypatch.setattr(easyreflectometry.sample.assemblies.gradient_layer, 'LayerCollection', mock_LayerCollection) diff --git a/tests/sample/assemblies/test_multilayer.py b/tests/sample/assemblies/test_multilayer.py index 82f807a4..f2d7bd61 100644 --- a/tests/sample/assemblies/test_multilayer.py +++ b/tests/sample/assemblies/test_multilayer.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for MultiLayer class module """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - import unittest from easyscience import global_object diff --git a/tests/sample/assemblies/test_repeating_multilayer.py b/tests/sample/assemblies/test_repeating_multilayer.py index 6eb17d0a..3ee2858b 100644 --- a/tests/sample/assemblies/test_repeating_multilayer.py +++ b/tests/sample/assemblies/test_repeating_multilayer.py @@ -1,11 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for RepeatingMultiLayer module """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - - import unittest from easyscience import global_object diff --git a/tests/sample/assemblies/test_surfactant_layer.py b/tests/sample/assemblies/test_surfactant_layer.py index 3a0549cd..62b92689 100644 --- a/tests/sample/assemblies/test_surfactant_layer.py +++ b/tests/sample/assemblies/test_surfactant_layer.py @@ -1,11 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for SurfactantLayer class module """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - - import unittest from easyscience import global_object @@ -111,7 +110,12 @@ def test_dict_repr(self): 'solvent_fraction': '0.200 dimensionless', 'sld': '2.269e-6 1/Å^2', 'isld': '0.000e-6 1/Å^2', - 'material': {'C10H18NO8P': {'sld': '1.246e-6 1/Å^2', 'isld': '0.000e-6 1/Å^2'}}, + 'material': { + 'C10H18NO8P': { + 'sld': '1.246e-6 1/Å^2', + 'isld': '0.000e-6 1/Å^2', + } + }, 'solvent': {'D2O': {'sld': '6.360e-6 1/Å^2', 'isld': '0.000e-6 1/Å^2'}}, } }, diff --git a/tests/sample/collections/test_base_collection.py b/tests/sample/collections/test_base_collection.py index b9ad429e..44b8e208 100644 --- a/tests/sample/collections/test_base_collection.py +++ b/tests/sample/collections/test_base_collection.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from unittest.mock import MagicMock from easyreflectometry.sample.collections.base_collection import BaseCollection diff --git a/tests/sample/collections/test_layer_collection.py b/tests/sample/collections/test_layer_collection.py index 543e9235..7eb8071b 100644 --- a/tests/sample/collections/test_layer_collection.py +++ b/tests/sample/collections/test_layer_collection.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for LayerCollection class. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - import unittest from easyscience import global_object diff --git a/tests/sample/collections/test_material_collection.py b/tests/sample/collections/test_material_collection.py index 25f30b59..3ca9f22b 100644 --- a/tests/sample/collections/test_material_collection.py +++ b/tests/sample/collections/test_material_collection.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for LayerCollection class. """ diff --git a/tests/sample/collections/test_sample.py b/tests/sample/collections/test_sample.py index 3cb1d0c1..6f64dd25 100644 --- a/tests/sample/collections/test_sample.py +++ b/tests/sample/collections/test_sample.py @@ -1,11 +1,10 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Sample class. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - - import pytest from easyscience import global_object from numpy.testing import assert_equal diff --git a/tests/sample/elements/layers/test_layer.py b/tests/sample/elements/layers/test_layer.py index 1cbecb17..26a5cf0a 100644 --- a/tests/sample/elements/layers/test_layer.py +++ b/tests/sample/elements/layers/test_layer.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Layer class. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - import unittest import numpy as np diff --git a/tests/sample/elements/layers/test_layer_area_per_molecule.py b/tests/sample/elements/layers/test_layer_area_per_molecule.py index 505eec5d..4d0abf7d 100644 --- a/tests/sample/elements/layers/test_layer_area_per_molecule.py +++ b/tests/sample/elements/layers/test_layer_area_per_molecule.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for LayerAreaPerMolecule class. """ diff --git a/tests/sample/elements/materials/test_material.py b/tests/sample/elements/materials/test_material.py index c07a2217..997102a4 100644 --- a/tests/sample/elements/materials/test_material.py +++ b/tests/sample/elements/materials/test_material.py @@ -1,10 +1,10 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Tests for Material class. """ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' - from easyscience import global_object from easyreflectometry.sample.elements.materials.material import DEFAULTS diff --git a/tests/sample/elements/materials/test_material_density.py b/tests/sample/elements/materials/test_material_density.py index d1945e1e..99c6615a 100644 --- a/tests/sample/elements/materials/test_material_density.py +++ b/tests/sample/elements/materials/test_material_density.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import unittest import numpy as np diff --git a/tests/sample/elements/materials/test_material_mixture.py b/tests/sample/elements/materials/test_material_mixture.py index 423bfb2d..0e70a2b7 100644 --- a/tests/sample/elements/materials/test_material_mixture.py +++ b/tests/sample/elements/materials/test_material_mixture.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from unittest.mock import MagicMock from easyscience import global_object diff --git a/tests/sample/elements/materials/test_material_solvated.py b/tests/sample/elements/materials/test_material_solvated.py index a50211d5..3d4a9917 100644 --- a/tests/sample/elements/materials/test_material_solvated.py +++ b/tests/sample/elements/materials/test_material_solvated.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from unittest.mock import MagicMock import pytest diff --git a/tests/special/test_calculations.py b/tests/special/test_calculations.py index e7f1f89f..10ca1ca1 100644 --- a/tests/special/test_calculations.py +++ b/tests/special/test_calculations.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2022 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import unittest diff --git a/tests/summary/test_summary.py b/tests/summary/test_summary.py index 3bbb186e..8a8f581f 100644 --- a/tests/summary/test_summary.py +++ b/tests/summary/test_summary.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import os from unittest.mock import MagicMock diff --git a/tests/test_data.py b/tests/test_data.py index 0ee95d94..d08402f2 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,5 +1,6 @@ -__author__ = 'github.com/arm61' -__version__ = '0.0.1' +# SPDX-FileCopyrightText: 2022 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import os import unittest @@ -224,7 +225,10 @@ def test_load_txt_three_columns(self): assert coords_name in er_data['coords'] # xe should be zeros for 3-column file - assert_almost_equal(er_data['coords'][coords_name].variances, np.zeros_like(er_data['coords'][coords_name].values)) + assert_almost_equal( + er_data['coords'][coords_name].variances, + np.zeros_like(er_data['coords'][coords_name].values), + ) def test_load_txt_with_zero_errors(self): fpath = os.path.join(PATH_STATIC, 'ref_zero_var.txt') diff --git a/tests/test_fitting.py b/tests/test_fitting.py index 2a03a98c..28dfe0fa 100644 --- a/tests/test_fitting.py +++ b/tests/test_fitting.py @@ -1,4 +1,6 @@ -__author__ = 'github.com/arm61' +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import os from unittest.mock import MagicMock @@ -175,12 +177,10 @@ def test_fitting_with_manual_zero_variance(): variances[30:32] = 0.0 # 2 more zero variance points # Create scipp DataGroup manually - data = sc.DataGroup( - { - 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=qz_values)}, - 'data': {'R_0': sc.array(dims=['Qz_0'], values=r_values, variances=variances)}, - } - ) + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=qz_values)}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=r_values, variances=variances)}, + }) # Create a simple model for fitting si = Material(2.07, 0, 'Si') @@ -428,7 +428,12 @@ def test_prepare_fit_arrays_legacy_mask_drops_zero_variance(): assert np.allclose(x_out, [0.01, 0.03]) assert np.allclose(y_eff, [1.0, 0.6]) assert np.allclose(weights, [1.0 / np.sqrt(0.01), 1.0 / np.sqrt(0.04)]) - assert stats == {'valid': 2, 'mighell_substituted': 0, 'masked': 1, 'transformed_all_points': False} + assert stats == { + 'valid': 2, + 'mighell_substituted': 0, + 'masked': 1, + 'transformed_all_points': False, + } def test_prepare_fit_arrays_hybrid_transforms_zero_variance(): @@ -449,7 +454,12 @@ def test_prepare_fit_arrays_hybrid_transforms_zero_variance(): assert y_eff[1] == pytest.approx(0.8 + 0.8) # sigma = sqrt(y + 1) = sqrt(1.8) assert weights[1] == pytest.approx(1.0 / np.sqrt(1.8)) - assert stats == {'valid': 2, 'mighell_substituted': 1, 'masked': 0, 'transformed_all_points': False} + assert stats == { + 'valid': 2, + 'mighell_substituted': 1, + 'masked': 0, + 'transformed_all_points': False, + } def test_prepare_fit_arrays_mighell_transforms_all(): @@ -466,7 +476,12 @@ def test_prepare_fit_arrays_mighell_transforms_all(): # sigma = sqrt(y + 1) assert weights[0] == pytest.approx(1.0 / np.sqrt(1.5)) assert weights[1] == pytest.approx(1.0 / np.sqrt(1.3)) - assert stats == {'valid': 0, 'mighell_substituted': 2, 'masked': 0, 'transformed_all_points': True} + assert stats == { + 'valid': 0, + 'mighell_substituted': 2, + 'masked': 0, + 'transformed_all_points': True, + } def test_fit_single_data_set_1d_hybrid_keeps_zero_variance_points(): @@ -553,13 +568,17 @@ def test_classical_and_objective_chi_are_split_for_fit_results(): fitter._models = [MagicMock(unique_name='model_0', as_dict=MagicMock(return_value={'name': 'model_0'}))] fitter._fit_func = [lambda x: np.array([0.8, 0.75, 0.7])] - data = sc.DataGroup( - { - 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.array([0.01, 0.02, 0.03]), unit=sc.Unit('1/angstrom'))}, - 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.array([1.0, 0.9, 0.7]), variances=np.array([0.01, 0.0, 0.04]))}, - 'attrs': {}, - } - ) + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.array([0.01, 0.02, 0.03]), unit=sc.Unit('1/angstrom'))}, + 'data': { + 'R_0': sc.array( + dims=['Qz_0'], + values=np.array([1.0, 0.9, 0.7]), + variances=np.array([0.01, 0.0, 0.04]), + ) + }, + 'attrs': {}, + }) analysed = fitter.fit(data) @@ -654,12 +673,10 @@ def test_fit_multi_dataset_hybrid_uses_transformed_y_and_weights(): variances = np.ones_like(r_values) * 0.01 variances[3:5] = 0.0 # 2 zero-variance points - data = sc.DataGroup( - { - 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=qz_values)}, - 'data': {'R_0': sc.array(dims=['Qz_0'], values=r_values, variances=variances)}, - } - ) + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=qz_values)}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=r_values, variances=variances)}, + }) model = Model() model.interface = CalculatorFactory() diff --git a/tests/test_limits.py b/tests/test_limits.py index e8320eb7..2fd1cc74 100644 --- a/tests/test_limits.py +++ b/tests/test_limits.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import numpy as np import pytest from easyscience import global_object diff --git a/tests/test_measurement_comprehensive.py b/tests/test_measurement_comprehensive.py index d6bb0c46..baffb2b8 100644 --- a/tests/test_measurement_comprehensive.py +++ b/tests/test_measurement_comprehensive.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ Comprehensive tests for measurement and data store functionality. Tests for all functions in measurement.py and data_store.py modules. @@ -113,7 +116,10 @@ def test_load_txt_handles_three_columns(self): coords_key = list(result['coords'].keys())[0] # xe should be zeros - assert_array_equal(result['coords'][coords_key].variances, np.zeros_like(result['coords'][coords_key].values)) + assert_array_equal( + result['coords'][coords_key].variances, + np.zeros_like(result['coords'][coords_key].values), + ) def test_load_txt_with_insufficient_columns(self): """Test that _load_txt raises error for files with too few columns.""" @@ -159,7 +165,16 @@ def test_constructor_all_parameters(self): xe = [0.1, 0.1, 0.1, 0.1] ye = [1, 2, 3, 4] - dataset = DataSet1D(name='TestData', x=x, y=y, xe=xe, ye=ye, x_label='Q (Å⁻¹)', y_label='Reflectivity', model=None) + dataset = DataSet1D( + name='TestData', + x=x, + y=y, + xe=xe, + ye=ye, + x_label='Q (Å⁻¹)', + y_label='Reflectivity', + model=None, + ) assert dataset.name == 'TestData' assert_array_equal(dataset.x, np.array(x)) @@ -233,7 +248,8 @@ def test_datastore_as_sequence(self): assert store[0].name == 'item2' def test_datastore_experiments_and_simulations_filtering(self): - """Test experiments and simulations properties filter correctly.""" + """Test experiments and simulations properties + filter correctly.""" exp1 = DataSet1D(name='exp1', x=[1], y=[2], model=Mock()) exp2 = DataSet1D(name='exp2', x=[3], y=[4], model=Mock()) sim1 = DataSet1D(name='sim1', x=[5], y=[6]) @@ -264,7 +280,8 @@ class TestProjectDataComprehensive: """Comprehensive tests for ProjectData class.""" def test_project_data_initialization(self): - """Test ProjectData initializes with correct default values.""" + """Test ProjectData initializes with correct + default values.""" project = ProjectData() assert project.name == 'DataStore' @@ -274,7 +291,8 @@ def test_project_data_initialization(self): assert project.sim_data.name == 'Sim Datastore' def test_project_data_with_custom_stores(self): - """Test ProjectData with custom experiment and simulation stores.""" + """Test ProjectData with custom experiment and + simulation stores.""" custom_exp = DataStore(name='CustomExp') custom_sim = DataStore(name='CustomSim') @@ -303,7 +321,8 @@ class TestIntegrationScenarios: """Integration tests for common usage scenarios.""" def test_complete_workflow_orso_file(self): - """Test complete workflow: load ORSO file -> create dataset -> store in project.""" + """Test complete workflow: load ORSO file + -> create dataset -> store in project.""" # Load file fpath = os.path.join(PATH_STATIC, 'test_example1.ort') dataset = load_as_dataset(fpath) @@ -318,7 +337,8 @@ def test_complete_workflow_orso_file(self): assert isinstance(project.exp_data[0], DataSet1D) def test_complete_workflow_txt_file(self): - """Test complete workflow: load txt file -> create dataset -> store in project.""" + """Test complete workflow: load txt file -> + create dataset -> store in project.""" # Load file fpath = os.path.join(PATH_STATIC, 'ref_concat_1.txt') dataset = load_as_dataset(fpath) diff --git a/tests/test_orso_utils.py b/tests/test_orso_utils.py index ebb662a1..4ad2e9ab 100644 --- a/tests/test_orso_utils.py +++ b/tests/test_orso_utils.py @@ -1,5 +1,5 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2025 DMSC import os import warnings @@ -91,10 +91,12 @@ def test_load_data_from_orso_file(): def test_orso_sld_unit_conversion(orso_data): - """Test that SLD values from ORSO are correctly converted from A^-2 to 10^-6 A^-2. + """Test that SLD values from ORSO are correctly converted + from A^-2 to 10^-6 A^-2. ORSO stores SLD in absolute units (A^-2), e.g., 3.47e-06. - The internal representation uses 10^-6 A^-2, so the value should be 3.47. + The internal representation uses 10^-6 A^-2, + so the value should be 3.47. """ sample = load_orso_model(orso_data) @@ -114,9 +116,7 @@ def test_orso_sld_unit_conversion(orso_data): subphase = sample[2] si_layer = subphase.layers[0] assert si_layer.material.name == 'Si' - assert abs(si_layer.material.sld.value - 2.07) < 1e-6, ( - f'Expected SLD ~2.07 (10^-6 A^-2), got {si_layer.material.sld.value}' - ) + assert abs(si_layer.material.sld.value - 2.07) < 1e-6, f'Expected SLD ~2.07 (10^-6 A^-2), got {si_layer.material.sld.value}' # Check air superphase layer # ORSO file has: sld: {real: 0.0, imag: 0.0} @@ -152,7 +152,8 @@ def test_LoadOrso_with_nonexistent_file(): def test_get_sld_values_defaults_to_zero_when_sld_and_density_missing(): - """_get_sld_values should return (0.0, 0.0) when both sld and mass_density are None.""" + """_get_sld_values should return (0.0, 0.0) when both + sld and mass_density are None.""" material = SimpleNamespace(sld=None, mass_density=None) m_sld, m_isld = _get_sld_values(material, 'Unknown') assert m_sld == 0.0 @@ -160,7 +161,8 @@ def test_get_sld_values_defaults_to_zero_when_sld_and_density_missing(): def test_load_orso_model_returns_none_and_warns_when_no_sample_model(): - """load_orso_model should return None and emit a warning when the ORSO file has no sample model.""" + """load_orso_model should return None and emit a warning + when the ORSO file has no sample model.""" orso_data = orso.load_orso(os.path.join(PATH_STATIC, 'test_example1.ort')) # Verify the file indeed has no model assert orso_data[0].info.data_source.sample.model is None diff --git a/tests/test_ort_file.py b/tests/test_ort_file.py index 8ef1de16..b1277033 100644 --- a/tests/test_ort_file.py +++ b/tests/test_ort_file.py @@ -1,5 +1,5 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2025 DMSC import logging @@ -154,7 +154,8 @@ def test_validate_physical_data__r_values_ureal_positive(load_data): for val_a, val_b in zip(a, b): if val_a > val_b: pytest.warns( - UserWarning, reason=f'Reflectivity value {val_a} is unphysically large compared to its uncertainty {val_b}' + UserWarning, + reason=f'Reflectivity value {val_a} is unphysically large compared to its uncertainty {val_b}', ) assert all(load_data['data']['R_0'].values <= 1 + 2 * np.sqrt(load_data['data']['R_0'].variances)) diff --git a/tests/test_parameter_utils.py b/tests/test_parameter_utils.py index d8d2ce97..72e3bbc0 100644 --- a/tests/test_parameter_utils.py +++ b/tests/test_parameter_utils.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import numpy as np import pytest from numpy.testing import assert_equal diff --git a/tests/test_project.py b/tests/test_project.py index 3c390960..73aeca85 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + import datetime import os from pathlib import Path @@ -133,7 +136,8 @@ def remove_interface(d): models_dict['unique_name'] = 'project_models' remove_interface(project_models_dict) remove_interface(models_dict) - # Since as_dict may not include unique_name, remove it for comparison + # Since as_dict may not include unique_name, + # remove it for comparison for d in [project_models_dict, models_dict]: if 'unique_name' in d: del d['unique_name'] @@ -171,7 +175,12 @@ def test_sld_data_for_model_at_index(self): assert len(sample_data.x) == 500 assert_allclose( np.array([4.6119497e-08, 6.3189932e00, 6.3350000e00, 2.0740000e00]), - np.array([sample_data.y[0], sample_data.y[100], sample_data.y[300], sample_data.y[499]]), + np.array([ + sample_data.y[0], + sample_data.y[100], + sample_data.y[300], + sample_data.y[499], + ]), ) def test_sample_data_for_model_at_index(self): @@ -200,7 +209,12 @@ def test_model_data_for_model_at_index(self): # Expect assert len(model_data.y) == 4 assert_allclose( - np.array([0.9738701849233727, 0.0017678986451491123, 0.00016581714423990004, 3.3290653551465554e-08]), + np.array([ + 0.9738701849233727, + 0.0017678986451491123, + 0.00016581714423990004, + 3.3290653551465554e-08, + ]), model_data.y, ) @@ -558,7 +572,8 @@ def test_load_from_json(self, tmp_path): # Then new_project.load_from_json(tmp_path / 'name' / 'project.json') - # Do it twice to ensure that potential global objects don't collide + # Do it twice to ensure that potential + # global objects don't collide new_project.load_from_json(tmp_path / 'name' / 'project.json') # Expect @@ -904,11 +919,13 @@ def test_add_sample_from_orso_with_shared_materials(self): # Expect - shared material should not be duplicated assert len(project._models) == 2 - # The shared material instance is already in the collection, so count should stay the same + # The shared material instance is already in the collection, + # so count should stay the same assert len(project._materials) == initial_material_count def test_replace_models_from_orso(self): - """Test that replace_models_from_orso replaces all existing models with a single new model.""" + """Test that replace_models_from_orso replaces all existing + models with a single new model.""" # When global_object.map._clear() project = Project() @@ -1077,7 +1094,12 @@ def test_remove_model_at_index_removes_experiment_at_same_index(self): project._models.append(model) # Add experiment linked to model 0 experiment = DataSet1D( - name='exp0', x=[0.01, 0.02], y=[1.0, 0.5], ye=[0.1, 0.1], xe=[0.001, 0.001], model=project._models[0] + name='exp0', + x=[0.01, 0.02], + y=[1.0, 0.5], + ye=[0.1, 0.1], + xe=[0.001, 0.001], + model=project._models[0], ) project._experiments[0] = experiment diff --git a/tests/test_topmost_nesting.py b/tests/test_topmost_nesting.py index 5c38ec0b..622991b2 100644 --- a/tests/test_topmost_nesting.py +++ b/tests/test_topmost_nesting.py @@ -1,5 +1,8 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + """ -Tests exercising the methods of the topmost classes for nested structure. +Testing the methods of the topmost classes for nested structure. To ensure that the parameters are relayed. """ diff --git a/tests/test_utils.py b/tests/test_utils.py index 64bd7791..4d9c4828 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + from easyreflectometry import Project from easyreflectometry.utils import count_fixed_parameters from easyreflectometry.utils import count_free_parameters @@ -27,16 +30,3 @@ def test_count_fixed_parameters(): # Expect assert count == 13 - - -def test_count_parameter_user_constraints(): - # When - project = Project() - project.default_model() - # project.parameters[0].user_constraints['name_other_parameter'] = 'constraint' - - # # Then - # count = count_parameter_user_constraints(project) - - # # Expect - # assert count == 1 diff --git a/tests/unit/test_dummy.py b/tests/unit/test_dummy.py new file mode 100644 index 00000000..6927fe89 --- /dev/null +++ b/tests/unit/test_dummy.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +def test_dummy(): + calculated = 2 + 2 + expected = 4 + assert calculated == expected diff --git a/tools/license_headers.py b/tools/license_headers.py new file mode 100644 index 00000000..f276ca1b --- /dev/null +++ b/tools/license_headers.py @@ -0,0 +1,321 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Add, remove, or check SPDX headers in Python files.""" + +from __future__ import annotations + +import argparse +import fnmatch +import tomllib +from datetime import datetime +from pathlib import Path +from typing import Any +from typing import Optional +from typing import Union + +from git import Repo +from spdx_headers.core import find_repository_root +from spdx_headers.core import get_copyright_info +from spdx_headers.core import has_spdx_header +from spdx_headers.data import load_license_data +from spdx_headers.operations import add_header_to_single_file +from spdx_headers.operations import remove_header_from_single_file + +LICENSE_DATABASE = load_license_data() + + +def load_pyproject(repo_path: Union[str, Path]) -> dict[str, Any]: + """ + Load and return parsed ``pyproject.toml`` data for the repository. + """ + repo_root = find_repository_root(repo_path) + pyproject_path = repo_root / 'pyproject.toml' + + with pyproject_path.open('rb') as file_handle: + return tomllib.load(file_handle) + + +def get_pyproject_value(pyproject_data: dict[str, Any], dotted_key: str) -> Any: + """Return a nested ``pyproject.toml`` value from a dotted key.""" + value: Any = pyproject_data + for part in dotted_key.split('.'): + if not isinstance(value, dict) or part not in value: + raise KeyError(dotted_key) + value = value[part] + return value + + +def normalize_pattern(pattern: str) -> str: + """Normalize an exclude pattern to a POSIX-style relative path.""" + normalized = Path(pattern).as_posix() + if normalized.startswith('./'): + normalized = normalized[2:] + return normalized.rstrip('/') + + +def get_exclude_patterns( + repo_path: Union[str, Path], + exclude_values: list[str], + exclude_from_pyproject_toml: Optional[str], +) -> list[str]: + """ + Return normalized exclude patterns from CLI and ``pyproject.toml``. + """ + pyproject_data = load_pyproject(repo_path) + patterns: list[str] = [] + + if exclude_from_pyproject_toml: + value = get_pyproject_value(pyproject_data, exclude_from_pyproject_toml) + if not isinstance(value, list) or not all(isinstance(item, str) for item in value): + raise ValueError( + f'{exclude_from_pyproject_toml} in pyproject.toml must be a list of strings.', + ) + patterns.extend(value) + + for item in exclude_values: + try: + value = get_pyproject_value(pyproject_data, item) + except KeyError: + patterns.append(item) + continue + + if not isinstance(value, list) or not all(isinstance(entry, str) for entry in value): + raise ValueError(f'{item} in pyproject.toml must be a list of strings.') + patterns.extend(value) + + normalized_patterns: list[str] = [] + seen: set[str] = set() + for pattern in patterns: + normalized = normalize_pattern(pattern) + if normalized and normalized not in seen: + normalized_patterns.append(normalized) + seen.add(normalized) + + return normalized_patterns + + +def get_file_creation_year(file_path: Union[str, Path]) -> str: + """Return the year the file was first added to Git history. + + If the year cannot be determined, fall back to the current year. + """ + file_path = Path(file_path) + + repo = Repo(file_path, search_parent_directories=True) + root = Path(repo.working_tree_dir).resolve() + rel_path = file_path.resolve().relative_to(root) + + rel_path_git = rel_path.as_posix() + + log_output = repo.git.log( + '--follow', + '--diff-filter=A', + '--reverse', + '--format=%ad', + '--date=format:%Y', + '--', + rel_path_git, + ).strip() + + year = log_output.splitlines()[0].strip() if log_output else '' + + return year or str(datetime.now().year) + + +def get_org_url(repo_path: Union[str, Path]) -> str: + """ + Return the organization URL derived from the repository source URL. + """ + pyproject_data = load_pyproject(repo_path) + repo_url = pyproject_data['project']['urls']['Source Code'] + return repo_url.rsplit('/', 1)[0] + + +def get_project_license(repo_path: Union[str, Path]) -> str: + """Return the project license value from ``pyproject.toml``.""" + pyproject_data = load_pyproject(repo_path) + return pyproject_data['project']['license'] + + +def get_copyright_holder(repo_path: Union[str, Path]) -> str: + """Return the repository copyright holder name.""" + _, name, _ = get_copyright_info(repo_path) + return name + + +def add_spdx_header( + target_file: Union[str, Path], + *, + license_key: str, + copyright_holder: str, + org_url: str, +) -> None: + """Add SPDX headers to one file.""" + year = get_file_creation_year(target_file) + + add_header_to_single_file( + filepath=target_file, + license_key=license_key, + license_data=LICENSE_DATABASE, + year=year, + name=copyright_holder, + email=org_url, + ) + + +def is_excluded(relative_path: str, exclude_patterns: list[str]) -> bool: + """Return whether a relative path should be excluded.""" + for pattern in exclude_patterns: + if fnmatch.fnmatch(relative_path, pattern): + return True + if relative_path == pattern: + return True + if relative_path.startswith(f'{pattern}/'): + return True + return False + + +def iter_python_files( + paths: list[str], + *, + repo_root: Path, + exclude_patterns: list[str], + parser: argparse.ArgumentParser, +) -> list[Path]: + """Collect Python files under the given paths after exclusions.""" + files: list[Path] = [] + seen: set[Path] = set() + + for base_dir in paths: + base_path = Path(base_dir) + if not base_path.exists(): + parser.error(f'Path does not exist: {base_dir}') + + if base_path.is_file(): + candidates = [base_path] if base_path.suffix == '.py' else [] + else: + candidates = sorted(base_path.rglob('*.py')) + + for py_file in candidates: + resolved = py_file.resolve() + try: + relative_path = resolved.relative_to(repo_root).as_posix() + except ValueError: + relative_path = py_file.as_posix() + + if is_excluded(relative_path, exclude_patterns): + continue + + if resolved not in seen: + files.append(py_file) + seen.add(resolved) + + return files + + +def run_add( + files: list[Path], + *, + license_key: str, + copyright_holder: str, + org_url: str, +) -> int: + """Add SPDX headers to all selected files.""" + for py_file in files: + add_spdx_header( + py_file, + license_key=license_key, + copyright_holder=copyright_holder, + org_url=org_url, + ) + return 0 + + +def run_remove(files: list[Path]) -> int: + """Remove SPDX headers from all selected files.""" + for py_file in files: + remove_header_from_single_file(py_file) + return 0 + + +def run_check(files: list[Path]) -> int: + """Check SPDX headers in all selected files.""" + missing_files = [py_file for py_file in files if not has_spdx_header(py_file)] + + if not missing_files: + print('✓ All Python files have valid SPDX headers.') + return 0 + + print('✗ The following files are missing SPDX headers:') + for py_file in missing_files: + print(f' - {py_file.as_posix()}') + print(f'\nFound {len(missing_files)} files without SPDX headers.') + return 1 + + +def build_parser() -> argparse.ArgumentParser: + """Build the CLI argument parser.""" + parser = argparse.ArgumentParser( + description='Add, remove, or check SPDX headers in Python files.', + ) + subparsers = parser.add_subparsers(dest='command', required=True) + + for command_name in ('check', 'remove', 'add'): + command_parser = subparsers.add_parser(command_name) + command_parser.add_argument( + 'paths', + nargs='+', + help='Relative paths to scan (e.g. src tests)', + ) + command_parser.add_argument( + '--exclude', + nargs='*', + default=[], + help='Exclude paths, glob patterns, or pyproject dotted keys.', + ) + command_parser.add_argument( + '--exclude-from-pyproject-toml', + help='Read exclude patterns from a dotted key in pyproject.toml.', + ) + + return parser + + +def main(argv: Optional[list[str]] = None) -> int: + """Run the SPDX header CLI.""" + parser = build_parser() + args = parser.parse_args(argv) + + repo_path = Path('.').resolve() + repo_root = find_repository_root(repo_path).resolve() + exclude_patterns = get_exclude_patterns( + repo_path, + args.exclude, + args.exclude_from_pyproject_toml, + ) + files = iter_python_files( + args.paths, + repo_root=repo_root, + exclude_patterns=exclude_patterns, + parser=parser, + ) + + if args.command == 'check': + return run_check(files) + + if args.command == 'remove': + return run_remove(files) + + license_key = get_project_license(repo_path) + copyright_holder = get_copyright_holder(repo_path) + org_url = get_org_url(repo_path) + return run_add( + files, + license_key=license_key, + copyright_holder=copyright_holder, + org_url=org_url, + ) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tools/update_docs_assets.py b/tools/update_docs_assets.py new file mode 100644 index 00000000..d375093c --- /dev/null +++ b/tools/update_docs_assets.py @@ -0,0 +1,91 @@ +""" +Update documentation assets from the assets-branding repository. + +This script fetches branding assets (logos, icons, images) from the +easyscience/assets-branding GitHub repository and copies them to the +appropriate locations in the documentation directory. +""" + +import shutil +from pathlib import Path + +import pooch + +# Configuration: Define what to fetch and where to copy +GITHUB_REPO = 'easyscience/assets-branding' +GITHUB_BRANCH = 'master' +BASE_URL = f'https://raw.githubusercontent.com/{GITHUB_REPO}/refs/heads/{GITHUB_BRANCH}' +PROJECT_NAME = 'easyreflectometry' + +# Mapping of source files to destination paths +# Format: "source_path_in_repo": "destination_path_in_project" +ASSETS_MAP = { + # Logos + f'{PROJECT_NAME}/logos/dark.svg': 'docs/docs/assets/images/logo_dark.svg', + f'{PROJECT_NAME}/logos/light.svg': 'docs/docs/assets/images/logo_light.svg', + # Favicon + f'{PROJECT_NAME}/icons/color.png': 'docs/docs/assets/images/favicon.png', + # Icon overrides + f'{PROJECT_NAME}/icons/bw.svg': f'docs/overrides/.icons/{PROJECT_NAME}.svg', + 'easyscience-org/icons/eso-icon_bw.svg': 'docs/overrides/.icons/easyscience.svg', +} + + +def fetch_and_copy_asset( + source_path: str, + dest_path: str, + cache_dir: Path, +) -> None: + """ + Fetch an asset from GitHub and copy it to the destination. + + Args: + source_path: Path to the file in the GitHub repository + dest_path: Destination path in the project + cache_dir: Directory to cache downloaded files + """ + url = f'{BASE_URL}/{source_path}' + + # Create a unique cache filename based on source path + cache_filename = source_path.replace('/', '_') + + # Download file using pooch + file_path = pooch.retrieve( + url=url, + known_hash=None, # Skip hash verification + path=cache_dir, + fname=cache_filename, + ) + + # Create destination directory if it doesn't exist + dest = Path(dest_path) + dest.parent.mkdir(parents=True, exist_ok=True) + + # Copy the file to destination + shutil.copy2(file_path, dest) + print(f'Copied {file_path} -> {dest_path}') + + +def main(): + """Main function to update all documentation assets.""" + print('📥 Updating documentation assets...') + print(f' Repository: {GITHUB_REPO}') + print(f' Branch: {GITHUB_BRANCH}\n') + + # Use a temporary cache directory + cache_dir = Path.home() / '.cache' / GITHUB_REPO + cache_dir.mkdir(parents=True, exist_ok=True) + + # Fetch and copy each asset + for source_path, dest_path in ASSETS_MAP.items(): + try: + fetch_and_copy_asset(source_path, dest_path, cache_dir) + print() + except Exception as e: + print(f'❌ Failed to fetch {source_path}: {e}') + + print('\n✅ Documentation assets updated successfully!') + + +if __name__ == '__main__': + main() diff --git a/tools/update_github_labels.py b/tools/update_github_labels.py new file mode 100644 index 00000000..84de575e --- /dev/null +++ b/tools/update_github_labels.py @@ -0,0 +1,341 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Set/update GitHub labels for current or specified easyscience +repository. + +Requires: + - gh CLI installed + - gh auth login completed + +Usage: + python update_github_labels.py + python update_github_labels.py --dry-run + python update_github_labels.py --repo easyscience/my-repo + python update_github_labels.py --repo easyscience/my-repo --dry-run +""" + +from __future__ import annotations + +import argparse +import json +import shlex +import subprocess # noqa: S404 +import sys +from dataclasses import dataclass + +EASYSCIENCE_ORG = 'easyscience' + + +# Data structures + + +@dataclass(frozen=True) +class Label: + """A GitHub label with name, color, and description.""" + + name: str + color: str + description: str = '' + + +@dataclass(frozen=True) +class LabelRename: + """Mapping from old label name to new label name.""" + + old: str + new: str + + +class Colors: + """Hex color codes for label groups.""" + + SCOPE = 'd73a4a' + MAINTAINER = '0e8a16' + PRIORITY = 'fbca04' + BOT = '5319e7' + + +LABEL_RENAMES = [ + # Default GitHub labels to rename (if they exist) + LabelRename('bug', '[scope] bug'), + LabelRename('documentation', '[scope] documentation'), + LabelRename('duplicate', '[maintainer] duplicate'), + LabelRename('enhancement', '[scope] enhancement'), + LabelRename('good first issue', '[maintainer] good first issue'), + LabelRename('help wanted', '[maintainer] help wanted'), + LabelRename('invalid', '[maintainer] invalid'), + LabelRename('question', '[maintainer] question'), + LabelRename('wontfix', '[maintainer] wontfix'), + # Custom label renames (if they exist) + LabelRename('[bot] pull request', '[bot] release'), +] + +LABELS = [ + # Scope labels + Label( + '[scope] bug', + Colors.SCOPE, + 'Bug report or fix (major.minor.PATCH)', + ), + Label( + '[scope] documentation', + Colors.SCOPE, + 'Documentation only changes (major.minor.patch.POST)', + ), + Label( + '[scope] enhancement', + Colors.SCOPE, + 'Adds/improves features (major.MINOR.patch)', + ), + Label( + '[scope] maintenance', + Colors.SCOPE, + 'Code/tooling cleanup, no feature or bugfix (major.minor.PATCH)', + ), + Label( + '[scope] significant', + Colors.SCOPE, + 'Breaking or major changes (MAJOR.minor.patch)', + ), + Label( + '[scope] ⚠️ label needed', + Colors.SCOPE, + 'Automatically added to issues and PRs without a [scope] label', + ), + # Maintainer labels + Label( + '[maintainer] duplicate', + Colors.MAINTAINER, + 'Already reported or submitted', + ), + Label( + '[maintainer] good first issue', + Colors.MAINTAINER, + 'Good entry-level issue for newcomers', + ), + Label( + '[maintainer] help wanted', + Colors.MAINTAINER, + 'Needs additional help to resolve or implement', + ), + Label( + '[maintainer] invalid', + Colors.MAINTAINER, + 'Invalid, incorrect or outdated', + ), + Label( + '[maintainer] question', + Colors.MAINTAINER, + 'Needs clarification, discussion, or more information', + ), + Label( + '[maintainer] wontfix', + Colors.MAINTAINER, + 'Will not be fixed or continued', + ), + # Priority labels + Label( + '[priority] lowest', + Colors.PRIORITY, + 'Very low urgency', + ), + Label( + '[priority] low', + Colors.PRIORITY, + 'Low importance', + ), + Label( + '[priority] medium', + Colors.PRIORITY, + 'Normal/default priority', + ), + Label( + '[priority] high', + Colors.PRIORITY, + 'Should be prioritized soon', + ), + Label( + '[priority] highest', + Colors.PRIORITY, + 'Urgent. Needs attention ASAP', + ), + Label( + '[priority] ⚠️ label needed', + Colors.PRIORITY, + 'Automatically added to issues without a [priority] label', + ), + # Bot label + Label( + '[bot] release', + Colors.BOT, + 'Automated release PR. Excluded from changelog/versioning', + ), + Label( + '[bot] backmerge', + Colors.BOT, + 'Automated backmerge master → develop failed due to conflicts', + ), +] + + +# Helpers + + +@dataclass(frozen=True) +class CmdResult: + """Result of a shell command execution.""" + + returncode: int + stdout: str + stderr: str + + +def run_cmd( + args: list[str], + *, + dry_run: bool, + check: bool = True, +) -> CmdResult: + """Run a command (or print it in dry-run mode).""" + cmd_str = ' '.join(shlex.quote(a) for a in args) + + if dry_run: + print(f' [dry-run] {cmd_str}') + return CmdResult(0, '', '') + + proc = subprocess.run( + args=args, + text=True, + capture_output=True, + ) + result = CmdResult( + proc.returncode, + proc.stdout.strip(), + proc.stderr.strip(), + ) + + if check and proc.returncode != 0: + raise RuntimeError(f'Command failed ({proc.returncode}): {cmd_str}\n{result.stderr}') + + return result + + +def get_current_repo() -> str: + """Get the current repository name in 'owner/repo' format.""" + result = subprocess.run( + args=[ + 'gh', + 'repo', + 'view', + '--json', + 'nameWithOwner', + ], + text=True, + capture_output=True, + check=True, + ) + data = json.loads(result.stdout) + name_with_owner = data.get('nameWithOwner', '') + + if '/' not in name_with_owner: + raise RuntimeError('Could not determine current repository name') + + return name_with_owner + + +def rename_label( + repo: str, + rename: LabelRename, + *, + dry_run: bool, +) -> None: + """Rename a label, silently skipping if it doesn't exist.""" + result = run_cmd( + args=[ + 'gh', + 'label', + 'edit', + rename.old, + '--name', + rename.new, + '--repo', + repo, + ], + dry_run=dry_run, + check=False, + ) + + if dry_run or result.returncode == 0: + print(f' Rename: {rename.old!r} → {rename.new!r}') + else: + print(f' Skip (not found): {rename.old!r}') + + +def upsert_label( + repo: str, + label: Label, + *, + dry_run: bool, +) -> None: + """Create or update a label.""" + run_cmd( + [ + 'gh', + 'label', + 'create', + label.name, + '--color', + label.color, + '--description', + label.description, + '--force', + '--repo', + repo, + ], + dry_run=dry_run, + ) + print(f' Upsert: {label.name!r}') + + +# Main + + +def main() -> int: + """Entry point: parse arguments and sync labels.""" + parser = argparse.ArgumentParser(description='Sync GitHub labels for easyscience repos') + parser.add_argument( + '--repo', + help='Target repository (owner/name)', + ) + parser.add_argument( + '--dry-run', + action='store_true', + help='Print actions without applying changes', + ) + args = parser.parse_args() + + repo = args.repo or get_current_repo() + org = repo.split('/')[0] + + if org.lower() != EASYSCIENCE_ORG: + print(f"Error: repository '{repo}' is not under '{EASYSCIENCE_ORG}'", file=sys.stderr) + return 2 + + print(f'Repository: {repo}') + if args.dry_run: + print('Mode: DRY-RUN (no changes will be made)\n') + + print('\nRenaming default labels...') + for rename in LABEL_RENAMES: + rename_label(repo, rename, dry_run=args.dry_run) + + print('\nUpserting labels...') + for label in LABELS: + upsert_label(repo, label, dry_run=args.dry_run) + + print('\nDone.') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/vscode-template/settings.json b/vscode-template/settings.json index 35487105..239b6f6a 100644 --- a/vscode-template/settings.json +++ b/vscode-template/settings.json @@ -1,13 +1,11 @@ { - "python.testing.pytestArgs": [ - "tests" - ], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - "[python]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - }, - }, -} \ No newline at end of file + "python.testing.pytestArgs": ["tests"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + } +} From 74e9a5b00e4b785132e8de5f96466cdf35d308cb Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 22 May 2026 08:33:14 +0200 Subject: [PATCH 10/38] prettify the summary (#356) --- .../summary/html_templates.py | 8 +-- src/easyreflectometry/summary/summary.py | 65 +++++++++++++++++-- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/easyreflectometry/summary/html_templates.py b/src/easyreflectometry/summary/html_templates.py index 9b770c01..66012d40 100644 --- a/src/easyreflectometry/summary/html_templates.py +++ b/src/easyreflectometry/summary/html_templates.py @@ -123,10 +123,10 @@ Minimization engine minimization_engine - - - - + + Goodness-of-fit: reduced χ2 + goodness_of_fit + No. of parameters: num_total_params diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index 394c9d78..c03cd3f7 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: 2024 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from html import escape +from urllib.parse import quote + import matplotlib.pyplot as plt import numpy as np from easyscience import global_object @@ -16,6 +19,39 @@ from .html_templates import HTML_REFINEMENT_TEMPLATE from .html_templates import HTML_TEMPLATE +_NAME_MAX_LEN = 20 +# Custom href scheme used to pass the full name to QML via TextEdit.hoveredLink. +_TOOLTIP_SCHEME = 'nametooltip' + + +def _format_error(error: float) -> str: + """Format a parameter error the same way the Analysis table does. + + Mirrors the JS ``formatError`` in Fittables.qml: + 2 significant figures; fall back to 1-decimal exponential for long strings. + Zero (no fit run yet) is shown as '0.0'. + """ + if error == 0.0: + return '0.0' + s = f'{error:.2g}' + if len(s) <= 6: + return s + return f'{error:.1e}' + + +def _truncate_name(name: str, max_len: int = _NAME_MAX_LEN) -> str: + """Return an HTML snippet with a truncated name and tooltip for the full text. + + Browsers use the ``title`` attribute; QML reads the ``href`` via + ``TextEdit.hoveredLink`` and shows a native ToolTip. + """ + safe = escape(name) + if len(name) <= max_len: + return safe + short = escape(name[:max_len].rstrip()) + encoded = quote(name, safe='') + return f'{short}…' + class Summary: def __init__(self, project: Project): @@ -140,7 +176,8 @@ def _sample_section(self) -> str: html_parameter = html_parameter.replace('parameter_name', f'{name}') html_parameter = html_parameter.replace('parameter_value', f'{value}') html_parameter = html_parameter.replace('parameter_unit', f'{unit}') - html_parameter = html_parameter.replace('parameter_error', f'{error}') + error_str = _format_error(error) + html_parameter = html_parameter.replace('parameter_error', error_str) html_parameters.append(html_parameter) html_parameters_str = '\n'.join(html_parameters) @@ -162,7 +199,7 @@ def _experiments_section(self) -> str: range_max = max(experiment.y) range_units = 'Å⁻¹' html_experiment = HTML_DATA_COLLECTION_TEMPLATE - html_experiment = html_experiment.replace('experiment_name', f'{experiment_name}') + html_experiment = html_experiment.replace('experiment_name', _truncate_name(experiment_name)) html_experiment = html_experiment.replace('range_min', f'{range_min}') html_experiment = html_experiment.replace('range_max', f'{range_max}') html_experiment = html_experiment.replace('range_units', f'{range_units}') @@ -185,19 +222,37 @@ def _refinement_section(self) -> str: num_free_params = sum(1 for parameter in parameters if parameter.free) num_fixed_params = sum(1 for parameter in parameters if not parameter.free) num_params = num_free_params + num_fixed_params - # goodness_of_fit = self._project.status.goodnessOfFit - # goodness_of_fit = goodness_of_fit.split(' → ')[-1] num_constraints = sum(1 for parameter in parameters if not parameter.independent) + goodness_of_fit = self._compute_goodness_of_fit() + html_refinement = html_refinement.replace('calculation_engine', f'{self._project._calculator.current_interface_name}') html_refinement = html_refinement.replace('minimization_engine', f'{self._project.minimizer.name}') - # html = html.replace('goodness_of_fit', f'{goodness_of_fit}') + html_refinement = html_refinement.replace('goodness_of_fit', goodness_of_fit) html_refinement = html_refinement.replace('num_total_params', f'{num_params}') html_refinement = html_refinement.replace('num_free_params', f'{num_free_params}') html_refinement = html_refinement.replace('num_fixed_params', f'{num_fixed_params}') html_refinement = html_refinement.replace('num_constriants', f'{num_constraints}') return html_refinement + def _compute_goodness_of_fit(self) -> str: + """Return reduced chi² as a formatted string, or 'N/A' if no fit has been run.""" + last_fit_results = getattr(self._project, '_last_fit_results', None) + if not last_fit_results: + return 'N/A' + try: + if len(last_fit_results) == 1: + gof = float(last_fit_results[0].reduced_chi2) + else: + total_chi2 = sum(float(r.chi2) for r in last_fit_results) + total_points = sum(len(r.x) for r in last_fit_results) + n_pars = last_fit_results[0].n_pars + dof = total_points - n_pars + gof = total_chi2 / dof if dof > 0 else 0.0 + return f'{gof:.4g}' + except (AttributeError, TypeError, ValueError, ZeroDivisionError): + return 'N/A' + def _figures_section(self) -> None: """Figures section.""" html_figures = HTML_FIGURES_TEMPLATE From 7e73364306118a75843011be5d0bfab9b23b2503 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 22 May 2026 11:00:23 +0200 Subject: [PATCH 11/38] refactor printout formatter (#358) --- src/easyreflectometry/summary/summary.py | 25 ++++++++++++------------ 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index c03cd3f7..f53a9392 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -24,19 +24,18 @@ _TOOLTIP_SCHEME = 'nametooltip' -def _format_error(error: float) -> str: - """Format a parameter error the same way the Analysis table does. +def _format_value(value: float, sig_figs: int) -> str: + """Format a numeric value for summary display. - Mirrors the JS ``formatError`` in Fittables.qml: - 2 significant figures; fall back to 1-decimal exponential for long strings. - Zero (no fit run yet) is shown as '0.0'. + *sig_figs* significant figures; fall back to 1-decimal exponential when + the formatted string is too long. Zero is shown as '0.0'. """ - if error == 0.0: + if value == 0.0: return '0.0' - s = f'{error:.2g}' - if len(s) <= 6: + s = f'{value:.{sig_figs}g}' + if len(s) <= sig_figs + 4: return s - return f'{error:.1e}' + return f'{value:.1e}' def _truncate_name(name: str, max_len: int = _NAME_MAX_LEN) -> str: @@ -174,9 +173,9 @@ def _sample_section(self) -> str: html_parameter = HTML_PARAMETER_TEMPLATE html_parameter = html_parameter.replace('parameter_name', f'{name}') - html_parameter = html_parameter.replace('parameter_value', f'{value}') + html_parameter = html_parameter.replace('parameter_value', _format_value(value, 3)) html_parameter = html_parameter.replace('parameter_unit', f'{unit}') - error_str = _format_error(error) + error_str = _format_value(error, 2) html_parameter = html_parameter.replace('parameter_error', error_str) html_parameters.append(html_parameter) @@ -200,8 +199,8 @@ def _experiments_section(self) -> str: range_units = 'Å⁻¹' html_experiment = HTML_DATA_COLLECTION_TEMPLATE html_experiment = html_experiment.replace('experiment_name', _truncate_name(experiment_name)) - html_experiment = html_experiment.replace('range_min', f'{range_min}') - html_experiment = html_experiment.replace('range_max', f'{range_max}') + html_experiment = html_experiment.replace('range_min', _format_value(range_min, 2)) + html_experiment = html_experiment.replace('range_max', _format_value(range_max, 2)) html_experiment = html_experiment.replace('range_units', f'{range_units}') html_experiment = html_experiment.replace('num_data_points', f'{num_data_points}') html_experiment = html_experiment.replace('resolution_function', f'{resolution_function}') From c95d2e00577e46496a634d1ed1118db7624fed0d Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 22 May 2026 11:29:00 +0200 Subject: [PATCH 12/38] make PDF links clickable (#359) --- src/easyreflectometry/summary/summary.py | 42 ++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index f53a9392..c06751af 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -2,6 +2,8 @@ # SPDX-License-Identifier: BSD-3-Clause from html import escape +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version from urllib.parse import quote import matplotlib.pyplot as plt @@ -23,6 +25,36 @@ # Custom href scheme used to pass the full name to QML via TextEdit.hoveredLink. _TOOLTIP_SCHEME = 'nametooltip' +# URLs for known calculation engines and minimizer packages. +_ENGINE_URLS: dict[str, str] = { + 'refnx': 'https://refnx.readthedocs.io', + 'refl1d': 'https://refl1d.readthedocs.io', + 'bornagain': 'https://www.bornagainproject.org', + 'lm': 'https://lmfit.github.io/lmfit-py/', + 'bumps': 'https://bumps.readthedocs.io', + 'dfo': 'https://github.com/fitbenchmarking/dfo-ls', +} + + +def _engine_link(name: str, package: str | None = None) -> str: + """Return an HTML hyperlink for an engine, including its version. + + Falls back to plain text when no URL is known for the engine. + """ + url = _ENGINE_URLS.get(name) or _ENGINE_URLS.get(package or '') + display = escape(name) + if package: + try: + ver = version(package) + except PackageNotFoundError: + ver = None + if ver: + display = f'{display} (v{ver})' + + if url: + return f'{display}' + return display + def _format_value(value: float, sig_figs: int) -> str: """Format a numeric value for summary display. @@ -225,8 +257,14 @@ def _refinement_section(self) -> str: goodness_of_fit = self._compute_goodness_of_fit() - html_refinement = html_refinement.replace('calculation_engine', f'{self._project._calculator.current_interface_name}') - html_refinement = html_refinement.replace('minimization_engine', f'{self._project.minimizer.name}') + html_refinement = html_refinement.replace( + 'calculation_engine', + _engine_link(self._project._calculator.current_interface_name), + ) + html_refinement = html_refinement.replace( + 'minimization_engine', + _engine_link(self._project.minimizer.name, self._project.minimizer.package), + ) html_refinement = html_refinement.replace('goodness_of_fit', goodness_of_fit) html_refinement = html_refinement.replace('num_total_params', f'{num_params}') html_refinement = html_refinement.replace('num_free_params', f'{num_free_params}') From 5642d8c316cd3b5e8203d65a7b2a69e5cc644103 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Thu, 28 May 2026 11:26:41 +0200 Subject: [PATCH 13/38] Move to EasyList (#360) * initial commit * PR review * more tests --- CHANGELOG.md | 33 ++ docs/docs/tutorials/simulation/bilayer.ipynb | 12 +- src/easyreflectometry/model/model.py | 126 ++++--- .../model/model_collection.py | 57 +-- src/easyreflectometry/project.py | 30 +- .../sample/assemblies/base_assembly.py | 26 +- .../sample/assemblies/bilayer.py | 59 +-- .../sample/assemblies/gradient_layer.py | 43 ++- .../sample/assemblies/multilayer.py | 8 +- .../sample/assemblies/repeating_multilayer.py | 21 +- .../sample/assemblies/surfactant_layer.py | 28 +- src/easyreflectometry/sample/base_core.py | 251 +++++++++++-- .../sample/collections/base_collection.py | 351 ++++++++++++++---- .../sample/collections/layer_collection.py | 11 +- .../sample/collections/material_collection.py | 5 +- .../sample/collections/sample.py | 41 +- .../sample/elements/layers/layer.py | 49 ++- .../layers/layer_area_per_molecule.py | 207 +++++------ .../sample/elements/materials/material.py | 37 +- .../elements/materials/material_density.py | 107 ++++-- .../elements/materials/material_mixture.py | 210 ++++++----- .../elements/materials/material_solvated.py | 105 +++--- src/easyreflectometry/summary/summary.py | 4 +- tests/model/test_model.py | 117 ++++++ tests/model/test_model_collection.py | 43 ++- tests/sample/assemblies/test_base_assembly.py | 15 + tests/sample/assemblies/test_bilayer.py | 14 +- .../assemblies/test_surfactant_layer.py | 20 +- .../collections/test_base_collection.py | 179 ++++++++- .../layers/test_layer_area_per_molecule.py | 75 +++- .../materials/test_material_density.py | 19 + .../materials/test_material_mixture.py | 60 ++- .../materials/test_material_solvated.py | 32 +- tests/sample/test_base_core.py | 284 ++++++++++++++ tests/test_project.py | 40 ++ 35 files changed, 2055 insertions(+), 664 deletions(-) create mode 100644 tests/sample/test_base_core.py diff --git a/CHANGELOG.md b/CHANGELOG.md index bb888bf6..2537591f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ +# Unreleased + +Migrated sample / model classes off the deprecated `easyscience.ObjBase` +and `easyscience.CollectionBase` pipeline. + +- `BaseCore` is now built on `ModelBase`; `BaseCollection` on + `EasyList`. `Model`, `Material`, `Layer`, `MaterialMixture`, + `MaterialSolvated`, `LayerAreaPerMolecule`, `Multilayer`, + `RepeatingMultilayer`, `GradientLayer`, `Bilayer`, `SurfactantLayer`, + `BaseAssembly`, `LayerCollection`, `MaterialCollection`, `Sample`, and + `ModelCollection` were all rewritten to use the new bases. +- Properties returning a `Parameter` (`Material.sld`-style) now expose + the `Parameter` object directly across all sample classes, replacing + the inconsistent legacy behaviour where `MaterialMixture.fraction`, + `MaterialSolvated.solvent_fraction`, + `LayerAreaPerMolecule.area_per_molecule`, and + `LayerAreaPerMolecule.solvent_fraction` returned `float`. Read the + value via `.value` (e.g. `material_mixture.fraction.value`). Setters + still accept a float. `MaterialMixture.sld` / `MaterialMixture.isld` + remain `float` — they are derived via constraints, not constructor + arguments. +- `BaseCollection.remove(index)` (the legacy index-based helper) renamed + to `remove_at(index)`. The standard `MutableSequence.remove(value)` is + now inherited unmodified. +- Project files saved by previous versions cannot be read. + `Project.as_dict` writes `file_format=2`; `Project.from_dict` raises a + clear `ValueError` on missing or unsupported markers. +- `model.get_parameters()` / `collection.get_parameters()` still work + (kept as compatibility shims) but new code should use + `get_all_parameters()`. +- No more `DeprecationWarning` from `easyscience.ObjBase` / + `CollectionBase` on construction of any sample / model object. + # Version 1.6.0 (1 May 2026) Add Mighell-based handling of non-positive-variance points in fitting diff --git a/docs/docs/tutorials/simulation/bilayer.ipynb b/docs/docs/tutorials/simulation/bilayer.ipynb index b16b8284..5f37b010 100644 --- a/docs/docs/tutorials/simulation/bilayer.ipynb +++ b/docs/docs/tutorials/simulation/bilayer.ipynb @@ -202,7 +202,7 @@ "# Access key structural parameters\n", "print(f'Head thickness: {bilayer.front_head_layer.thickness.value:.2f} Å')\n", "print(f'Tail thickness: {bilayer.front_tail_layer.thickness.value:.2f} Å')\n", - "print(f'Area per molecule: {bilayer.front_head_layer.area_per_molecule:.2f} Ų')" + "print(f'Area per molecule: {bilayer.front_head_layer.area_per_molecule.value:.2f} Ų')" ] }, { @@ -224,14 +224,14 @@ "source": [ "# Head layers share thickness and area per molecule via constrain_heads=True,\n", "# but solvent fraction is independent and can be set separately for each side.\n", - "print(f'Front head solvent fraction: {bilayer.front_head_layer.solvent_fraction:.2f}')\n", - "print(f'Back head solvent fraction: {bilayer.back_head_layer.solvent_fraction:.2f}')\n", + "print(f'Front head solvent fraction: {bilayer.front_head_layer.solvent_fraction.value:.2f}')\n", + "print(f'Back head solvent fraction: {bilayer.back_head_layer.solvent_fraction.value:.2f}')\n", "\n", "# We can set them independently\n", "bilayer.back_head_layer.solvent_fraction = 0.5\n", "print('\\nAfter setting back head solvent fraction to 0.5:')\n", - "print(f'Front head solvent fraction: {bilayer.front_head_layer.solvent_fraction:.2f}')\n", - "print(f'Back head solvent fraction: {bilayer.back_head_layer.solvent_fraction:.2f}')" + "print(f'Front head solvent fraction: {bilayer.front_head_layer.solvent_fraction.value:.2f}')\n", + "print(f'Back head solvent fraction: {bilayer.back_head_layer.solvent_fraction.value:.2f}')" ] }, { @@ -685,7 +685,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.12.11" } }, "nbformat": 4, diff --git a/src/easyreflectometry/model/model.py b/src/easyreflectometry/model/model.py index b1aedf85..da1396d0 100644 --- a/src/easyreflectometry/model/model.py +++ b/src/easyreflectometry/model/model.py @@ -9,15 +9,14 @@ from typing import Union import numpy as np -from easyscience import ObjBase as BaseObj from easyscience import global_object from easyscience.variable import Parameter from easyreflectometry.limits import apply_default_limits from easyreflectometry.sample import BaseAssembly from easyreflectometry.sample import Sample +from easyreflectometry.sample.base_core import BaseCore from easyreflectometry.utils import get_as_parameter -from easyreflectometry.utils import yaml_dump from .resolution_functions import PercentageFwhm from .resolution_functions import ResolutionFunction @@ -58,18 +57,12 @@ ] -class Model(BaseObj): +class Model(BaseCore): """Model is the class that represents the experiment. It is used to store the information about the experiment and to perform the calculations. """ - # Added in super().__init__ - name: str - sample: Sample - scale: Parameter - background: Parameter - def __init__( self, sample: Union[Sample, None] = None, @@ -115,18 +108,45 @@ def __init__( background = get_as_parameter('background', background, DEFAULTS) self.color = color self._is_default = False + self._resolution_function = resolution_function + + super().__init__(name=name, unique_name=unique_name) + self._sample = sample + self._scale = scale + self._background = background + + # Set interface last — propagates to children via BaseCore.generate_bindings + # and then sets the resolution function on the calculator (see setter). + if interface is not None: + self.interface = interface + + # ----- @property accessors for serialization round-trip ----- + + @property + def sample(self) -> Sample: + return self._sample + + @sample.setter + def sample(self, value: Sample) -> None: + self._sample = value - super().__init__( - name=name, - unique_name=unique_name, - sample=sample, - scale=scale, - background=background, - ) - self.resolution_function = resolution_function + @property + def scale(self) -> Parameter: + return self._scale + + @scale.setter + def scale(self, value: float) -> None: + self._scale.value = value + + @property + def background(self) -> Parameter: + return self._background - # Must be set after resolution function - self.interface = interface + @background.setter + def background(self, value: float) -> None: + self._background.value = value + + # ----- assembly management ----- def add_assemblies(self, *assemblies: list[BaseAssembly]) -> None: """Add assemblies to the model sample. @@ -183,15 +203,11 @@ def is_default(self) -> bool: @is_default.setter def is_default(self, value: bool) -> None: - """Set whether this model is a default placeholder. - - Parameters - ---------- - value : bool - True if the model is a default placeholder. - """ + """Set whether this model is a default placeholder.""" self._is_default = value + # ----- resolution function ----- + @property def resolution_function(self) -> ResolutionFunction: """Return the resolution function.""" @@ -204,21 +220,20 @@ def resolution_function(self, resolution_function: ResolutionFunction) -> None: if self.interface is not None: self.interface().set_resolution_function(self._resolution_function) - @property - def interface(self): - """Get the current interface of the object.""" - return self._interface + # ----- interface (override BaseCore's to add resolution-function side effect) ----- - @interface.setter + @BaseCore.interface.setter def interface(self, new_interface) -> None: - """Set the interface for the model.""" - # From super class - self._interface = new_interface + """Set the interface; runs `generate_bindings` and then refreshes the + calculator's resolution function. + """ + # Call BaseCore.interface.setter for the binding propagation. + BaseCore.interface.fset(self, new_interface) if new_interface is not None: - self.generate_bindings() - self._interface().set_resolution_function(self._resolution_function) + new_interface().set_resolution_function(self._resolution_function) + + # ----- representation ----- - # Representation @property def _dict_repr(self) -> dict[str, dict[str, str]]: """A simplified dict representation.""" @@ -238,24 +253,15 @@ def _dict_repr(self) -> dict[str, dict[str, str]]: } } - def __repr__(self) -> str: - """String representation of the layer.""" - return yaml_dump(self._dict_repr) - - def as_dict(self, skip: Optional[list[str]] = None) -> dict: - """Produces a cleaned dict using a custom as_dict method to skip necessary things. - - The resulting dict matches the parameters in __init__ + # ----- serialization (custom because resolution_function + interface need special handling) ----- - Parameters - ---------- - skip : Optional[list[str]], optional - List of keys to skip. By default, None. - """ + def to_dict(self, skip: Optional[list[str]] = None) -> dict: + """Serialize the model, encoding the resolution function and interface name.""" if skip is None: skip = [] - skip.extend(['sample', 'resolution_function', 'interface']) - this_dict = super().as_dict(skip=skip) + # Sample/resolution_function/interface get bespoke encoding below. + skip_for_super = list(skip) + ['sample', 'resolution_function', 'interface'] + this_dict = super().to_dict(skip=skip_for_super) this_dict['sample'] = self.sample.as_dict(skip=skip) this_dict['resolution_function'] = self.resolution_function.as_dict(skip=skip) if self.interface is None: @@ -264,23 +270,23 @@ def as_dict(self, skip: Optional[list[str]] = None) -> dict: this_dict['interface'] = self.interface().name return this_dict + def as_dict(self, skip: Optional[list[str]] = None) -> dict: + """Compatibility alias for :meth:`to_dict`.""" + return self.to_dict(skip=skip) + def as_orso(self) -> dict: """Convert the model to a dictionary suitable for ORSO.""" - this_dict = self.as_dict() - - return this_dict + return self.as_dict() @classmethod def from_dict(cls, passed_dict: dict) -> Model: """Create a Model from a dictionary.""" - # Causes circular import if imported at the top + # Circular import if hoisted to module-top. from easyreflectometry.calculators import CalculatorFactory this_dict = copy.deepcopy(passed_dict) - resolution_function = ResolutionFunction.from_dict(this_dict['resolution_function']) - del this_dict['resolution_function'] - interface_name = this_dict['interface'] - del this_dict['interface'] + resolution_function = ResolutionFunction.from_dict(this_dict.pop('resolution_function')) + interface_name = this_dict.pop('interface') if interface_name is not None: interface = CalculatorFactory() interface.switch(interface_name) diff --git a/src/easyreflectometry/model/model_collection.py b/src/easyreflectometry/model/model_collection.py index 1a1b7e88..817fc5f0 100644 --- a/src/easyreflectometry/model/model_collection.py +++ b/src/easyreflectometry/model/model_collection.py @@ -3,7 +3,6 @@ from __future__ import annotations -from typing import List from typing import Optional from typing import Tuple @@ -36,20 +35,33 @@ def __init__( models = DEFAULT_ELEMENTS(interface) else: models = [] - # Needed to ensure an empty list is created when saving and instatiating the object as_dict -> from_dict - # Else collisions might occur in global_object.map - self.populate_if_none = False + + # `_next_color_index` must exist before super().__init__ because each + # `append` during construction routes through `_append_internal` → + # `_advance_color_index`, which reads the attribute. self._next_color_index = next_color_index - super().__init__(name, interface, *models, unique_name=unique_name, **kwargs) + super().__init__( + name, + interface, + *models, + unique_name=unique_name, + populate_if_none=False, + **kwargs, + ) color_count = len(COLORS) if color_count == 0: self._next_color_index = 0 - elif self._next_color_index is None: + elif next_color_index is None: self._next_color_index = len(self) % color_count else: - self._next_color_index %= color_count + self._next_color_index = next_color_index % color_count + + @property + def next_color_index(self) -> Optional[int]: + """Index of the next colour to assign — kept around so it round-trips.""" + return self._next_color_index def add_model(self, model: Optional[Model] = None): """Add a model to the collection. @@ -76,28 +88,24 @@ def duplicate_model(self, index: int): duplicate.name = duplicate.name + ' duplicate' self.append(duplicate) - def as_dict(self, skip: List[str] | None = None) -> dict: - """As dict.""" - this_dict = super().as_dict(skip=skip) - this_dict['populate_if_none'] = self.populate_if_none - this_dict['next_color_index'] = self._next_color_index - return this_dict - @classmethod def from_dict(cls, this_dict: dict) -> ModelCollection: """Create an instance of a collection from a dictionary.""" - collection_dict = this_dict.copy() - # We need to call from_dict on the base class to get the models - dict_data = collection_dict.pop('data') + collection_dict = dict(this_dict) + dict_data = collection_dict.pop('data', []) next_color_index = collection_dict.pop('next_color_index', None) - collection = super().from_dict(collection_dict) # type: ModelCollection + # Reconstruct empty collection via EasyList.from_dict (handles + # protected_types and assigns name/unique_name/populate_if_none). + collection = super().from_dict(collection_dict) + # Append each model without advancing the colour index — the saved + # `next_color_index` below is the source of truth. for model_data in dict_data: collection._append_internal(Model.from_dict(model_data), advance=False) - if len(collection) != len(this_dict['data']): - raise ValueError(f'Expected {len(collection)} models, got {len(this_dict["data"])}') + if len(collection) != len(dict_data): + raise ValueError(f'Expected {len(dict_data)} models, got {len(collection)}') color_count = len(COLORS) if color_count == 0: @@ -115,7 +123,14 @@ def append(self, model: Model) -> None: # type: ignore[override] def _append_internal(self, model: Model, advance: bool) -> None: """Append internal.""" - super().append(model) + # Bypass our own `append` override and go straight to EasyList's + # `MutableSequence.append` → `insert` path. Calling `super().append` + # would dispatch back to `ModelCollection.append` because Python + # resolves `append` via MRO from MutableSequence which doesn't + # define it on a class higher than ModelCollection. + from collections.abc import MutableSequence + + MutableSequence.append(self, model) if advance: self._advance_color_index() diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 5127ad16..5ae272a6 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -90,7 +90,7 @@ def parameters(self) -> List[Parameter]: seen_ids: set[int] = set() if self._models is not None: for model in self._models: - for param in model.get_parameters(): + for param in model.get_all_parameters(): pid = id(param) if pid not in seen_ids: seen_ids.add(pid) @@ -847,9 +847,18 @@ def load_from_json(self, path: Optional[Union[Path, str]] = None): else: print(f'ERROR: File {path} does not exist') + #: Schema version embedded in every serialized project. Bumped from 1 → 2 + #: when the sample/model classes migrated from the legacy + #: ``easyscience.ObjBase``/``CollectionBase`` pipeline to + #: ``ModelBase``/``EasyList``. The on-disk shape of nested objects (Layer, + #: Material, MaterialMixture, MaterialSolvated, LayerAreaPerMolecule, etc.) + #: changed in a way that is not backward-compatible with v1 files. + FILE_FORMAT = 2 + def as_dict(self, include_materials_not_in_model=False): """As dict.""" project_dict = {} + project_dict['file_format'] = self.FILE_FORMAT project_dict['info'] = self._info project_dict['with_experiments'] = self._with_experiments if self._models is not None: @@ -898,6 +907,25 @@ def _as_dict_add_experiments(self, project_dict: dict): def from_dict(self, project_dict: dict): """From dict.""" keys = list(project_dict.keys()) + # Validate file format. v1 files were written by the legacy + # `ObjBase`/`CollectionBase` pipeline; their inner shapes (Layer, + # Material, MaterialMixture, …) are not compatible with the v2 + # `ModelBase`/`EasyList` deserializer. Older files must be re-created. + file_format = project_dict.get('file_format') + if file_format is None: + raise ValueError( + 'This project file predates file_format=2 and cannot be loaded by ' + 'this version of easyreflectometry. The serialization format changed ' + 'when the sample/model classes migrated from the legacy ObjBase / ' + 'CollectionBase pipeline. Please re-create the project from its ' + 'underlying data using the current API.' + ) + if file_format != self.FILE_FORMAT: + raise ValueError( + f'Unsupported project file_format={file_format!r}; this version of ' + f'easyreflectometry only reads file_format={self.FILE_FORMAT}. Please ' + 'either update easyreflectometry or re-create the project.' + ) self._info = project_dict['info'] self._with_experiments = project_dict['with_experiments'] if 'calculator' in keys: diff --git a/src/easyreflectometry/sample/assemblies/base_assembly.py b/src/easyreflectometry/sample/assemblies/base_assembly.py index 39cb2b18..fcb2f06d 100644 --- a/src/easyreflectometry/sample/assemblies/base_assembly.py +++ b/src/easyreflectometry/sample/assemblies/base_assembly.py @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause -from typing import Any from typing import Optional from ..base_core import BaseCore @@ -17,28 +16,33 @@ class BaseAssembly(BaseCore): its index number depends on the number of finite layers in the system, but it might be accessed at index -1. """ - # Added in super().__init__ - #: Name of the assembly. - name: str - #: Layers in the assembly. - layers: LayerCollection - #: Interface to the calculator. - interface: Any - def __init__( self, name: str, type: str, interface, - **layers: LayerCollection, + layers: LayerCollection, + unique_name: Optional[str] = None, ): - super().__init__(name=name, interface=interface, **layers) + super().__init__(name=name, unique_name=unique_name) + self._layers = layers # Type is needed when fitting in easyscience self._type = type self._roughness_constraints_setup = False self._thickness_constraints_setup = False + if interface is not None: + self.interface = interface + + @property + def layers(self) -> LayerCollection: + return self._layers + + @layers.setter + def layers(self, value: LayerCollection) -> None: + self._layers = value + @property def type(self) -> str: """Get type of the assembly. diff --git a/src/easyreflectometry/sample/assemblies/bilayer.py b/src/easyreflectometry/sample/assemblies/bilayer.py index 5409d359..995128ed 100644 --- a/src/easyreflectometry/sample/assemblies/bilayer.py +++ b/src/easyreflectometry/sample/assemblies/bilayer.py @@ -59,6 +59,7 @@ def __init__( front_head_layer: LayerAreaPerMolecule | None = None, front_tail_layer: LayerAreaPerMolecule | None = None, back_head_layer: LayerAreaPerMolecule | None = None, + back_tail_layer: LayerAreaPerMolecule | None = None, name: str = 'EasyBilayer', unique_name: str | None = None, constrain_heads: bool = True, @@ -73,10 +74,16 @@ def __init__( Layer representing the front head part of the bilayer. By default, None. front_tail_layer : LayerAreaPerMolecule | None, optional Layer representing the front tail part of the bilayer. - A back tail layer is created internally with its thickness, area per molecule, - and solvent fraction constrained to match this layer. By default, None. + The back tail layer's thickness, area per molecule, and solvent fraction are + constrained to match this layer. By default, None. back_head_layer : LayerAreaPerMolecule | None, optional Layer representing the back head part of the bilayer. By default, None. + back_tail_layer : LayerAreaPerMolecule | None, optional + Layer representing the back tail part of the bilayer. If omitted, a back tail + is created from the front tail (same molecular_formula, solvent, roughness, etc.). + Independent state (solvent, molecular_formula, name, roughness when + ``conformal_roughness`` is False) is preserved across serialization; the + structural parameters listed above are derived from the front tail. By default, None. name : str, optional Name for bilayer. By default, 'EasyBilayer'. unique_name : str | None, optional @@ -109,13 +116,16 @@ def __init__( interface=interface, ) - # Create back tail layer with initial values copied from the front tail. - # Its parameters will be constrained to the front tail after construction. - back_tail_layer = self._create_back_tail_layer( - front_tail_layer=front_tail_layer, - unique_name=unique_name, - interface=interface, - ) + # If no back tail is supplied, derive one from the front tail. The structural + # parameters (thickness, area_per_molecule, solvent_fraction) get constrained to + # the front tail in `_setup_tail_constraints` below regardless of which path + # produced this layer. + if back_tail_layer is None: + back_tail_layer = self._create_back_tail_layer( + front_tail_layer=front_tail_layer, + unique_name=unique_name, + interface=interface, + ) if back_head_layer is None: back_head_layer = self._create_default_head_layer( @@ -143,7 +153,6 @@ def __init__( interface=interface, ) - self.interface = interface self._conformal_roughness = False self._constrain_heads = False self._tail_constraints_setup = False @@ -271,8 +280,8 @@ def _create_back_tail_layer( molecular_formula=front_tail_layer.molecular_formula, thickness=front_tail_layer.thickness.value, solvent=solvent, - solvent_fraction=front_tail_layer.solvent_fraction, - area_per_molecule=front_tail_layer.area_per_molecule, + solvent_fraction=front_tail_layer.solvent_fraction.value, + area_per_molecule=front_tail_layer.area_per_molecule.value, roughness=front_tail_layer.roughness.value, name=front_tail_layer.name + ' Back', unique_name=unique_name + '_LayerAreaPerMoleculeBackTail', @@ -534,21 +543,17 @@ def _dict_repr(self) -> dict: } } - def as_dict(self, skip: list[str] | None = None) -> dict: - """Produce a cleaned dict using a custom as_dict method. - - The resulting dict matches the parameters in __init__ + def to_dict(self, skip: list[str] | None = None) -> dict: + """Serialize, dropping derived fields. - Parameters - ---------- - skip : list[str] | None, optional - List of keys to skip. By default, None. + The `back_tail_layer` and the underlying `layers` collection are + derived in ``__init__`` from the front head / front tail / back head + constructor arguments, so they are not part of the persisted state. """ - this_dict = super().as_dict(skip=skip) - this_dict['front_head_layer'] = self.front_head_layer.as_dict(skip=skip) - this_dict['front_tail_layer'] = self.front_tail_layer.as_dict(skip=skip) - this_dict['back_head_layer'] = self.back_head_layer.as_dict(skip=skip) - this_dict['constrain_heads'] = self.constrain_heads - this_dict['conformal_roughness'] = self.conformal_roughness - del this_dict['layers'] + this_dict = super().to_dict(skip=skip) + this_dict.pop('layers', None) return this_dict + + def as_dict(self, skip: list[str] | None = None) -> dict: + """Compatibility alias for :meth:`to_dict`.""" + return self.to_dict(skip=skip) diff --git a/src/easyreflectometry/sample/assemblies/gradient_layer.py b/src/easyreflectometry/sample/assemblies/gradient_layer.py index 1e08bb2e..fa38879c 100644 --- a/src/easyreflectometry/sample/assemblies/gradient_layer.py +++ b/src/easyreflectometry/sample/assemblies/gradient_layer.py @@ -53,15 +53,12 @@ def __init__( if front_material is None: front_material = Material(0.0, 0.0, 'Air') - self._front_material = front_material if back_material is None: back_material = Material(6.36, 0.0, 'D2O') - self._back_material = back_material if discretisation_elements < 2: raise ValueError('Discretisation elements must be greater than 2.') - self._discretisation_elements = discretisation_elements gradient_layers = _prepare_gradient_layers( front_material=front_material, @@ -74,9 +71,12 @@ def __init__( layers=gradient_layers, name=name, unique_name=unique_name, - interface=interface, + interface=None, type='Gradient-layer', ) + self._front_material = front_material + self._back_material = back_material + self._discretisation_elements = discretisation_elements self._setup_thickness_constraints() self._enable_thickness_constraints() @@ -87,6 +87,21 @@ def __init__( self.thickness = thickness self.roughness = roughness + if interface is not None: + self.interface = interface + + @property + def front_material(self) -> Material: + return self._front_material + + @property + def back_material(self) -> Material: + return self._back_material + + @property + def discretisation_elements(self) -> int: + return self._discretisation_elements + @property def thickness(self) -> float: """Get the thickness of the gradient layer in Angstrom.""" @@ -129,21 +144,31 @@ def _dict_repr(self) -> dict[str, str]: 'front_layer': self.front_layer._dict_repr, } - def as_dict(self, skip: Optional[list[str]] = None) -> dict: - """Produces a cleaned dict using a custom as_dict method to skip necessary things. + def to_dict(self, skip: Optional[list[str]] = None) -> dict: + """Produces a cleaned dict using a custom to_dict method to skip necessary things. - The resulting dict matches the parameters in __init__ + The resulting dict matches the parameters in __init__: layers are derived + in ``__init__`` from ``front_material``/``back_material``/``discretisation_elements`` + so they are excluded from the serialized representation. Parameters ---------- skip : Optional[list[str]], optional List of keys to skip. By default, None. """ - this_dict = super().as_dict(skip=skip) + this_dict = super().to_dict(skip=skip) # Determined in __init__ - del this_dict['layers'] + this_dict.pop('layers', None) + # `thickness` / `roughness` are read-only float views; the serialized + # constructor args are the floats themselves. + this_dict['thickness'] = float(self.thickness) + this_dict['roughness'] = float(self.roughness) return this_dict + def as_dict(self, skip: Optional[list[str]] = None) -> dict: + """Compatibility alias for :meth:`to_dict`.""" + return self.to_dict(skip=skip) + def _linear_gradient( front_value: float, diff --git a/src/easyreflectometry/sample/assemblies/multilayer.py b/src/easyreflectometry/sample/assemblies/multilayer.py index 9144b3a8..db02592d 100644 --- a/src/easyreflectometry/sample/assemblies/multilayer.py +++ b/src/easyreflectometry/sample/assemblies/multilayer.py @@ -62,7 +62,13 @@ def __init__( # Else collisions might occur in global_object.map self.populate_if_none = False - super().__init__(name, unique_name=unique_name, layers=layers, type=type, interface=interface) + super().__init__( + name=name, + type=type, + interface=interface, + layers=layers, + unique_name=unique_name, + ) def add_layer(self, *layers: tuple[Layer]) -> None: """Add a layer to the multi layer. diff --git a/src/easyreflectometry/sample/assemblies/repeating_multilayer.py b/src/easyreflectometry/sample/assemblies/repeating_multilayer.py index cb396836..79eab4fa 100644 --- a/src/easyreflectometry/sample/assemblies/repeating_multilayer.py +++ b/src/easyreflectometry/sample/assemblies/repeating_multilayer.py @@ -75,9 +75,6 @@ def __init__( layers = LayerCollection(layers, name=layers.name) elif isinstance(layers, list): layers = LayerCollection(*layers, name='/'.join([layer.name for layer in layers])) - # Needed to ensure an empty list is created when saving and instatiating the object as_dict -> from_dict - # Else collisions might occur in global_object.map - self.populate_if_none = False repetitions = get_as_parameter( name='repetitions', @@ -89,11 +86,23 @@ def __init__( super().__init__( layers=layers, name=name, - interface=interface, + unique_name=unique_name, + interface=None, type='Repeating Multi-layer', + populate_if_none=False, ) - self._add_component('repetitions', repetitions) - self.interface = interface + self._repetitions = repetitions + + if interface is not None: + self.interface = interface + + @property + def repetitions(self) -> Parameter: + return self._repetitions + + @repetitions.setter + def repetitions(self, value) -> None: + self._repetitions.value = value # Representation @property diff --git a/src/easyreflectometry/sample/assemblies/surfactant_layer.py b/src/easyreflectometry/sample/assemblies/surfactant_layer.py index 81c9c36c..8e81e097 100644 --- a/src/easyreflectometry/sample/assemblies/surfactant_layer.py +++ b/src/easyreflectometry/sample/assemblies/surfactant_layer.py @@ -57,7 +57,6 @@ def __init__( interface : Calculator interface. By default, None. """ - # We need to generate a unique name to create the nested objects if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) @@ -114,11 +113,13 @@ def __init__( interface=interface, ) - self.interface = interface self.conformal = False + if constrain_area_per_molecule: + self.constrain_area_per_molecule = True if conformal_roughness: self._enable_roughness_constraints() + self.conformal = True @property def tail_layer(self) -> Optional[LayerAreaPerMolecule]: @@ -276,20 +277,13 @@ def _dict_repr(self) -> dict: } } - def as_dict(self, skip: Optional[list[str]] = None) -> dict: - """Produces a cleaned dict using a custom as_dict method to skip necessary things. - - The resulting dict matches the parameters in __init__ - - Parameters - ---------- - skip : Optional[list[str]], optional - List of keys to skip. By default, None. + def to_dict(self, skip: Optional[list[str]] = None) -> dict: + """Serialize, dropping the derived ``layers`` field (it is rebuilt + from ``tail_layer`` and ``head_layer`` in ``__init__``). """ - this_dict = super().as_dict(skip=skip) - this_dict['tail_layer'] = self.tail_layer.as_dict(skip=skip) - this_dict['head_layer'] = self.head_layer.as_dict(skip=skip) - this_dict['constrain_area_per_molecule'] = self.constrain_area_per_molecule - this_dict['conformal_roughness'] = self.conformal_roughness - del this_dict['layers'] + this_dict = super().to_dict(skip=skip) + this_dict.pop('layers', None) return this_dict + + def as_dict(self, skip: Optional[list[str]] = None) -> dict: + return self.to_dict(skip=skip) diff --git a/src/easyreflectometry/sample/base_core.py b/src/easyreflectometry/sample/base_core.py index 4cf600a6..611665cd 100644 --- a/src/easyreflectometry/sample/base_core.py +++ b/src/easyreflectometry/sample/base_core.py @@ -1,49 +1,240 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from __future__ import annotations + from abc import abstractmethod +from typing import Any +from typing import Optional -from easyscience import ObjBase as BaseObj +from easyscience.base_classes import ModelBase from easyreflectometry.utils import yaml_dump -class BaseCore(BaseObj): +class BaseCore(ModelBase): + """Local base class for sample-tree objects (Material, Layer, assemblies). + + Built on top of `easyscience.base_classes.ModelBase` (the replacement for + the deprecated `ObjBase`). On top of `ModelBase` this class adds: + + - a `name` property + - an `interface` property whose setter propagates the calculator interface + to child objects and (re)generates bindings + - a yaml-formatted `__repr__` driven by an abstract `_dict_repr` + - an `_get_linkable_attributes` compatibility shim used by the calculator's + `InterfaceFactoryTemplate.generate_bindings` + - an `as_dict` alias for `to_dict` + + Subclass `__init__` convention: + 1. Build child Parameters / sub-objects. + 2. Call ``super().__init__(name=..., unique_name=...)``. + 3. Assign children to backing fields (``self._sld = sld`` etc.) or pass + them as ``**kwargs`` to this base class (transitional path; each + kwarg is stored as a plain instance attribute). + 4. Last: ``self.interface = interface`` (triggers ``generate_bindings``). + """ + def __init__( self, name: str, - interface, - **kwargs, + interface: Any = None, + unique_name: Optional[str] = None, + display_name: Optional[str] = None, + **kwargs: Any, ): - """Init function.""" - super().__init__(name=name, **kwargs) + super().__init__(unique_name=unique_name, display_name=display_name) + self._name = name + self._interface = None + # `user_data` is part of the legacy `BasedBase` API — a free-form dict + # callers stash arbitrary metadata in. Kept for back-compat with code + # like `Project.replace_models_from_orso` which stores the ORSO sample + # name on the model. + self.user_data: dict = {} - # Updates interface using property in base object - self.interface = interface + # Transitional path: subclasses still pass parameter / child objects via + # **kwargs (legacy `ObjBase` accepted them and stashed in `_kwargs`). + # Here we simply store each one as a plain instance attribute so + # `obj.` keeps working. Step 2 of the migration replaces this with + # explicit assignments in each subclass. + for key, value in kwargs.items(): + setattr(self, key, value) - @abstractmethod - def _dict_repr(self) -> dict[str, str]: ... + # Assign interface LAST so children exist when generate_bindings runs. + if interface is not None: + self.interface = interface - def __repr__(self) -> str: - """String representation of the layer. + # ----- name ----- + + @property + def name(self) -> str: + """Common (display-friendly) name.""" + return self._name + + @name.setter + def name(self, value: str) -> None: + self._name = value + + # ----- interface ----- + + @property + def interface(self) -> Any: + """The calculator interface attached to this object (may be None).""" + return self._interface - Returns - ------- - str - A string representation of the layer. + @interface.setter + def interface(self, new_interface: Any) -> None: + self._interface = new_interface + if new_interface is not None: + self.generate_bindings() + + def generate_bindings(self) -> None: + """Propagate the interface to child objects, then bind via the calculator. + + We propagate to any child whose class advertises an ``interface`` property + with a setter. That includes both the new `BaseCore`-based children and + legacy `BasedBase`-derived collections (which extend `SerializerComponent`, + not `NewBase`). """ - return yaml_dump(self._dict_repr) + if self._interface is None: + raise AttributeError('Interface error for generating bindings. `interface` has to be set.') + for attr in self._iter_public_children(): + if self._has_interface_setter(type(attr)): + attr.interface = self._interface + self._interface.generate_bindings(self) + + def _iter_public_children(self): + """Yield public child objects from both class-level (dir) and instance-level (__dict__) attrs. + + `NewBase.__dir__` exposes only class attributes, which means plain instance + attributes (the transitional `setattr(self, key, value)` path in + `__init__`) are invisible to a pure `dir()` scan. To bridge both worlds — + legacy subclasses that still use plain attrs, and migrated subclasses that + expose children via `@property` accessors — this helper unions the two. + Once all subclasses migrate to `@property`-backed children with `_field` + backing storage, the `__dict__` branch becomes a no-op (private names are + skipped). + """ + seen_ids = {id(self)} + # Class-level (properties, methods named like sld/isld/material). + for attr_name in dir(self): + if attr_name.startswith('_') or attr_name in ('interface', 'name'): + continue + try: + attr = getattr(self, attr_name, None) + except AttributeError: + # A subclass @property may legitimately raise AttributeError + # mid-construction (the `_field` backing isn't set yet); skip + # those entries silently. Other exceptions should propagate. + continue + if attr is None or id(attr) in seen_ids: + continue + seen_ids.add(id(attr)) + yield attr + # Instance-level (plain attrs set via the transitional kwargs path). + for attr_name, attr in list(self.__dict__.items()): + if attr_name.startswith('_') or attr_name in ('interface', 'name'): + continue + if attr is None or id(attr) in seen_ids: + continue + seen_ids.add(id(attr)) + yield attr + + @staticmethod + def _has_interface_setter(obj_type: type) -> bool: + for klass in obj_type.__mro__: + prop = klass.__dict__.get('interface') + if isinstance(prop, property): + return prop.fset is not None + return False + + # ----- compatibility shims ----- - # For classes with special serialization needs one must adopt the dict produced by super - # def as_dict(self, skip: list = None) -> dict: - # """Should produce a cleaned dict that matches the parameters in __init__ - # - # :param skip: List of keys to skip, defaults to `None`. - # """ - # if skip is None: - # skip = [] - # this_dict = super().as_dict(skip=skip) - # ... - # Correct the dict here - # ... - # return this_dict + def _get_linkable_attributes(self): + """Used by `easyscience.fitting.calculators.interface_factory.generate_bindings`. + + Returns the same set as :meth:`get_all_variables` (the modern API on + :class:`ModelBase`). Kept under the legacy name because the calculator + in `easyscience` core has not yet been updated. + """ + return self.get_all_variables() + + def get_parameters(self): + """Compatibility shim for legacy callers; prefer :meth:`get_all_parameters`.""" + return self.get_all_parameters() + + def _add_component(self, key: str, component: Any) -> None: + """Compatibility shim for legacy `ObjBase._add_component`. + + Legacy callers (e.g. `LayerAreaPerMolecule`) used this to register an + additional child after `super().__init__`. In the new world the + equivalent is simply setting an attribute; we do that here so the + existing call sites keep working until Step 2 removes them. + """ + setattr(self, key, component) + + def get_all_variables(self): + """Discover Parameters/Descriptors across both class-level and instance-level attrs. + + `ModelBase.get_all_variables` walks `dir(self)`, which `NewBase` restricts + to class attributes only. During the transition some subclasses still + store child Parameters as plain instance attributes (see the kwargs path + in :meth:`__init__`); those are invisible to `dir()`. We therefore also + scan `self.__dict__` for `DescriptorBase` instances and for child + ModelBase objects whose own `get_all_variables` we recurse into. + """ + from easyscience.variable.descriptor_base import DescriptorBase + + out: list = [] + seen_param_ids: set[int] = set() + for attr in self._iter_public_children(): + if isinstance(attr, DescriptorBase): + if id(attr) not in seen_param_ids: + seen_param_ids.add(id(attr)) + out.append(attr) + elif hasattr(attr, 'get_all_variables'): + for v in attr.get_all_variables(): + if id(v) not in seen_param_ids: + seen_param_ids.add(id(v)) + out.append(v) + return out + + def to_dict(self, skip: Optional[list[str]] = None) -> dict[str, Any]: + """Serialize, skipping the calculator interface and unique_name by default. + + The calculator (`CalculatorFactory`) is not serializable and is not part + of the model's persistent state — round-trip code that needs it back + reattaches it after `from_dict`. The legacy `ObjBase`-based pipeline + achieved the same by never including `interface` in its `_kwargs` + encoding; we replicate that here. + + `unique_name` is also stripped by default, matching the legacy + `BasedBase.as_dict` contract. The installed `SerializerBase` does *not* + propagate per-object `_default_unique_name` to nested children — if + we leave it in, child Parameters end up with explicit unique_names in + the dict (e.g. `Parameter_0`) that subsequently collide on reload when + the global counter restarts from 0. + + Pass a *copy* of `skip` to super since `NewBase.to_dict` mutates the + list (appends `unique_name` / `display_name` if those are default). + """ + skip = list(skip or []) + if 'interface' not in skip: + skip.append('interface') + if 'unique_name' not in skip: + skip.append('unique_name') + return super().to_dict(skip=list(skip)) + + def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, Any]: + """Compatibility alias for :meth:`to_dict`.""" + return self.to_dict(skip=skip) + + # ----- repr ----- + + @property + @abstractmethod + def _dict_repr(self) -> dict[str, Any]: ... + + def __repr__(self) -> str: + """Yaml-formatted multi-line string built from :attr:`_dict_repr`.""" + return yaml_dump(self._dict_repr) diff --git a/src/easyreflectometry/sample/collections/base_collection.py b/src/easyreflectometry/sample/collections/base_collection.py index 28d494a5..59f7038e 100644 --- a/src/easyreflectometry/sample/collections/base_collection.py +++ b/src/easyreflectometry/sample/collections/base_collection.py @@ -1,118 +1,331 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from __future__ import annotations + +from typing import Any from typing import List from typing import Optional -from easyscience import global_object -from easyscience.base_classes import CollectionBase as EasyBaseCollection +from easyscience.base_classes import EasyList +from easyscience.base_classes.new_base import NewBase +from easyscience.variable import Parameter from easyreflectometry.utils import yaml_dump -class BaseCollection(EasyBaseCollection): +class BaseCollection(EasyList): + """Local base for sample-tree collections (Material/Layer/Assembly/Model collections). + + Built on top of `easyscience.base_classes.EasyList` (the replacement for + the deprecated `CollectionBase`). On top of `EasyList` this class adds: + + - a `name` property + - an `interface` property whose setter propagates the calculator interface + to every contained item + - propagation of the current `interface` to newly inserted items + - a `populate_if_none` flag preserved for serialization round-trip + - convenience helpers `names`, `move_up`, `move_down`, `remove_at` + - a yaml-formatted `__repr__` driven by `_dict_repr` + - an `as_dict` alias for `to_dict` with `skip=` support and `interface` + excluded by default + + Subclasses (`LayerCollection`, `MaterialCollection`, `Sample`, + `ModelCollection`) keep their existing constructor shape — they pass + `name` and `interface` positionally to this class, items as `*args`, and + additional configuration as kwargs. + """ + def __init__( self, name: str, - interface, - *args, + interface: Any = None, + *args: Any, unique_name: Optional[str] = None, - **kwargs, + populate_if_none: bool = False, + **kwargs: Any, ): - """Init function.""" - if unique_name is None: - unique_name = global_object.generate_unique_name(self.__class__.__name__) + # `_interface` must exist before `super().__init__` because `EasyList` + # calls `self.append(item)` for each positional arg, which routes + # through our `insert` override and reads `self._interface`. + self._interface = None + self._name = name + # Legacy `CollectionBase` accepted items either positionally or as a + # list-valued keyword (e.g. ``LayerCollection(layers=[a, b])``). Pull + # any list-valued kwarg into the positional stream so callers using + # that older pattern keep working. + extra_items = [] + for key in list(kwargs.keys()): + if isinstance(kwargs[key], list) and kwargs[key] and key != 'data': + extra_items.extend(kwargs.pop(key)) + if extra_items: + args = tuple(args) + tuple(extra_items) + super().__init__(*args, unique_name=unique_name, **kwargs) + # `populate_if_none` is a control flag, not state. It is serialized so + # `from_dict` knows whether the original construction filled in + # defaults; the value should be `False` once the data is restored. + self.populate_if_none = populate_if_none - super().__init__(name, unique_name=unique_name, *args, **kwargs) - self.interface = interface + # Assign interface LAST — propagates to all contained items. + if interface is not None: + self.interface = interface - # Needed to ensure an empty list is created when saving and instatiating the object as_dict -> from_dict - # Else collisions might occur in global_object.map - self.populate_if_none = False + # ----- name ----- - def __repr__(self) -> str: - """String representation of the collection. + @property + def name(self) -> str: + return self._name - Returns - ------- - str - A string representation of the collection. - """ - return yaml_dump(self._dict_repr) + @name.setter + def name(self, value: str) -> None: + self._name = value + + # ----- interface ----- @property - def names(self) -> list: - """Names function. + def interface(self) -> Any: + return self._interface + + @interface.setter + def interface(self, new_interface: Any) -> None: + self._interface = new_interface + if new_interface is None: + return + # Propagate to existing items. + for item in self._data: + if self._has_interface_setter(type(item)): + item.interface = new_interface + # Tell the calculator to bind to self (matches the legacy CollectionBase + # behavior which called `interface.generate_bindings(self)` once per + # collection). + if hasattr(new_interface, 'generate_bindings'): + new_interface.generate_bindings(self) + + def _get_key(self, obj): + """Use the item's `name` for string-indexed lookups. - Returns - ------- - s : list - List of names for the elements in the collection. + Matches the legacy `CollectionBase.__getitem__` behaviour which + searched by `item.name`. `EasyList` defaults to `unique_name`; the + existing `Project` code (and callers) look items up by their pretty + name (e.g. `materials['Air']`). """ - return [i.name for i in self] + return getattr(obj, 'name', None) or obj.unique_name - def move_up(self, index: int): - """Move the element at the given index up in the collection. + @staticmethod + def _has_interface_setter(obj_type: type) -> bool: + for klass in obj_type.__mro__: + prop = klass.__dict__.get('interface') + if isinstance(prop, property): + return prop.fset is not None + return False - Parameters - ---------- - index : int - Index of the element to move up. + # ----- mutable-sequence overrides that propagate interface ----- + + def insert(self, index: int, value: Any) -> None: + """Insert and (if an interface is set) propagate it to the new item. + + Legacy `CollectionBase.insert` did `value.interface = self.interface` + after registering the item; we replicate the same behaviour here so + downstream calculator state stays in sync when items are appended + after the collection's interface was already set. + + The type check from `EasyList.insert` is bypassed: each subclass + accepts a single item type (Layer / Material / BaseAssembly / Model) + and enforces it elsewhere, while the EasyList check would require + every item to be a `NewBase` subclass — which was historically not + guaranteed and forces an extra coupling we don't need. """ - if index == 0: + if not isinstance(index, int): + raise TypeError('Index must be an integer') + # Skip the EasyList protected-types check; mimic the rest of its insert + # (duplicate-name warning + append-to-_data). + import warnings as _warnings + + if value in self: + _warnings.warn(f'Item with unique name "{self._get_key(value)}" already in collection, it will be ignored') return - self.insert(index - 1, self.pop(index)) + self._data.insert(index, value) + if self._interface is not None and self._has_interface_setter(type(value)): + value.interface = self._interface - def move_down(self, index: int): - """Move the element at the given index down in the collection. + # ----- helpers ----- - Parameters - ---------- - index : int - Index of the element to move down. + @property + def names(self) -> list: + """List of item names.""" + return [getattr(item, 'name', None) for item in self._data] + + @property + def data(self) -> list: + """Read-only view of the underlying item list. + + Provided for compatibility with code (and tests) written against the + legacy `CollectionBase` shape, which exposed items via `.data`. """ + return list(self._data) + + def move_up(self, index: int) -> None: + """Move the element at the given index up in the collection.""" + if index == 0: + return + self.insert(index - 1, self.pop(index)) + + def move_down(self, index: int) -> None: + """Move the element at the given index down in the collection.""" if index == len(self) - 1: return self.insert(index + 1, self.pop(index)) - def remove(self, index: int): - """Remove an element from the elements. + def remove_at(self, index: int) -> None: + """Remove the item at *index* from the collection. - Parameters - ---------- - index : int - Index of the element to remove. + Renamed from the legacy `BaseCollection.remove(index)` which shadowed + `MutableSequence.remove(value)` (remove-by-value, inherited from + `EasyList`). Callers that meant "remove by index" should use this; the + standard `remove(value)` is still available with its usual semantics. """ self.pop(index) + # ----- compatibility shims (kept until call sites migrate) ----- + + def get_parameters(self) -> List[Parameter]: + """Compatibility alias for legacy callers; prefer `get_all_parameters`.""" + return self.get_all_parameters() + + def get_all_variables(self) -> List: + """Flat list of every Parameter/Descriptor across all items. + + Walks each item in the collection and unions whatever each item + exposes via its own `get_all_variables` (for `ModelBase` / + `BaseCore` children) or, for objects that lack that hook, + unions any direct `DescriptorBase` attributes. + """ + from easyscience.variable.descriptor_base import DescriptorBase + + out: list = [] + seen: set[int] = set() + for item in self._data: + if hasattr(item, 'get_all_variables'): + for v in item.get_all_variables(): + if id(v) not in seen: + seen.add(id(v)) + out.append(v) + elif isinstance(item, DescriptorBase): + if id(item) not in seen: + seen.add(id(item)) + out.append(item) + return out + + def get_all_parameters(self) -> List[Parameter]: + return [v for v in self.get_all_variables() if isinstance(v, Parameter)] + + def get_free_parameters(self) -> List[Parameter]: + return [p for p in self.get_all_parameters() if p.independent and not p.fixed] + + def get_fit_parameters(self) -> List[Parameter]: + """Alias kept for the minimizer; matches `ModelBase.get_fit_parameters`.""" + return self.get_free_parameters() + + def _get_linkable_attributes(self) -> List[Parameter]: + """Bridge for `easyscience.fitting.calculators.interface_factory.generate_bindings`.""" + return self.get_all_variables() + + # ----- repr ----- + @property def _dict_repr(self) -> dict: - """A simplified dict representation. + """A simplified dict representation.""" + return {self.name: [getattr(i, '_dict_repr', repr(i)) for i in self._data]} - Returns - ------- - dict - Simple dictionary. - """ - return {self.name: [i._dict_repr for i in self]} + def __repr__(self) -> str: + try: + return yaml_dump(self._dict_repr) + except Exception: + return super().__repr__() - def as_dict(self, skip: Optional[List[str]] = None) -> dict: - """Create a dictionary representation of the collection. + # ----- serialization ----- - Returns - ------- - dict - A dictionary representation of the collection. + def _convert_to_dict(self, d: dict, encoder=None, skip: Optional[List[str]] = None, **kwargs) -> dict: + """Serializer hook used when this collection is encoded as a *child* + attribute (e.g. `Multilayer.layers`). + + `SerializerBase._convert_to_dict` iterates `_arg_spec` to populate the + base dict and then calls `obj._convert_to_dict(d, ...)` if defined. + Because `data` is supplied via `*args` (VAR_POSITIONAL — not part of + `_arg_spec`), without this hook the nested encoding would miss the + items entirely and round-trip would reconstruct an empty collection. """ if skip is None: skip = [] - this_dict = super().as_dict(skip=skip) - this_dict['data'] = [] - for collection_element in self: - this_dict['data'].append(collection_element.as_dict(skip=skip)) - this_dict['populate_if_none'] = self.populate_if_none - return this_dict + if self._protected_types != [NewBase] and 'protected_types' not in d: + d['protected_types'] = [{'@module': c.__module__, '@class': c.__name__} for c in self._protected_types] + # Encode each item. Defer to the encoder's recursive walk so nested + # ModelBase / NewBase items get properly serialized. + item_skip = list(skip) + items: list = [] + for item in self._data: + if encoder is not None and hasattr(encoder, '_recursive_encoder'): + items.append(encoder._recursive_encoder(item, skip=item_skip, encoder=encoder, full_encode=False)) + elif hasattr(item, 'to_dict'): + try: + items.append(item.to_dict(skip=list(item_skip))) + except TypeError: + items.append(item.to_dict()) + else: + items.append(item) + d['data'] = items + return d + + def to_dict(self, skip: Optional[List[str]] = None) -> dict: + """Serialize with `skip` support; `interface` excluded by default. + + `EasyList.to_dict` doesn't accept a `skip` argument and is hard-wired + to dump `data` plus the parent's `_arg_spec` view. We reimplement here + so existing callers (`Project`, `Model.as_dict`, etc.) can keep + passing `skip=['unique_name']` or similar. + + ``NewBase.to_dict`` mutates the ``skip`` list in-place (e.g. it + appends ``'unique_name'`` when the collection's own unique_name is + default-generated). We therefore pass a *copy* to it, otherwise the + mutation would leak into the per-item serialization below and force + every item Parameter dict to drop its ``unique_name`` — breaking the + from_dict round-trip. + """ + skip = list(skip or []) + if 'interface' not in skip: + skip.append('interface') + # Matches legacy `BasedBase.as_dict`: drop unique_name from the + # serialized form so nested Parameters don't get explicit names that + # would collide with the auto-generated names produced when the global + # counter restarts during reconstruction. + if 'unique_name' not in skip: + skip.append('unique_name') + dict_repr = NewBase.to_dict(self, skip=list(skip)) + if self._protected_types != [NewBase]: + dict_repr['protected_types'] = [{'@module': c.__module__, '@class': c.__name__} for c in self._protected_types] + dict_repr['data'] = [] + for item in self._data: + # Items that are ModelBase / BaseCore subclasses accept `skip`; + # other shapes use their no-arg `to_dict`/`as_dict`. + if hasattr(item, 'to_dict'): + try: + dict_repr['data'].append(item.to_dict(skip=list(skip))) + except TypeError: + dict_repr['data'].append(item.to_dict()) + else: + dict_repr['data'].append(item.as_dict(skip=list(skip))) + return dict_repr + + def as_dict(self, skip: Optional[List[str]] = None) -> dict: + """Compatibility alias for :meth:`to_dict`.""" + return self.to_dict(skip=skip) def __deepcopy__(self, memo): - """Deepcopy function.""" + """Round-trip via dict-skip-unique to get a fresh copy. + + `NewBase.__copy__` already does this; the override is kept (rather + than deleted) to mirror the legacy `BaseCollection.__deepcopy__` + semantics — callers that relied on `copy.deepcopy(collection)` still + get a clone built from `from_dict(as_dict(skip=['unique_name']))`. + """ return self.from_dict(self.as_dict(skip=['unique_name'])) diff --git a/src/easyreflectometry/sample/collections/layer_collection.py b/src/easyreflectometry/sample/collections/layer_collection.py index 99424f72..a3ae0f19 100644 --- a/src/easyreflectometry/sample/collections/layer_collection.py +++ b/src/easyreflectometry/sample/collections/layer_collection.py @@ -15,14 +15,21 @@ def __init__( name: str = 'EasyLayerCollection', interface=None, unique_name: Optional[str] = None, - populate_if_none: bool = True, # Needed to match as_dict signature from BaseCollection + populate_if_none: bool = True, **kwargs, ): """Init function.""" if not layers: layers = [] - super().__init__(name, interface, unique_name=unique_name, *layers, **kwargs) + super().__init__( + name, + interface, + *layers, + unique_name=unique_name, + populate_if_none=populate_if_none, + **kwargs, + ) def add_layer(self, layer: Optional[Layer] = None): """Add a layer to the collection. diff --git a/src/easyreflectometry/sample/collections/material_collection.py b/src/easyreflectometry/sample/collections/material_collection.py index d7b5af3b..726678b2 100644 --- a/src/easyreflectometry/sample/collections/material_collection.py +++ b/src/easyreflectometry/sample/collections/material_collection.py @@ -30,7 +30,7 @@ def __init__( **kwargs, ): """Init function.""" - if not materials: # Empty tuple if no materials are provided + if not materials: if populate_if_none: materials = DEFAULT_ELEMENTS(interface) else: @@ -39,8 +39,9 @@ def __init__( super().__init__( name, interface, - unique_name=unique_name, *materials, + unique_name=unique_name, + populate_if_none=False, **kwargs, ) diff --git a/src/easyreflectometry/sample/collections/sample.py b/src/easyreflectometry/sample/collections/sample.py index 1bbdb156..384626e8 100644 --- a/src/easyreflectometry/sample/collections/sample.py +++ b/src/easyreflectometry/sample/collections/sample.py @@ -52,6 +52,11 @@ def __init__( interface : Calculator interface. By default, None. """ + # `from_dict` (via `EasyList.from_dict`) passes the items as a single + # list-positional arg; unpack that so validation and super() agree. + if len(assemblies) == 1 and isinstance(assemblies[0], list): + assemblies = tuple(assemblies[0]) + if not assemblies: if populate_if_none: assemblies = DEFAULT_ELEMENTS(interface) @@ -61,7 +66,14 @@ def __init__( for assembly in assemblies: if not issubclass(type(assembly), BaseAssembly): raise ValueError('The elements must be an Assembly.') - super().__init__(name, interface, unique_name=unique_name, *assemblies, **kwargs) + super().__init__( + name, + interface, + *assemblies, + unique_name=unique_name, + populate_if_none=populate_if_none, + **kwargs, + ) def add_assembly(self, assembly: Optional[BaseAssembly] = None): """Add an assembly to the sample. @@ -87,13 +99,19 @@ def duplicate_assembly(self, index: int): assembly : Assembly to add. """ + # Order matters: RepeatingMultilayer and SurfactantLayer are subclasses of + # BaseAssembly but not Multilayer; however a RepeatingMultilayer IS a + # Multilayer, so the most-specific check must come first to avoid + # serialising it through the wrong `from_dict`. to_be_duplicated = self[index] - if isinstance(to_be_duplicated, Multilayer): - duplicate = Multilayer.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) - elif isinstance(to_be_duplicated, RepeatingMultilayer): + if isinstance(to_be_duplicated, RepeatingMultilayer): duplicate = RepeatingMultilayer.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) elif isinstance(to_be_duplicated, SurfactantLayer): duplicate = SurfactantLayer.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) + elif isinstance(to_be_duplicated, Multilayer): + duplicate = Multilayer.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) + else: + raise TypeError(f'Cannot duplicate assembly of type {type(to_be_duplicated).__name__}') duplicate.name = duplicate.name + ' duplicate' self.append(duplicate) @@ -140,18 +158,3 @@ def subphase(self) -> Layer: return self[-1].front_layer else: return self[-1].back_layer - - # Representation - def as_dict(self, skip: Optional[List[str]] = None) -> dict: - """Produces a cleaned dict using a custom as_dict method to skip necessary things. - - The resulting dict matches the parameters in __init__ - - Parameters - ---------- - skip : Optional[List[str]], optional - List of keys to skip. By default, None. - """ - this_dict = super().as_dict(skip=skip) - this_dict['populate_if_none'] = self.populate_if_none - return this_dict diff --git a/src/easyreflectometry/sample/elements/layers/layer.py b/src/easyreflectometry/sample/elements/layers/layer.py index fa58dc15..7eea9872 100644 --- a/src/easyreflectometry/sample/elements/layers/layer.py +++ b/src/easyreflectometry/sample/elements/layers/layer.py @@ -37,14 +37,6 @@ class Layer(BaseCore): - # Added in super().__init__ - #: Material that makes up the layer. - material: Material - #: Thickness of the layer in Angstrom. - thickness: Parameter - #: Roughness of the layer in Angstrom. - roughness: Parameter - def __init__( self, material: Union[Material, None] = None, @@ -95,14 +87,37 @@ def __init__( ) roughness.default_limits_pending = not isinstance(roughness_value, Parameter) - super().__init__( - name=name, - interface=interface, - material=material, - thickness=thickness, - roughness=roughness, - unique_name=unique_name, - ) + super().__init__(name=name, unique_name=unique_name) + self._material = material + self._thickness = thickness + self._roughness = roughness + + if interface is not None: + self.interface = interface + + @property + def material(self) -> Material: + return self._material + + @material.setter + def material(self, value: Material) -> None: + self._material = value + + @property + def thickness(self) -> Parameter: + return self._thickness + + @thickness.setter + def thickness(self, value: float) -> None: + self._thickness.value = value + + @property + def roughness(self) -> Parameter: + return self._roughness + + @roughness.setter + def roughness(self, value: float) -> None: + self._roughness.value = value def assign_material(self, material: Material) -> None: """Assign a material to the layer interface. @@ -112,7 +127,7 @@ def assign_material(self, material: Material) -> None: material : Material The material to assign to the layer. """ - self.material = material + self._material = material if self.interface is not None: self.interface().assign_material_to_layer(self.material.unique_name, self.unique_name) diff --git a/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py b/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py index 9053aa95..32161dcc 100644 --- a/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py +++ b/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py @@ -55,17 +55,6 @@ class LayerAreaPerMolecule(Layer): molecular formula an area per molecule, and a solvent. """ - # Added in __init__ - #: Real part of the scattering length. - _scattering_length_real: Parameter - #: Imaginary part of the scattering length. - _scattering_length_imag: Parameter - #: Area per molecule in the layer in Anstrom^2. - _area_per_molecule: Parameter - - # Other typer than in __init__.super() - material: MaterialSolvated - def __init__( self, molecular_formula: Union[str, None] = None, @@ -113,7 +102,6 @@ def __init__( interface=interface, ) - # Create the solvated molecule and corresponding constraints if molecular_formula is None: molecular_formula = DEFAULTS['molecular_formula'] molecule_material = Material( @@ -130,40 +118,37 @@ def __init__( default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Thickness', ) - _area_per_molecule = get_as_parameter( + area_per_molecule_param = get_as_parameter( name='area_per_molecule', value=area_per_molecule, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_AreaPerMolecule', ) - _scattering_length_real = get_as_parameter( + scattering_length_real = get_as_parameter( name='scattering_length_real', value=0.0, default_dict=DEFAULTS['sl'], unique_name_prefix=f'{unique_name}_Sl', ) - _scattering_length_imag = get_as_parameter( + scattering_length_imag = get_as_parameter( name='scattering_length_imag', value=0.0, default_dict=DEFAULTS['isl'], unique_name_prefix=f'{unique_name}_Isl', ) - # Constrain the real part of the sld value for the molecule - dependency_expression = 'scattering_length / (thickness * area_per_molecule) * 1e6' - dependency_map = { - 'scattering_length': _scattering_length_real, - 'thickness': thickness, - 'area_per_molecule': _area_per_molecule, - } - molecule_material.sld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) - - # # Constrain the real part of the sld value for the molecule - dependency_expression = 'a / (b*p) * 1e6' - dependency_map = {'a': _scattering_length_real, 'b': thickness, 'p': _area_per_molecule} - molecule_material.sld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) - dependency_map = {'a': _scattering_length_imag, 'b': thickness, 'p': _area_per_molecule} - molecule_material.isld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) + # Constrain molecule.sld / .isld to scattering_length / (thickness * area_per_molecule). + # `_setup_sld_constraints` rebuilds the same expression after from_dict, so keep the + # variable names (`a`, `b`, `p`) consistent with that path. + dependency_expression = 'a / (b*p) * 1e6' + molecule_material.sld.make_dependent_on( + dependency_expression=dependency_expression, + dependency_map={'a': scattering_length_real, 'b': thickness, 'p': area_per_molecule_param}, + ) + molecule_material.isld.make_dependent_on( + dependency_expression=dependency_expression, + dependency_map={'a': scattering_length_imag, 'b': thickness, 'p': area_per_molecule_param}, + ) solvated_molecule_material = MaterialSolvated( material=molecule_material, @@ -178,17 +163,85 @@ def __init__( roughness=roughness, name=name, unique_name=unique_name, - interface=interface, + interface=None, ) - self._add_component('_scattering_length_real', _scattering_length_real) - self._add_component('_scattering_length_imag', _scattering_length_imag) - self._add_component('_area_per_molecule', _area_per_molecule) + self._area_per_molecule = area_per_molecule_param + self._scattering_length_real = scattering_length_real + self._scattering_length_imag = scattering_length_imag scattering_length = neutron_scattering_length(molecular_formula) self._scattering_length_real.value = scattering_length.real self._scattering_length_imag.value = scattering_length.imag self._molecular_formula = molecular_formula - self.interface = interface + + if interface is not None: + self.interface = interface + + # ----- constraint plumbing ----- + + def _setup_sld_constraints(self) -> None: + """Wire the inner molecule material's ``sld`` / ``isld`` to depend on + the current scattering-length, thickness, and area-per-molecule + parameters. + + Idempotent — called once from ``__init__`` and again from + ``from_dict`` after the saved Parameter objects replace the + constructor-time temporaries. + """ + molecule_material = self.material.material + for derived in (molecule_material.sld, molecule_material.isld): + if not derived.independent: + derived.make_independent() + + dependency_expression = 'a / (b*p) * 1e6' + molecule_material.sld.make_dependent_on( + dependency_expression=dependency_expression, + dependency_map={ + 'a': self._scattering_length_real, + 'b': self._thickness, + 'p': self._area_per_molecule, + }, + ) + molecule_material.isld.make_dependent_on( + dependency_expression=dependency_expression, + dependency_map={ + 'a': self._scattering_length_imag, + 'b': self._thickness, + 'p': self._area_per_molecule, + }, + ) + + # ----- deserialization ----- + + @classmethod + def from_dict(cls, obj_dict: dict) -> 'LayerAreaPerMolecule': + """Re-route the saved ``solvent_fraction`` Parameter and rebuild the + molecule-SLD constraint chain after :class:`ModelBase.from_dict` + swaps in the persisted Parameter objects. + + `ModelBase.from_dict` writes the deserialized ``solvent_fraction`` + Parameter to ``self._solvent_fraction`` (orphan — the live property + delegates to ``self.material.solvent_fraction``, which is + ``self.material._fraction``). It also reassigns ``self._thickness`` + and ``self._area_per_molecule``, but the constraint graph built in + ``__init__`` still references the temporary Parameters created from + the float kwargs. We fix both here. + """ + instance = super().from_dict(obj_dict) + + saved_solvent_fraction = instance.__dict__.pop('_solvent_fraction', None) + if saved_solvent_fraction is not None: + mixture = instance.material + old = mixture._fraction + mixture._fraction = saved_solvent_fraction + try: + instance._global_object.map.prune(old.unique_name) + except (AttributeError, KeyError): + pass + mixture._materials_constraints() + + instance._setup_sld_constraints() + return instance @property def area_per_molecule_parameter(self) -> Parameter: @@ -196,22 +249,15 @@ def area_per_molecule_parameter(self) -> Parameter: return self._area_per_molecule @property - def area_per_molecule(self) -> float: - """Get the area per molecule.""" - return self._area_per_molecule.value + def area_per_molecule(self) -> Parameter: + """The Parameter that controls area per molecule.""" + return self._area_per_molecule @area_per_molecule.setter - def area_per_molecule(self, new_area_per_molecule: float) -> None: - """Set the area per molecule. - - Parameters - ---------- - new_area_per_molecule : float - New area per molecule. - """ - if new_area_per_molecule < 0: - raise ValueError('new_area_per_molecule must be greater than 0.0.') - self._area_per_molecule.value = new_area_per_molecule + def area_per_molecule(self, value: float) -> None: + if value < 0: + raise ValueError('area_per_molecule must be greater than 0.0.') + self._area_per_molecule.value = value @property def molecule(self) -> Material: @@ -225,40 +271,21 @@ def solvent(self) -> Material: @solvent.setter def solvent(self, new_solvent: Material) -> None: - """Set the solvent material. - - Parameters - ---------- - new_solvent : Material - New solvent material. - """ self.material.solvent = new_solvent @property - def solvent_fraction_parameter(self) -> float: + def solvent_fraction_parameter(self) -> Parameter: """Get parameter for the fraction of the layer occupied by the solvent.""" return self.material.solvent_fraction_parameter @property - def solvent_fraction(self) -> float: - """Get the fraction of the layer occupied by the solvent. - - This could be a result of either water solvating the molecule, or incomplete surface coverage of the molecules. - """ + def solvent_fraction(self) -> Parameter: + """The Parameter for the fraction of the layer occupied by the solvent.""" return self.material.solvent_fraction @solvent_fraction.setter - def solvent_fraction(self, solvent_fraction: float) -> None: - """Set the fraction of the layer occupied by the solvent. - - This could be a result of either water solvating the molecule, or incomplete surface coverage of the molecules. - - Parameters - ---------- - solvent_fraction : float - Fraction of layer described by the solvent. - """ - self.material.solvent_fraction = solvent_fraction + def solvent_fraction(self, value: float) -> None: + self.material.solvent_fraction = value @property def molecular_formula(self) -> str: @@ -267,16 +294,8 @@ def molecular_formula(self) -> str: @molecular_formula.setter def molecular_formula(self, formula_string: str) -> None: - """Set the formula of the molecule in the material. - - Parameters - ---------- - formula_string : str - String that defines the molecular formula. - """ self._molecular_formula = formula_string scattering_length = neutron_scattering_length(formula_string) - # The molecule is also being updated through the constraints self._scattering_length_real.value = scattering_length.real self._scattering_length_imag.value = scattering_length.imag @@ -285,30 +304,8 @@ def molecular_formula(self, formula_string: str) -> None: @property def _dict_repr(self) -> dict[str, str]: - """Dictionary representation of the `area_per_molecule` object. - - Produces a simple dictionary. - """ + """Dictionary representation of the `area_per_molecule` object.""" dict_repr = super()._dict_repr dict_repr['molecular_formula'] = self._molecular_formula - dict_repr['area_per_molecule'] = f'{self.area_per_molecule:.2f} {self._area_per_molecule.unit}' + dict_repr['area_per_molecule'] = f'{self._area_per_molecule.value:.2f} {self._area_per_molecule.unit}' return dict_repr - - def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, str]: - """Produces a cleaned dict using a custom as_dict method to skip necessary things. - - The resulting dict matches the parameters in __init__ - - Parameters - ---------- - skip : Optional[list[str]], optional - List of keys to skip. By default, None. - """ - this_dict = super().as_dict(skip=skip) - this_dict['solvent_fraction'] = self.material._fraction.as_dict(skip=skip) - this_dict['area_per_molecule'] = self._area_per_molecule.as_dict(skip=skip) - this_dict['solvent'] = self.solvent.as_dict(skip=skip) - del this_dict['material'] - del this_dict['_scattering_length_real'] - del this_dict['_scattering_length_imag'] - return this_dict diff --git a/src/easyreflectometry/sample/elements/materials/material.py b/src/easyreflectometry/sample/elements/materials/material.py index 42091bd6..f072a639 100644 --- a/src/easyreflectometry/sample/elements/materials/material.py +++ b/src/easyreflectometry/sample/elements/materials/material.py @@ -37,10 +37,6 @@ class Material(BaseCore): - # Added in super().__init__ - sld: Parameter - isld: Parameter - def __init__( self, sld: Union[Parameter, float, None] = None, @@ -83,13 +79,28 @@ def __init__( ) apply_default_limits(isld, 'isld') - super().__init__( - name=name, - sld=sld, - isld=isld, - interface=interface, - unique_name=unique_name, - ) + super().__init__(name=name, unique_name=unique_name) + self._sld = sld + self._isld = isld + + if interface is not None: + self.interface = interface + + @property + def sld(self) -> Parameter: + return self._sld + + @sld.setter + def sld(self, value: float) -> None: + self._sld.value = value + + @property + def isld(self) -> Parameter: + return self._isld + + @isld.setter + def isld(self, value: float) -> None: + self._isld.value = value # Representation @property @@ -97,7 +108,7 @@ def _dict_repr(self) -> dict[str, str]: """A simplified dict representation.""" return { self.name: { - 'sld': f'{self.sld.value:.3f}e-6 {self.sld.unit}', - 'isld': f'{self.isld.value:.3f}e-6 {self.isld.unit}', + 'sld': f'{self._sld.value:.3f}e-6 {self._sld.unit}', + 'isld': f'{self._isld.value:.3f}e-6 {self._isld.unit}', } } diff --git a/src/easyreflectometry/sample/elements/materials/material_density.py b/src/easyreflectometry/sample/elements/materials/material_density.py index b85bda98..5be1cf9d 100644 --- a/src/easyreflectometry/sample/elements/materials/material_density.py +++ b/src/easyreflectometry/sample/elements/materials/material_density.py @@ -41,12 +41,6 @@ class MaterialDensity(Material): - # Added in __init__ - scattering_length_real: Parameter - scattering_length_imag: Parameter - molecular_weight: Parameter - density: Parameter - def __init__( self, chemical_structure: Union[str, None] = None, @@ -123,14 +117,59 @@ def __init__( dependency_map = {'d': density, 'sl': scattering_length_imag, 'mw': mw} isld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) - super().__init__(sld, isld, name=name, interface=interface) + super().__init__(sld=sld, isld=isld, name=name, unique_name=unique_name, interface=None) - self._add_component('scattering_length_real', scattering_length_real) - self._add_component('scattering_length_imag', scattering_length_imag) - self._add_component('molecular_weight', mw) - self._add_component('density', density) + self._scattering_length_real = scattering_length_real + self._scattering_length_imag = scattering_length_imag + self._molecular_weight = mw + self._density = density self._chemical_structure = chemical_structure - self.interface = interface + + if interface is not None: + self.interface = interface + + def _setup_sld_constraints(self) -> None: + """Wire the derived `sld` / `isld` to depend on the current density and + scattering-length Parameters. + + Idempotent — invoked once from `__init__` and again from `from_dict` + after :class:`ModelBase` has swapped in the saved Parameter objects. + """ + for derived in (self._sld, self._isld): + if not derived.independent: + derived.make_independent() + + dependency_expression = '1e-23*(0.602214076e6 * d * sl) / mw' + self._sld.make_dependent_on( + dependency_expression=dependency_expression, + dependency_map={ + 'd': self._density, + 'sl': self._scattering_length_real, + 'mw': self._molecular_weight, + }, + ) + self._isld.make_dependent_on( + dependency_expression=dependency_expression, + dependency_map={ + 'd': self._density, + 'sl': self._scattering_length_imag, + 'mw': self._molecular_weight, + }, + ) + + @classmethod + def from_dict(cls, obj_dict: dict) -> 'MaterialDensity': + """Re-attach sld/isld dependencies after deserialization. + + :class:`ModelBase.from_dict` re-points `self._density` at the + deserialized Parameter (because `density` is a constructor argument); + the constraint graph built in `__init__` still references the + temporary Parameter created from the float kwarg. Rebuild here so + `q.density = X` propagates to the derived SLDs. + """ + instance = super().from_dict(obj_dict) + instance._setup_sld_constraints() + return instance @property def chemical_structure(self) -> str: @@ -148,8 +187,28 @@ def chemical_structure(self, structure_string: str) -> None: """ self._chemical_structure = structure_string scattering_length = neutron_scattering_length(structure_string) - self.scattering_length_real.value = scattering_length.real - self.scattering_length_imag.value = scattering_length.imag + self._scattering_length_real.value = scattering_length.real + self._scattering_length_imag.value = scattering_length.imag + + @property + def density(self) -> Parameter: + return self._density + + @density.setter + def density(self, value: float) -> None: + self._density.value = value + + @property + def molecular_weight(self) -> Parameter: + return self._molecular_weight + + @property + def scattering_length_real(self) -> Parameter: + return self._scattering_length_real + + @property + def scattering_length_imag(self) -> Parameter: + return self._scattering_length_imag @property def _dict_repr(self) -> dict[str, str]: @@ -158,23 +217,3 @@ def _dict_repr(self) -> dict[str, str]: mat_dict['chemical_structure'] = self._chemical_structure mat_dict['density'] = f'{self.density.value:.2e} {self.density.unit}' return mat_dict - - def as_dict(self, skip: list = []) -> dict[str, str]: - """Produces a cleaned dict using a custom as_dict method to skip necessary things. - - The resulting dict matches the parameters in __init__ - - Parameters - ---------- - skip : list, optional - List of keys to skip. By default, []. - """ - this_dict = super().as_dict(skip=skip) - # From Material - del this_dict['sld'] - del this_dict['isld'] - # Determined in __init__ - del this_dict['scattering_length_real'] - del this_dict['scattering_length_imag'] - del this_dict['molecular_weight'] - return this_dict diff --git a/src/easyreflectometry/sample/elements/materials/material_mixture.py b/src/easyreflectometry/sample/elements/materials/material_mixture.py index c999093e..1ab76ddc 100644 --- a/src/easyreflectometry/sample/elements/materials/material_mixture.py +++ b/src/easyreflectometry/sample/elements/materials/material_mixture.py @@ -28,11 +28,6 @@ class MaterialMixture(BaseCore): - # Added in super().__init__ - _material_a: Material - _material_b: Material - _fraction: Parameter - def __init__( self, material_a: Union[Material, None] = None, @@ -74,108 +69,57 @@ def __init__( unique_name_prefix=f'{unique_name}_Fraction', ) - sld = weighted_average( + sld_value = weighted_average( a=material_a.sld.value, b=material_b.sld.value, p=fraction.value, ) - isld = weighted_average( + isld_value = weighted_average( a=material_a.isld.value, b=material_b.isld.value, p=fraction.value, ) - self._sld = get_as_parameter( + sld = get_as_parameter( name='sld', - value=sld, + value=sld_value, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Sld', ) - self._isld = get_as_parameter( + isld = get_as_parameter( name='isld', - value=isld, + value=isld_value, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Isld', ) - # To avoid problems when setting the interface - # self._sld and self._isld need to be declared before calling the super constructor - super().__init__( - name, - _material_a=material_a, - _material_b=material_b, - _fraction=fraction, - interface=interface, - ) + # `name` may be None to signal "derive from material names"; resolve + # before super().__init__ since BaseCore stores `_name` directly. if name is None: - self._update_name() - - self._materials_constraints() - self.interface = interface - - def _get_linkable_attributes(self): - """Get linkable attributes.""" - return [self._sld, self._isld] - - @property - def sld(self) -> float: - """Sld function.""" - return self._sld.value - - @property - def isld(self) -> float: - """Isld function.""" - return self._isld.value - - def _materials_constraints(self): - """Materials constraints.""" - dependency_expression = 'a * (1 - p) + b * p' - dependency_map = { - 'a': self._material_a.sld, - 'b': self._material_b.sld, - 'p': self._fraction, - } - self._sld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) + resolved_name = material_a.name + '/' + material_b.name + else: + resolved_name = name - dependency_map = { - 'a': self._material_a.isld, - 'b': self._material_b.isld, - 'p': self._fraction, - } - self._isld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) + super().__init__(name=resolved_name, unique_name=unique_name) + self._material_a = material_a + self._material_b = material_b + self._fraction = fraction + self._sld = sld + self._isld = isld - @property - def fraction(self) -> float: - """Get the fraction of material_b.""" - return self._fraction.value + self._materials_constraints() - @fraction.setter - def fraction(self, fraction: float) -> None: - """Setter for fraction of material_b. + if interface is not None: + self.interface = interface - Parameters - ---------- - fraction : float - The fraction of material_b in material_a. - """ - if not isinstance(fraction, float): - raise ValueError('fraction must be a float') - self._fraction.value = fraction + # ----- constructor-arg accessors ----- @property def material_a(self) -> Material: - """Getter for material_a.""" return self._material_a @material_a.setter def material_a(self, new_material_a: Material) -> None: - """Setter for material_a. - - Parameters - ---------- - new_material_a : Material - New Material for material_a. - """ self._material_a = new_material_a self._materials_constraints() if self.interface is not None: @@ -184,28 +128,108 @@ def material_a(self, new_material_a: Material) -> None: @property def material_b(self) -> Material: - """Getter for material_b.""" return self._material_b @material_b.setter def material_b(self, new_material_b: Material) -> None: - """Setter for material_b. - - Parameters - ---------- - new_material_b : Material - New Materialfor material_b. - """ self._material_b = new_material_b self._materials_constraints() if self.interface is not None: self.interface.generate_bindings(self) self._update_name() + @property + def fraction(self) -> Parameter: + """The Parameter that controls the mixing fraction of material_b in material_a.""" + return self._fraction + + @fraction.setter + def fraction(self, value: float) -> None: + if not isinstance(value, (int, float)): + raise ValueError('fraction must be a float') + self._fraction.value = value + + # ----- derived sld / isld parameters (shared shape with Material) ----- + # + # These are *derived* via the constraints set up in `_materials_constraints` + # (not constructor arguments) so we expose them as floats to match the + # legacy MaterialMixture API. The underlying Parameter objects remain + # available as `self._sld` / `self._isld`. + + @property + def sld(self) -> float: + return self._sld.value + + @property + def isld(self) -> float: + return self._isld.value + + # ----- calculator binding ----- + + def _get_linkable_attributes(self): + """Return the *mixed* sld / isld parameters for calculator binding. + + Override of the inherited `BaseCore._get_linkable_attributes`, which + walks `get_all_variables()` and would otherwise expose the **child** + materials' sld/isld (because our own `sld` / `isld` are floats, not + Parameters). The calculator's `InterfaceFactoryTemplate.generate_bindings` + matches by parameter `name`; without this override it binds to + `material_a.sld` and reflectivity is computed off the wrong SLD. + """ + return [self._sld, self._isld] + + # ----- internal helpers ----- + + def _materials_constraints(self): + """Wire the mixed `_sld` / `_isld` to depend on the current child + material parameters and the current `_fraction`. Idempotent: callers + invoke this once from ``__init__`` and again from ``from_dict`` after + the saved Parameters have been reattached (so the dependency graph + points at the right objects, not the temporary constructor params).""" + # Detach any existing dependency before rebuilding so make_dependent_on + # doesn't chain on top of stale references. + for derived in (self._sld, self._isld): + if not derived.independent: + derived.make_independent() + + dependency_expression = 'a * (1 - p) + b * p' + dependency_map = { + 'a': self._material_a.sld, + 'b': self._material_b.sld, + 'p': self._fraction, + } + self._sld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) + + dependency_map = { + 'a': self._material_a.isld, + 'b': self._material_b.isld, + 'p': self._fraction, + } + self._isld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map) + def _update_name(self) -> None: """Update name.""" self.name = self._material_a.name + '/' + self._material_b.name + # ----- deserialization ----- + + @classmethod + def from_dict(cls, obj_dict: dict) -> 'MaterialMixture': + """Re-attach mixed-sld dependencies after :class:`ModelBase` swaps in + the saved ``_fraction`` Parameter. + + :class:`ModelBase.from_dict` runs ``__init__`` (which builds the + ``_sld`` / ``_isld`` constraints against the *temporary* ``_fraction`` + created from the float kwargs) and then re-points ``self._fraction`` + at the persisted Parameter. The constraint graph still references the + temporary object, so subsequent ``mm.fraction = X`` mutations don't + propagate to ``_sld`` / ``_isld``. Re-running ``_materials_constraints`` + here points the graph at the live objects. + """ + instance = super().from_dict(obj_dict) + instance._materials_constraints() + return instance + # Representation @property def _dict_repr(self) -> dict[str, str]: @@ -219,19 +243,3 @@ def _dict_repr(self) -> dict[str, str]: 'material_b': self._material_b._dict_repr, } } - - def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, str]: - """Produces a cleaned dict using a custom as_dict method to skip necessary things. - - The resulting dict matches the parameters in __init__ - - Parameters - ---------- - skip : Optional[list[str]], optional - List of keys to skip. By default, None. - """ - this_dict = super().as_dict(skip=skip) - this_dict['material_a'] = self._material_a.as_dict(skip=skip) - this_dict['material_b'] = self._material_b.as_dict(skip=skip) - this_dict['fraction'] = self._fraction.as_dict(skip=skip) - return this_dict diff --git a/src/easyreflectometry/sample/elements/materials/material_solvated.py b/src/easyreflectometry/sample/elements/materials/material_solvated.py index 454904ba..d6121d10 100644 --- a/src/easyreflectometry/sample/elements/materials/material_solvated.py +++ b/src/easyreflectometry/sample/elements/materials/material_solvated.py @@ -72,6 +72,7 @@ def __init__( material_b=solvent, fraction=solvent_fraction, name=name, + unique_name=unique_name, interface=interface, ) if name is None: @@ -84,13 +85,7 @@ def material(self) -> Material: @material.setter def material(self, new_material: Material) -> None: - """Set the material. - - Parameters - ---------- - new_material : Material - Matrerial to be useed. - """ + """Set the material.""" self.material_a = new_material @property @@ -100,13 +95,7 @@ def solvent(self) -> Material: @solvent.setter def solvent(self, new_solvent: Material) -> None: - """Set the solvent. - - Parameters - ---------- - new_solvent : Material - Solvent to be used. - """ + """Set the solvent.""" self.material_b = new_solvent @property @@ -115,39 +104,58 @@ def solvent_fraction_parameter(self) -> Parameter: return self._fraction @property - def solvent_fraction(self) -> float: - """Get the fraction of layer described by the solvent. + def solvent_fraction(self) -> Parameter: + """The Parameter for the fraction of the layer described by the solvent. - This might be fraction of: - Solvation where solvent is within the layer - Patches of solvent in the layer where no material is present. + This might be the fraction of: + - solvation where solvent is within the layer, or + - patches of solvent in the layer where no material is present. """ - return self.fraction + return self._fraction @solvent_fraction.setter def solvent_fraction(self, solvent_fraction: float) -> None: - """Set the fraction of layer covered by the material. - - This might be fraction of: - Solvation where solvent is within the layer - Patches of solvent in the layer where no material is present. - - Parameters - ---------- - solvent_fraction : float - Fraction of layer described by the solvent. - """ - try: - self.fraction = solvent_fraction - if solvent_fraction < 0 or solvent_fraction > 1: - raise ValueError('solvent_fraction must be between 0 and 1') - except ValueError: + """Set the fraction of layer covered by the material.""" + if not isinstance(solvent_fraction, (int, float)): raise ValueError('solvent_fraction must be a float between 0 and 1') + if solvent_fraction < 0 or solvent_fraction > 1: + raise ValueError('solvent_fraction must be between 0 and 1') + self._fraction.value = solvent_fraction def _update_name(self) -> None: """Update name.""" self.name = self._material_a.name + ' in ' + self._material_b.name + # ----- deserialization ----- + + @classmethod + def from_dict(cls, obj_dict: dict) -> 'MaterialSolvated': + """Re-route the saved ``solvent_fraction`` Parameter onto ``_fraction``. + + :class:`ModelBase.from_dict` writes the saved Parameter to + ``_solvent_fraction`` because that's the constructor-arg name, but + the live `solvent_fraction` property returns ``self._fraction`` + (the field MaterialMixture maintains). Without this override the + saved fit metadata (fixed/bounds/etc.) is stranded on the unused + ``_solvent_fraction`` attribute and the active parameter keeps the + defaults from `__init__`. + + Also re-runs `_materials_constraints` so the parent MaterialMixture's + mixed `_sld` / `_isld` depend on the live `_fraction`, not the + temporary Parameter created from the float kwarg. + """ + instance = super().from_dict(obj_dict) + saved = instance.__dict__.pop('_solvent_fraction', None) + if saved is not None: + old = instance._fraction + instance._fraction = saved + try: + instance._global_object.map.prune(old.unique_name) + except (AttributeError, KeyError): + pass + instance._materials_constraints() + return instance + # Representation @property def _dict_repr(self) -> dict[str, str]: @@ -161,28 +169,3 @@ def _dict_repr(self) -> dict[str, str]: 'solvent': self.solvent._dict_repr, } } - - def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, str]: - """Produces a cleaned dict using a custom as_dict method to skip necessary things. - - The resulting dict matches the parameters in __init__ - - Parameters - ---------- - skip : Optional[list[str]], optional - List of keys to skip. By default, None. - """ - this_dict = super().as_dict(skip=skip) - this_dict['material'] = self.material.as_dict(skip=skip) - this_dict['solvent'] = self.solvent.as_dict(skip=skip) - this_dict['solvent_fraction'] = self._fraction.as_dict(skip=skip) - # Property and protected varible from material_mixture - del this_dict['material_a'] - del this_dict['_material_a'] - # Property and protected varible from material_mixture - del this_dict['material_b'] - del this_dict['_material_b'] - # Property and protected varible from material_mixture - del this_dict['fraction'] - del this_dict['_fraction'] - return this_dict diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index c06751af..2fc2846f 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -191,7 +191,7 @@ def _sample_section(self) -> str: # Get parameters directly from the model instead of using project.parameters model = self._project._models[self._project.current_model_index] - parameters = model.get_parameters() + parameters = model.get_all_parameters() for parameter in parameters: path = global_object.map.find_path(model.unique_name, parameter.unique_name) @@ -248,7 +248,7 @@ def _refinement_section(self) -> str: # Get parameters directly from the model model = self._project._models[self._project.current_model_index] - parameters = model.get_parameters() + parameters = model.get_all_parameters() num_free_params = sum(1 for parameter in parameters if parameter.free) num_fixed_params = sum(1 for parameter in parameters if not parameter.free) diff --git a/tests/model/test_model.py b/tests/model/test_model.py index 2d623512..b11149c1 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -430,3 +430,120 @@ def test_dict_round_trip(interface): model.interface().reflectity_profile([0.3], model.unique_name), model_from_dict.interface().reflectity_profile([0.3], model_from_dict.unique_name), ) + + +class TestModelPropertyAccessors: + """Tests for the new @property accessors introduced in the ModelBase/EasyList migration.""" + + def test_scale_setter_updates_value(self): + model = Model() + model.scale = 3.0 + assert model.scale.value == 3.0 + + def test_scale_getter_returns_parameter(self): + model = Model(scale=2.5) + from easyscience.variable import Parameter + + assert isinstance(model.scale, Parameter) + assert model.scale.value == 2.5 + + def test_background_setter_updates_value(self): + model = Model() + model.background = 1e-6 + assert model.background.value == 1e-6 + + def test_background_getter_returns_parameter(self): + model = Model(background=5e-6) + from easyscience.variable import Parameter + + assert isinstance(model.background, Parameter) + assert model.background.value == 5e-6 + + def test_sample_setter(self): + model = Model() + new_sample = Sample(name='NewSample') + model.sample = new_sample + assert model.sample.name == 'NewSample' + + def test_to_dict_includes_sample_and_resolution(self): + model = Model() + d = model.to_dict() + assert 'sample' in d + assert 'resolution_function' in d + assert 'interface' in d # interface is None, encoded as None + assert 'name' in d + + def test_to_dict_with_interface_name(self): + interface = CalculatorFactory() + model = Model(interface=interface) + d = model.to_dict() + assert d['interface'] == 'refnx' + + def test_to_dict_excludes_derived_fields(self): + model = Model() + d = model.to_dict() + # sample, resolution_function, interface are handled separately + assert 'sample' in d + # The super().to_dict() skip prevents these from being top-level + assert 'resolution_function' in d + assert 'interface' in d + + def test_as_dict_alias(self): + model = Model() + assert model.as_dict() == model.to_dict() + + def test_is_default_property(self): + model = Model() + assert model.is_default is False + model.is_default = True + assert model.is_default is True + + +class TestModelRoundTrip: + """Tests verifying serialization round-trip for the Model class.""" + + def test_basic_round_trip_preserves_name(self): + global_object.map._clear() + model = Model(name='MyModel') + d = model.as_dict() + global_object.map._clear() + restored = Model.from_dict(d) + assert restored.name == 'MyModel' + + def test_round_trip_preserves_scale_and_background(self): + global_object.map._clear() + model = Model(scale=2.0, background=1e-7) + d = model.as_dict() + global_object.map._clear() + restored = Model.from_dict(d) + assert restored.scale.value == 2.0 + assert restored.background.value == 1e-7 + + def test_round_trip_preserves_resolution_function(self): + global_object.map._clear() + model = Model(resolution_function=PercentageFwhm(3.0)) + d = model.as_dict() + global_object.map._clear() + restored = Model.from_dict(d) + assert restored._resolution_function.smearing(100) == 3.0 + + def test_round_trip_preserves_interface(self): + global_object.map._clear() + interface = CalculatorFactory() + model = Model(interface=interface) + d = model.as_dict() + global_object.map._clear() + restored = Model.from_dict(d) + assert restored.interface().name == 'refnx' + + def test_round_trip_preserves_is_default(self): + global_object.map._clear() + model = Model() + model.is_default = True + d = model.as_dict() + global_object.map._clear() + restored = Model.from_dict(d) + # Note: is_default is a runtime flag that may not survive round-trip + # because from_dict reconstructs via __init__ which resets _is_default. + # This test documents the current behaviour. + assert restored.is_default is False diff --git a/tests/model/test_model_collection.py b/tests/model/test_model_collection.py index 4a6fd521..dda554b1 100644 --- a/tests/model/test_model_collection.py +++ b/tests/model/test_model_collection.py @@ -65,7 +65,7 @@ def test_add_model_color_cycle(self): collection.add_model() assert collection[1].color == COLORS[1] - collection.remove(0) + collection.remove_at(0) collection.add_model() assert collection[0].color == COLORS[1] @@ -101,7 +101,7 @@ def test_delete_model(self): # Then collection = ModelCollection(model_1, model_2) - collection.remove(0) + collection.remove_at(0) # Expect assert len(collection) == 1 @@ -163,3 +163,42 @@ def test_legacy_from_dict_sets_color_index(self): restored.add_model() assert [model.color for model in restored] == [COLORS[0], COLORS[1]] + + def test_next_color_index_property(self): + """next_color_index should be accessible as a property for serialization.""" + collection = ModelCollection(populate_if_none=False) + collection.add_model() + idx = collection.next_color_index + assert isinstance(idx, int) + assert idx >= 0 + + def test_next_color_index_none_when_no_colors(self): + """When COLORS is empty, next_color_index returns 0.""" + # We can test the None case when COLORS has entries, it wraps + collection = ModelCollection(populate_if_none=False) + # Without adding models, the index should still be accessible + assert collection.next_color_index is not None + + def test_from_dict_preserves_data_count(self): + """from_dict should reconstruct the exact number of models.""" + global_object.map._clear() + model_1 = Model(name='M1') + model_2 = Model(name='M2') + p = ModelCollection(model_1, model_2) + d = p.as_dict() + global_object.map._clear() + q = ModelCollection.from_dict(d) + assert len(q) == 2 + + def test_from_dict_with_extra_data_entries(self): + """from_dict should handle data entries correctly.""" + global_object.map._clear() + model_1 = Model(name='M1') + model_2 = Model(name='M2') + p = ModelCollection(model_1, model_2) + d = p.as_dict() + global_object.map._clear() + q = ModelCollection.from_dict(d) + assert len(q) == 2 + assert q[0].name == 'M1' + assert q[1].name == 'M2' diff --git a/tests/sample/assemblies/test_base_assembly.py b/tests/sample/assemblies/test_base_assembly.py index 482cf669..84228eca 100644 --- a/tests/sample/assemblies/test_base_assembly.py +++ b/tests/sample/assemblies/test_base_assembly.py @@ -182,3 +182,18 @@ def test_set_back_layer_with_front(self, base_assembly: BaseAssembly) -> None: # Expect assert base_assembly.layers == [self.mock_layer_0, self.mock_layer_1] + + def test_layers_setter(self) -> None: + """The layers property setter should replace the layer list.""" + global_object.map._clear() + BaseAssembly.__abstractmethods__ = set() + assembly = BaseAssembly( + name='test', + type='type', + interface=MagicMock(), + layers=[MagicMock(), MagicMock()], + ) + new_layers = [MagicMock(), MagicMock(), MagicMock()] + assembly.layers = new_layers + assert assembly.layers == new_layers + assert len(assembly.layers) == 3 diff --git a/tests/sample/assemblies/test_bilayer.py b/tests/sample/assemblies/test_bilayer.py index 03949ee7..431ad866 100644 --- a/tests/sample/assemblies/test_bilayer.py +++ b/tests/sample/assemblies/test_bilayer.py @@ -119,7 +119,7 @@ def test_tail_layers_linked(self): # Initial values should match assert p.front_tail_layer.thickness.value == p.back_tail_layer.thickness.value - assert p.front_tail_layer.area_per_molecule == p.back_tail_layer.area_per_molecule + assert p.front_tail_layer.area_per_molecule.value == p.back_tail_layer.area_per_molecule.value # Change front tail thickness - back tail should follow p.front_tail_layer.thickness.value = 20.0 @@ -128,8 +128,8 @@ def test_tail_layers_linked(self): # Change front tail area per molecule - back tail should follow p.front_tail_layer.area_per_molecule = 55.0 - assert p.front_tail_layer.area_per_molecule == 55.0 - assert p.back_tail_layer.area_per_molecule == 55.0 + assert p.front_tail_layer.area_per_molecule.value == 55.0 + assert p.back_tail_layer.area_per_molecule.value == 55.0 def test_constrain_heads_enabled(self): """Test head thickness/area constraint when enabled.""" @@ -142,8 +142,8 @@ def test_constrain_heads_enabled(self): # Change front head area per molecule - back head should follow p.front_head_layer.area_per_molecule = 60.0 - assert p.front_head_layer.area_per_molecule == 60.0 - assert p.back_head_layer.area_per_molecule == 60.0 + assert p.front_head_layer.area_per_molecule.value == 60.0 + assert p.back_head_layer.area_per_molecule.value == 60.0 def test_constrain_heads_disabled(self): """Test heads are independent when constraint disabled.""" @@ -190,8 +190,8 @@ def test_head_hydration_independent(self): p.back_head_layer.solvent_fraction = 0.5 # They should remain independent - assert p.front_head_layer.solvent_fraction == 0.3 - assert p.back_head_layer.solvent_fraction == 0.5 + assert p.front_head_layer.solvent_fraction.value == 0.3 + assert p.back_head_layer.solvent_fraction.value == 0.5 def test_conformal_roughness_enabled(self): """Test all roughnesses are linked when conformal roughness enabled.""" diff --git a/tests/sample/assemblies/test_surfactant_layer.py b/tests/sample/assemblies/test_surfactant_layer.py index 62b92689..653b19ba 100644 --- a/tests/sample/assemblies/test_surfactant_layer.py +++ b/tests/sample/assemblies/test_surfactant_layer.py @@ -41,31 +41,31 @@ def test_from_pars(self): assert p.tail_layer.molecular_formula == 'C8O10H12P' assert p.tail_layer.thickness.value == 12 assert p.tail_layer.solvent.as_dict() == h2o.as_dict() - assert p.tail_layer.solvent_fraction == 0.5 - assert p.tail_layer.area_per_molecule == 50 + assert p.tail_layer.solvent_fraction.value == 0.5 + assert p.tail_layer.area_per_molecule.value == 50 assert p.tail_layer.roughness.value == 2 assert p.layers[1].name == 'A Test Head Layer' assert p.head_layer.name == 'A Test Head Layer' assert p.head_layer.molecular_formula == 'C10H24' assert p.head_layer.thickness.value == 10 assert p.head_layer.solvent.as_dict() == noth2o.as_dict() - assert p.head_layer.solvent_fraction == 0.2 - assert p.head_layer.area_per_molecule == 40 + assert p.head_layer.solvent_fraction.value == 0.2 + assert p.head_layer.area_per_molecule.value == 40 assert p.name == 'A Test' def test_constraint_area_per_molecule(self): p = SurfactantLayer() p.tail_layer._area_per_molecule.value = 30 - assert p.tail_layer.area_per_molecule == 30.0 - assert p.head_layer.area_per_molecule == 48.2 + assert p.tail_layer.area_per_molecule.value == 30.0 + assert p.head_layer.area_per_molecule.value == 48.2 assert p.constrain_area_per_molecule is False p.constrain_area_per_molecule = True - assert p.tail_layer.area_per_molecule == 30 - assert p.head_layer.area_per_molecule == 30 + assert p.tail_layer.area_per_molecule.value == 30 + assert p.head_layer.area_per_molecule.value == 30 assert p.constrain_area_per_molecule is True p.tail_layer._area_per_molecule.value = 40 - assert p.tail_layer.area_per_molecule == 40 - assert p.head_layer.area_per_molecule == 40 + assert p.tail_layer.area_per_molecule.value == 40 + assert p.head_layer.area_per_molecule.value == 40 def test_conformal_roughness(self): p = SurfactantLayer() diff --git a/tests/sample/collections/test_base_collection.py b/tests/sample/collections/test_base_collection.py index 44b8e208..c3036bc9 100644 --- a/tests/sample/collections/test_base_collection.py +++ b/tests/sample/collections/test_base_collection.py @@ -3,6 +3,8 @@ from unittest.mock import MagicMock +import pytest + from easyreflectometry.sample.collections.base_collection import BaseCollection from easyreflectometry.sample.elements.layers.layer import Layer @@ -160,10 +162,185 @@ def test_remove(self): p.append(Layer(name='layer_4')) # Then - p.remove(1) + p.remove_at(1) # Then assert len(p) == 3 assert p[0].name == 'layer_1' assert p[1].name == 'layer_3' assert p[2].name == 'layer_4' + + # ---- new BaseCollection (EasyList-based) specific tests ---- + + def test_name_getter_and_setter(self): + """name property should be readable and writable.""" + p = BaseCollection('original', MagicMock()) + assert p.name == 'original' + p.name = 'changed' + assert p.name == 'changed' + + def test_data_property(self): + """data property should return a read-only copy of the internal list.""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + data = p.data + assert len(data) == 1 + assert data[0].name == 'layer' + # Mutating the returned copy must not affect the collection + data.append(Layer(name='extra')) + assert len(p) == 1 + + def test_interface_propagates_to_existing_items(self): + """Setting interface after construction should propagate to all items.""" + mock_iface = MagicMock() + elem = Layer(name='layer') + # Pass interface=None explicitly and items as positional args + p = BaseCollection('name', None, elem) + assert p.interface is None + p.interface = mock_iface + # The interface setter propagates to items then calls generate_bindings on the mock + assert elem.interface is mock_iface + mock_iface.generate_bindings.assert_called() + + def test_interface_propagates_to_inserted_items(self): + """Items inserted after interface is set should receive the interface.""" + mock_iface = MagicMock() + p = BaseCollection('name', mock_iface) + elem = Layer(name='new_layer') + p.append(elem) + assert elem.interface is mock_iface + + def test_get_all_variables(self): + """get_all_variables should collect parameters from all items.""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + variables = p.get_all_variables() + # A Layer has thickness, roughness, and the material's sld/isld + names = {v.name for v in variables if hasattr(v, 'name')} + assert 'thickness' in names + assert 'roughness' in names + + def test_get_all_parameters(self): + """get_all_parameters should filter to only Parameter instances.""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + params = p.get_all_parameters() + for param in params: + assert param.__class__.__name__ == 'Parameter' + + def test_get_free_parameters(self): + """get_free_parameters should return only independent, non-fixed parameters.""" + elem = Layer(name='layer') + # By default thickness/roughness are fixed + p = BaseCollection('name', MagicMock(), elem) + free = p.get_free_parameters() + # By default all params are fixed, so empty + assert len(free) == 0 + # Unfix one + elem.thickness.fixed = False + free = p.get_free_parameters() + assert len(free) == 1 + assert free[0].name == 'thickness' + + def test_get_fit_parameters_alias(self): + """get_fit_parameters should be an alias for get_free_parameters.""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + assert p.get_fit_parameters() == p.get_free_parameters() + + def test_get_parameters_shim(self): + """get_parameters should be a compatibility alias for get_all_parameters.""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + assert p.get_parameters() == p.get_all_parameters() + + def test_get_linkable_attributes(self): + """_get_linkable_attributes should return get_all_variables.""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + assert p._get_linkable_attributes() == p.get_all_variables() + + def test_to_dict_includes_data_and_name(self): + """to_dict should serialize data items and collection metadata.""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + d = p.to_dict() + assert d['name'] == 'name' + assert len(d['data']) == 1 + assert d['data'][0]['name'] == 'layer' + + def test_to_dict_skips_interface(self): + """to_dict should exclude the interface field.""" + mock_iface = MagicMock() + p = BaseCollection('name', mock_iface) + d = p.to_dict() + assert 'interface' not in d + + def test_to_dict_skips_unique_name_by_default(self): + """to_dict should drop unique_name (matching legacy behaviour).""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + d = p.to_dict() + assert 'unique_name' not in d + + def test_as_dict_is_alias_for_to_dict(self): + """as_dict should delegate to to_dict.""" + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + assert p.as_dict() == p.to_dict() + + def test_deepcopy_round_trips(self): + """__deepcopy__ should produce an equivalent collection via from_dict.""" + import copy + + elem = Layer(name='layer') + # Use a concrete subclass (LayerCollection) that properly supports deepcopy + from easyreflectometry.sample.collections.layer_collection import LayerCollection + + p = LayerCollection(elem, name='test_layers') + p_copy = copy.deepcopy(p) + assert len(p_copy) == len(p) + assert p_copy[0].name == p[0].name + + def test_repr_handles_exception_gracefully(self): + """__repr__ should not crash even with items lacking _dict_repr.""" + mock_item = MagicMock() + # Deliberately make _dict_repr raise + del mock_item._dict_repr + p = BaseCollection('name', interface=None) + # Manually insert the mock item bypassing normal insert + p._data.append(mock_item) + # Should not raise + result = repr(p) + assert isinstance(result, str) + + def test_insert_rejects_non_integer_index(self): + """insert should raise TypeError for non-integer indices.""" + p = BaseCollection('name', interface=None) + with pytest.raises(TypeError, match='Index must be an integer'): + p.insert('not_an_int', Layer(name='x')) + + def test_duplicate_insert_is_warned(self): + """Inserting an already-present item should warn and skip.""" + import warnings + + elem = Layer(name='layer') + p = BaseCollection('name', MagicMock(), elem) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + p.append(elem) + assert len(w) == 1 + assert 'already in collection' in str(w[0].message) + # Length unchanged + assert len(p) == 1 + + def test_get_key_uses_name(self): + """_get_key should use the item's name property.""" + elem = Layer(name='mylayer') + p = BaseCollection('name', MagicMock(), elem) + assert p._get_key(elem) == 'mylayer' + + def test_has_interface_setter(self): + """_has_interface_setter should correctly detect interface-writable types.""" + assert BaseCollection._has_interface_setter(Layer) is True + assert BaseCollection._has_interface_setter(int) is False diff --git a/tests/sample/elements/layers/test_layer_area_per_molecule.py b/tests/sample/elements/layers/test_layer_area_per_molecule.py index 4d0abf7d..0d466be7 100644 --- a/tests/sample/elements/layers/test_layer_area_per_molecule.py +++ b/tests/sample/elements/layers/test_layer_area_per_molecule.py @@ -18,7 +18,7 @@ class TestLayerAreaPerMolecule(unittest.TestCase): def test_default(self): p = LayerAreaPerMolecule() assert p.molecular_formula == 'C10H18NO8P' - assert p.area_per_molecule == 48.2 + assert p.area_per_molecule.value == 48.2 assert str(p._area_per_molecule.unit) == 'Å^2' assert p._area_per_molecule.fixed is True assert p.thickness.value == 10.0 @@ -33,7 +33,7 @@ def test_default(self): assert p.solvent.sld.value == 6.36 assert p.solvent.isld.value == 0 assert p.solvent.name == 'D2O' - assert p.solvent_fraction == 0.2 + assert p.solvent_fraction.value == 0.2 assert str(p.material._fraction.unit) == 'dimensionless' assert p.material._fraction.fixed is True @@ -49,12 +49,12 @@ def test_from_pars(self): name='PG/H2O', ) assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule == 50 + assert p.area_per_molecule.value == 50 assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 assert p.solvent.isld.value == 0 - assert p.solvent_fraction == 0.5 + assert p.solvent_fraction.value == 0.5 def test_from_pars_constraint(self): h2o = Material(-0.561, 0, 'H2O') @@ -68,15 +68,15 @@ def test_from_pars_constraint(self): name='PG/H2O', ) assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule == 50 + assert p.area_per_molecule.value == 50 assert_almost_equal(p.material.sld, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 assert p.solvent.isld.value == 0 - assert p.solvent_fraction == 0.5 + assert p.solvent_fraction.value == 0.5 p.area_per_molecule = 30 - assert p.area_per_molecule == 30 + assert p.area_per_molecule.value == 30 assert_almost_equal(p.material.sld, 0.7119138888888887) p.thickness.value = 10 assert p.thickness.value == 10 @@ -95,24 +95,24 @@ def test_solvent_change(self): name='PG/H2O', ) assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule == 50 + assert p.area_per_molecule.value == 50 print(p.material) assert_almost_equal(p.material.sld, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 assert p.solvent.isld.value == 0 - assert p.solvent_fraction == 0.5 + assert p.solvent_fraction.value == 0.5 d2o = Material(6.335, 0, 'D2O') p.solvent = d2o assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule == 50 + assert p.area_per_molecule.value == 50 assert_almost_equal(p.material.sld, 3.762948333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == 6.335 assert p.solvent.isld.value == 0 - assert p.solvent_fraction == 0.5 + assert p.solvent_fraction.value == 0.5 def test_molecular_formula_change(self): h2o = Material(-0.561, 0, 'H2O') @@ -126,24 +126,24 @@ def test_molecular_formula_change(self): name='PG/H2O', ) assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule == 50 + assert p.area_per_molecule.value == 50 assert_almost_equal(p.material.sld, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 assert p.solvent.isld.value == 0 - assert p.solvent_fraction == 0.5 + assert p.solvent_fraction.value == 0.5 assert p.material.name == 'C8O10H12P in H2O' p.molecular_formula = 'C8O10D12P' assert p.molecular_formula == 'C8O10D12P' - assert p.area_per_molecule == 50 + assert p.area_per_molecule.value == 50 assert_almost_equal(p.material.sld, 1.3558483333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 assert p.solvent.isld.value == 0 - assert p.solvent_fraction == 0.5 + assert p.solvent_fraction.value == 0.5 assert p.material.name == 'C8O10D12P in H2O' def test_dict_repr(self): @@ -185,3 +185,48 @@ def test_dict_round_trip(self): # Expect assert sorted(p.as_dict()) == sorted(q.as_dict()) + + def test_solvent_fraction_metadata_and_mutation_after_round_trip(self): + """Regression covering two bugs at once: + + - ``solvent_fraction`` is a constructor argument but its backing + storage is ``self.material._fraction`` (delegated through + ``MaterialSolvated``). Without an override, ``ModelBase.from_dict`` + would put the saved Parameter on an orphan ``_solvent_fraction`` + attribute and reset the live one to constructor defaults. + - ``__init__`` builds the molecule SLD constraint against the + *temporary* thickness / area_per_molecule Parameters; after + ``from_dict`` reattaches the saved ones, mutating them must still + propagate to ``material.material.sld``. + """ + p = LayerAreaPerMolecule( + molecular_formula='C10H18NO8P', + thickness=12.0, + solvent_fraction=0.3, + area_per_molecule=50.0, + roughness=2.0, + ) + p.solvent_fraction.fixed = False + p.solvent_fraction.min = 0.12 + + original_mol_sld = p.material.material.sld.value + p_dict = p.as_dict() + global_object.map._clear() + + q = LayerAreaPerMolecule.from_dict(p_dict) + + # solvent_fraction metadata preserved, no orphan field. + assert q.solvent_fraction.value == 0.3 + assert q.solvent_fraction.fixed is False + assert q.solvent_fraction.min == 0.12 + assert '_solvent_fraction' not in q.__dict__ + + # Molecule SLD constraint preserved. + assert_almost_equal(q.material.material.sld.value, original_mol_sld) + + # Mutate the independent parameters and verify the constraint chain + # propagates to the derived molecule SLD. + q.area_per_molecule = 25.0 # half APM doubles SLD + assert_almost_equal(q.material.material.sld.value, 2 * original_mol_sld) + q.thickness.value = 6.0 # half thickness doubles SLD again + assert_almost_equal(q.material.material.sld.value, 4 * original_mol_sld) diff --git a/tests/sample/elements/materials/test_material_density.py b/tests/sample/elements/materials/test_material_density.py index 99c6615a..0b424cad 100644 --- a/tests/sample/elements/materials/test_material_density.py +++ b/tests/sample/elements/materials/test_material_density.py @@ -64,3 +64,22 @@ def test_dict_round_trip(self): q = MaterialDensity.from_dict(p_dict) assert sorted(p.as_dict()) == sorted(q.as_dict()) + + def test_density_mutation_propagates_after_round_trip(self): + """Regression: after ``from_dict`` reattaches the saved ``_density`` + Parameter, mutating it must propagate to ``sld`` / ``isld`` (which + are constrained off it). The ``__init__``-time constraint references + the temporary constructor Parameter; ``from_dict`` rebuilds the + graph so subsequent mutations propagate correctly. + """ + p = MaterialDensity(chemical_structure='Si', density=2.33) + original_sld = p.sld.value + p_dict = p.as_dict() + global_object.map._clear() + + q = MaterialDensity.from_dict(p_dict) + assert_almost_equal(q.sld.value, original_sld) + + q.density = 4.66 + # SLD scales linearly with density (constraint: d * sl / mw, etc.) + assert_almost_equal(q.sld.value, 2 * original_sld) diff --git a/tests/sample/elements/materials/test_material_mixture.py b/tests/sample/elements/materials/test_material_mixture.py index 0e70a2b7..3c2a3f65 100644 --- a/tests/sample/elements/materials/test_material_mixture.py +++ b/tests/sample/elements/materials/test_material_mixture.py @@ -13,7 +13,7 @@ class TestMaterialMixture: def test_default(self) -> None: material_mixture = MaterialMixture() - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 4.186) assert_almost_equal(material_mixture.isld, 0) @@ -22,7 +22,7 @@ def test_default(self) -> None: def test_default_constraint(self) -> None: material_mixture = MaterialMixture() - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 4.186) assert_almost_equal(material_mixture.isld, 0) @@ -37,57 +37,57 @@ def test_fraction_constraint(self): p = Material() q = Material(6.908, -0.278, 'Boron') material_mixture = MaterialMixture(p, q, 0.2) - assert material_mixture.fraction == 0.2 + assert material_mixture.fraction.value == 0.2 assert_almost_equal(material_mixture.sld, 4.7304) assert_almost_equal(material_mixture.isld, -0.0556) material_mixture._fraction.value = 0.5 - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert_almost_equal(material_mixture.sld, 5.54700) assert_almost_equal(material_mixture.isld, -0.1390) def test_material_a_change(self) -> None: material_mixture = MaterialMixture() - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 4.186) assert_almost_equal(material_mixture.isld, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_a = q - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 5.54700) assert_almost_equal(material_mixture.isld, -0.1390) def test_material_b_change(self) -> None: material_mixture = MaterialMixture() - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 4.186) assert_almost_equal(material_mixture.isld, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_b = q - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 5.54700) assert_almost_equal(material_mixture.isld, -0.1390) def test_material_b_change_double(self) -> None: material_mixture = MaterialMixture() - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 4.186) assert_almost_equal(material_mixture.isld, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_b = q assert material_mixture.name == 'EasyMaterial/Boron' - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 5.54700) assert_almost_equal(material_mixture.isld, -0.1390) r = Material(0.00, 0.00, 'ACMW') material_mixture.material_b = r assert material_mixture.name == 'EasyMaterial/ACMW' - assert material_mixture.fraction == 0.5 + assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 2.0930) assert_almost_equal(material_mixture.isld, 0.0000) @@ -96,7 +96,7 @@ def test_from_pars(self): p = Material() q = Material(6.908, -0.278, 'Boron') material_mixture = MaterialMixture(p, q, 0.2) - assert material_mixture.fraction == 0.2 + assert material_mixture.fraction.value == 0.2 assert str(material_mixture._fraction.unit) == 'dimensionless' assert_almost_equal(material_mixture.sld, 4.7304) assert_almost_equal(material_mixture.isld, -0.0556) @@ -142,3 +142,39 @@ def test_update_name(self) -> None: # Expect assert material_mixture.name == 'name_a/name_b' + + def test_calculator_binding_uses_mixed_sld(self) -> None: + """Regression: the calculator wrapper must bind to the mixture's own + ``_sld``/``_isld`` (the weighted average), not to either child material's + sld/isld parameter. Without an explicit ``_get_linkable_attributes`` + override the inherited dir-walk picks up the first matching child + parameter and the wrapper silently gets the wrong SLD. + """ + from easyreflectometry.calculators import CalculatorFactory + + interface = CalculatorFactory() + material_a = Material(sld=2.0, isld=0.0) + material_b = Material(sld=6.0, isld=0.0) + mixture = MaterialMixture(material_a, material_b, fraction=0.25, interface=interface) + + # 2 * 0.75 + 6 * 0.25 = 1.5 + 1.5 = 3.0 + assert_almost_equal(mixture.sld, 3.0) + wrapper_material = interface()._wrapper.storage['material'][mixture.unique_name] + assert_almost_equal(wrapper_material.real.value, 3.0) + assert_almost_equal(wrapper_material.imag.value, 0.0) + + def test_mutation_propagates_after_round_trip(self) -> None: + """Regression: after ``from_dict`` swaps in the saved ``_fraction`` + Parameter, the dependency graph for ``_sld``/``_isld`` must point at + the live ``_fraction`` (not the temp Parameter created from the + float kwarg in ``__init__``).""" + p = MaterialMixture(Material(sld=2.0), Material(sld=6.0), fraction=0.25) + p_dict = p.as_dict() + global_object.map._clear() + + q = MaterialMixture.from_dict(p_dict) + assert_almost_equal(q.sld, 3.0) + + q.fraction = 0.8 + # 2 * 0.2 + 6 * 0.8 = 0.4 + 4.8 = 5.2 + assert_almost_equal(q.sld, 5.2) diff --git a/tests/sample/elements/materials/test_material_solvated.py b/tests/sample/elements/materials/test_material_solvated.py index 3d4a9917..3af8e379 100644 --- a/tests/sample/elements/materials/test_material_solvated.py +++ b/tests/sample/elements/materials/test_material_solvated.py @@ -32,7 +32,7 @@ def test_init(self, material_solvated: MaterialSolvated) -> None: # When Then Expect assert material_solvated.material_a == self.material assert material_solvated.material_b == self.solvent - assert material_solvated.fraction == 0.1 + assert material_solvated.fraction.value == 0.1 assert material_solvated.name == 'name' assert material_solvated.interface == self.mock_interface self.mock_interface.generate_bindings.call_count == 2 @@ -69,14 +69,14 @@ def test_set_solvent(self, material_solvated: MaterialSolvated) -> None: def test_solvent_fraction(self, material_solvated: MaterialSolvated) -> None: # When Then Expect - assert material_solvated.solvent_fraction == 0.1 + assert material_solvated.solvent_fraction.value == 0.1 def test_set_solvent_fraction(self, material_solvated: MaterialSolvated) -> None: # When Then material_solvated.solvent_fraction = 1.0 # Expect - assert material_solvated.solvent_fraction == 1.0 + assert material_solvated.solvent_fraction.value == 1.0 def test_set_solvent_fraction_exception(self, material_solvated: MaterialSolvated) -> None: # When Then Expect @@ -133,3 +133,29 @@ def test_update_name(self, material_solvated: MaterialSolvated) -> None: # Expect assert material_solvated.name == 'name_a in name_b' + + def test_solvent_fraction_metadata_survives_round_trip(self) -> None: + """Regression: ``solvent_fraction`` is a constructor argument, but its + backing storage is ``_fraction`` (inherited from MaterialMixture). + ``ModelBase.from_dict`` would write the saved Parameter to + ``_solvent_fraction`` (an orphan), silently resetting the active + parameter to constructor defaults. We re-route to ``_fraction`` in + ``MaterialSolvated.from_dict``. + """ + material = Material(sld=6.36, isld=0, name='D2O') + solvent = Material(sld=-0.561, isld=0, name='H2O') + p = MaterialSolvated(material=material, solvent=solvent, solvent_fraction=0.3) + # Tweak fit metadata that the default would not have. + p.solvent_fraction.fixed = False + p.solvent_fraction.min = 0.12 + + p_dict = p.as_dict() + global_object.map._clear() + + q = MaterialSolvated.from_dict(p_dict) + + assert q.solvent_fraction.value == 0.3 + assert q.solvent_fraction.fixed is False + assert q.solvent_fraction.min == 0.12 + # No orphan field. + assert '_solvent_fraction' not in q.__dict__ diff --git a/tests/sample/test_base_core.py b/tests/sample/test_base_core.py new file mode 100644 index 00000000..e9b32df7 --- /dev/null +++ b/tests/sample/test_base_core.py @@ -0,0 +1,284 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Tests for BaseCore class — the new ModelBase-based foundation for sample-tree objects.""" + +from __future__ import annotations + +from unittest.mock import MagicMock + +import pytest +from easyscience import global_object +from easyscience.variable import Parameter + +from easyreflectometry.sample.base_core import BaseCore + +# --------------------------------------------------------------------------- +# Minimal concrete subclass for testing the abstract BaseCore +# --------------------------------------------------------------------------- + + +class _ConcreteCore(BaseCore): + """A non-abstract BaseCore that exposes a simple ``_dict_repr``.""" + + def __init__(self, name='TestCore', interface=None, unique_name=None, **kwargs): + super().__init__(name=name, interface=interface, unique_name=unique_name, **kwargs) + + @property + def _dict_repr(self) -> dict[str, str]: + return {self.name: {'type': 'concrete'}} + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + + +class TestBaseCore: + """Direct unit tests for the BaseCore abstract base class.""" + + # ---- construction ---- + + def test_default_construction(self) -> None: + """A minimal concrete subclass should construct without errors.""" + obj = _ConcreteCore(name='Test') + assert obj.name == 'Test' + assert obj.interface is None + assert obj.user_data == {} + + def test_construction_with_interface(self) -> None: + """Passing an interface should trigger generate_bindings.""" + mock_iface = MagicMock() + obj = _ConcreteCore(name='WithIface', interface=mock_iface) + assert obj.interface is mock_iface + mock_iface.generate_bindings.assert_called_once_with(obj) + + def test_construction_with_unique_name(self) -> None: + """unique_name is passed through to ModelBase.""" + obj = _ConcreteCore(name='Uniq', unique_name='my_unique') + assert obj.unique_name == 'my_unique' + + def test_construction_kwargs_stored_as_attributes(self) -> None: + """Transitional kwargs path: extra kwargs become plain instance attrs.""" + child = Parameter('extra_param', 5.0) + obj = _ConcreteCore(name='Kwargs', extra=child, extra2=42) + assert obj.extra is child + assert obj.extra2 == 42 + + # ---- name property ---- + + def test_name_getter(self) -> None: + obj = _ConcreteCore(name='MyName') + assert obj.name == 'MyName' + + def test_name_setter(self) -> None: + obj = _ConcreteCore(name='Original') + obj.name = 'Changed' + assert obj.name == 'Changed' + + # ---- interface property ---- + + def test_interface_set_to_none(self) -> None: + obj = _ConcreteCore(name='NoIface') + obj.interface = None + assert obj.interface is None + + def test_interface_set_triggers_bindings(self) -> None: + obj = _ConcreteCore(name='Late') + mock_iface = MagicMock() + obj.interface = mock_iface + mock_iface.generate_bindings.assert_called_once_with(obj) + + def test_interface_setter_does_not_call_generate_bindings_for_none(self) -> None: + obj = _ConcreteCore(name='NoneIface') + # Setting to None should be safe (no generate_bindings call) + obj.interface = None + assert obj.interface is None + + # ---- generate_bindings ---- + + def test_generate_bindings_raises_when_interface_is_none(self) -> None: + obj = _ConcreteCore(name='NoIface') + with pytest.raises(AttributeError, match='Interface error'): + obj.generate_bindings() + + def test_generate_bindings_propagates_to_children(self) -> None: + """Children with an interface setter receive the parent's interface.""" + mock_iface = MagicMock() + child = _ConcreteCore(name='Child') + child._interface = None # reset so we can observe propagation + obj = _ConcreteCore(name='Parent', child=child) + obj.interface = mock_iface + # The child should have received the interface too. + assert child.interface is mock_iface + + def test_generate_bindings_propagates_to_parameter_children(self) -> None: + """Parameters stored as plain attrs should not break binding propagation.""" + mock_iface = MagicMock() + param = Parameter('p', 1.0) + obj = _ConcreteCore(name='WithParam', p=param) + obj.interface = mock_iface + mock_iface.generate_bindings.assert_called_once_with(obj) + + # ---- _iter_public_children ---- + + def test_iter_public_children_includes_class_attrs(self) -> None: + child = _ConcreteCore(name='Child') + obj = _ConcreteCore(name='Parent', child=child) + children = list(obj._iter_public_children()) + assert child in children + + def test_iter_public_children_includes_instance_attrs(self) -> None: + param = Parameter('p', 1.0) + obj = _ConcreteCore(name='Parent', p=param) + children = list(obj._iter_public_children()) + assert param in children + + def test_iter_public_children_excludes_private(self) -> None: + obj = _ConcreteCore(name='Parent') + obj._private_thing = 'secret' + children = list(obj._iter_public_children()) + names = [getattr(c, 'name', c) for c in children] + assert 'secret' not in names + + def test_iter_public_children_excludes_interface_and_name(self) -> None: + obj = _ConcreteCore(name='Parent') + children = list(obj._iter_public_children()) + assert obj.interface not in children + + def test_iter_public_children_no_duplicates(self) -> None: + """If a child appears both as a class attr and instance attr, only one copy.""" + child = _ConcreteCore(name='Child') + obj = _ConcreteCore(name='Parent', child=child) + # Also set as attr with same id + obj.duplicate_ref = child + children = list(obj._iter_public_children()) + # child should appear only once + assert children.count(child) == 1 + + # ---- _has_interface_setter ---- + + def test_has_interface_setter_true(self) -> None: + assert BaseCore._has_interface_setter(_ConcreteCore) is True + + def test_has_interface_setter_false_for_bare_object(self) -> None: + assert BaseCore._has_interface_setter(object) is False + + def test_has_interface_setter_false_for_parameter(self) -> None: + """Parameter doesn't have an interface property.""" + assert BaseCore._has_interface_setter(Parameter) is False + + # ---- compatibility shims ---- + + def test_get_linkable_attributes(self) -> None: + param = Parameter('p', 1.0) + obj = _ConcreteCore(name='Core', p=param) + result = obj._get_linkable_attributes() + assert param in result + + def test_get_parameters_shim(self) -> None: + param = Parameter('p', 1.0) + obj = _ConcreteCore(name='Core', p=param) + result = obj.get_parameters() + assert param in result + + def test_add_component(self) -> None: + obj = _ConcreteCore(name='Core') + comp = Parameter('comp', 42.0) + obj._add_component('my_comp', comp) + assert obj.my_comp is comp + + # ---- get_all_variables ---- + + def test_get_all_variables_includes_descriptors(self) -> None: + param = Parameter('p', 1.0) + obj = _ConcreteCore(name='Core', p=param) + result = obj.get_all_variables() + assert param in result + + def test_get_all_variables_recurses_into_children(self) -> None: + inner_param = Parameter('inner', 2.0) + child = _ConcreteCore(name='Child', p=inner_param) + obj = _ConcreteCore(name='Parent', child=child) + result = obj.get_all_variables() + assert inner_param in result + + def test_get_all_variables_no_duplicates_across_children(self) -> None: + param = Parameter('shared', 1.0) + child_a = _ConcreteCore(name='A', p=param) + child_b = _ConcreteCore(name='B', p=param) + obj = _ConcreteCore(name='Parent', a=child_a, b=child_b) + result = obj.get_all_variables() + assert result.count(param) == 1 + + # ---- to_dict / as_dict ---- + + def test_to_dict_skips_interface(self) -> None: + mock_iface = MagicMock() + obj = _ConcreteCore(name='Core', interface=mock_iface) + d = obj.to_dict() + assert 'interface' not in d + + def test_to_dict_skips_unique_name_by_default(self) -> None: + obj = _ConcreteCore(name='Core', unique_name='my_unique') + d = obj.to_dict() + assert 'unique_name' not in d + + def test_to_dict_includes_name(self) -> None: + obj = _ConcreteCore(name='MyName') + d = obj.to_dict() + assert d.get('name') == 'MyName' + + def test_as_dict_is_alias_for_to_dict(self) -> None: + obj = _ConcreteCore(name='Core') + assert obj.as_dict() == obj.to_dict() + + def test_to_dict_respects_custom_skip(self) -> None: + obj = _ConcreteCore(name='Core') + d = obj.to_dict(skip=['name']) + assert 'name' not in d + + def test_to_dict_skip_not_mutated_by_callee(self) -> None: + """Caller's skip list must not be mutated.""" + obj = _ConcreteCore(name='Core') + skip = ['name'] + obj.to_dict(skip=skip) + assert skip == ['name'] # not appended-to + + # ---- repr ---- + + def test_repr_returns_yaml_string(self) -> None: + obj = _ConcreteCore(name='Test') + r = repr(obj) + assert 'Test' in r + assert 'concrete' in r + + # ---- user_data ---- + + def test_user_data_is_dict(self) -> None: + obj = _ConcreteCore(name='Core') + obj.user_data['key'] = 'value' + assert obj.user_data['key'] == 'value' + + # ---- round-trip ---- + + def test_basic_round_trip_via_material(self) -> None: + """Round-trip through a real subclass (Material) to verify BaseCore serialization.""" + from easyreflectometry.sample.elements.materials.material import Material + + global_object.map._clear() + obj = Material(sld=2.0, isld=0.5, name='TestMat') + d = obj.to_dict() + global_object.map._clear() + + restored = Material.from_dict(d) + assert restored.name == 'TestMat' + assert restored.sld.value == 2.0 + assert restored.isld.value == 0.5 + + def test_round_trip_skips_interface(self) -> None: + """Round-trip via to_dict → from_dict should strip the interface.""" + global_object.map._clear() + obj = _ConcreteCore(name='WithIface') + d = obj.to_dict() + assert 'interface' not in d diff --git a/tests/test_project.py b/tests/test_project.py index 73aeca85..ec85b3f2 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -377,11 +377,13 @@ def test_as_dict(self): keys.sort() assert keys == [ 'calculator', + 'file_format', 'fitter_minimizer', 'info', 'models', 'with_experiments', ] + assert project_dict['file_format'] == Project.FILE_FORMAT assert project_dict['info'] == { 'name': 'DefaultEasyReflectometryProject', 'short_description': 'Reflectometry, 1D', @@ -417,6 +419,44 @@ def remove_interface(d): remove_interface(project_dict['models']) assert project_dict['models'] == models_dict + def test_from_dict_missing_file_format_raises(self): + """Loading a dict without file_format should raise ValueError.""" + project = Project() + bad_dict = {'info': {}, 'with_experiments': False, 'models': {'data': []}} + with pytest.raises(ValueError, match='predates file_format=2'): + project.from_dict(bad_dict) + + def test_from_dict_wrong_file_format_raises(self): + """Loading a dict with an unsupported file_format should raise ValueError.""" + project = Project() + bad_dict = { + 'file_format': 99, + 'info': {}, + 'with_experiments': False, + 'models': {'data': []}, + } + with pytest.raises(ValueError, match='Unsupported project file_format'): + project.from_dict(bad_dict) + + def test_from_dict_correct_file_format_succeeds(self): + """Loading a dict with the correct file_format should work.""" + global_object.map._clear() + # Build a valid project dict with at least one model + src_project = Project() + src_project._info['name'] = 'Test' + src_project._info['short_description'] = 'Desc' + src_project._info['modified'] = '01.01.2025 00:00' + src_project.default_model() # ensures at least one model exists + src_project._with_experiments = False + good_dict = src_project.as_dict() + global_object.map._clear() + + project = Project() + project.from_dict(good_dict) + assert project._info['name'] == 'Test' + assert project._with_experiments is False + assert len(project._models) >= 1 + def test_as_dict_materials_not_in_model(self): # When project = Project() From 7324189f3be62c278e8af7014f5f858d01586447 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 5 Jun 2026 16:05:25 +0200 Subject: [PATCH 14/38] Bayesian analysis (#352) * initial commit with examples * ruff format * refactor core modifications to live in reflectometry lib only * modified the notebook a bit * refactored MCMC sampling to easyscience module * added pass-through for cancellation and total number of calculations * silly typo * yet another linting * moved the notebook to the correct location and fixed the formatting * added more initial arguments to sample. Added unit tests * prettify charts. Updated notebook * fixed issue with the notebook * move file fetch to reflectometry/data * PR review fixes * be careful with small chains * ruff * try to use explicit dtype for Ubuntu * another attempt at fixing failing test * better corner and distribution plots * updates to the Bayesian API after changes to core * use bayesian_extend * minor fixes * fix failing test * test against develop, so we can merge the PR * use mcmc_sample * fixed tests * minor notebook fix --- BAYESIAN_IN_ERL.md | 361 + .../advancedfitting/bayesian_bumps.ipynb | 734 + .../tutorials/advancedfitting/example.ort | 448 + .../advancedfitting/multi_contrast.ipynb | 11 +- .../tutorials/fitting/material_solvated.ipynb | 11 +- docs/docs/tutorials/fitting/monolayer.ipynb | 12 +- docs/docs/tutorials/fitting/repeating.ipynb | 11 +- .../tutorials/fitting/simple_fitting.ipynb | 11 +- .../simulation/resolution_functions.ipynb | 24 +- pixi.lock | 21525 ++++++++-------- pyproject.toml | 5 +- src/easyreflectometry/__init__.py | 2 + src/easyreflectometry/analysis/__init__.py | 23 + src/easyreflectometry/analysis/bayesian.py | 1376 + src/easyreflectometry/fitting.py | 83 + tests/_static/amor_reduced_iofq.ort | 236 + tests/test_bayesian.py | 476 + tests/test_fitting.py | 263 + tests/test_ort_file.py | 29 +- 19 files changed, 14916 insertions(+), 10725 deletions(-) create mode 100644 BAYESIAN_IN_ERL.md create mode 100644 docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb create mode 100644 docs/docs/tutorials/advancedfitting/example.ort create mode 100644 src/easyreflectometry/analysis/__init__.py create mode 100644 src/easyreflectometry/analysis/bayesian.py create mode 100644 tests/_static/amor_reduced_iofq.ort create mode 100644 tests/test_bayesian.py diff --git a/BAYESIAN_IN_ERL.md b/BAYESIAN_IN_ERL.md new file mode 100644 index 00000000..dc99953f --- /dev/null +++ b/BAYESIAN_IN_ERL.md @@ -0,0 +1,361 @@ +# Bayesian Analysis in EasyReflectometryLib — Implementation Plan + +## Current State + +- `BAYESIAN_BUMPS.md` (in `MD/`) describes a 3-phase plan. Phase 1 + (docs/notebook) is complete. +- `bayesian_bumps.py` (attached notebook) works but users must directly + import from `bumps.fitters`, `bumps.names`, `bumps.parameter` — a + leaky abstraction. +- The `Bumps` minimizer in `easyscience` + (`core/src/easyscience/fitting/minimizers/minimizer_bumps.py`) only + exposes **classical optimization** methods (`amoeba`, `newton`, `lm`), + NOT the DREAM/MCMC sampler. +- The `AvailableMinimizers` enum only has `Bumps`, `Bumps_simplex`, + `Bumps_newton`, `Bumps_lm`. This should remain optimizer-focused; + DREAM should be exposed as a sampling workflow, not as another + minimizer choice. +- `MultiFitter` in reflectometry-lib currently only has `fit()` and + `fit_single_data_set_1d()`. + +## Goal + +Users should be able to run Bayesian MCMC sampling with a **clean +high-level API** like: + +```python +fitter = MultiFitter(model) +fitter.switch_minimizer(AvailableMinimizers.Bumps) + +# Classical fit first +analysed = fitter.fit(data) + +# Bayesian sampling +posterior = fitter.sample(data, samples=5000, burn=1000, thin=10) + +# Analyze +from easyreflectometry.analysis.bayesian import plot_corner, posterior_summary +plot_corner(posterior) +print(posterior_summary(posterior)) +``` + +Important API boundary: `fit()` remains classical optimization only. +Bayesian DREAM sampling is exposed through `sample()` so users do not +receive sampler-shaped results from an optimizer-shaped API. + +## Implementation Plan + +### Step 1 — Keep DREAM separate from `AvailableMinimizers` + +**File**: `core/src/easyscience/fitting/available_minimizers.py` + +Do **not** add `Bumps_dream` as a normal `AvailableMinimizers` member. +The enum is currently used to instantiate minimizer backends and to +route calls through `Fitter.fit()`, which expects optimizer-style +`FitResults`. DREAM is an MCMC sampler and returns a sampler state/chain +rather than a best-fit result. + +Use `AvailableMinimizers.Bumps` to select the BUMPS backend, then expose +DREAM through a dedicated `sample()` method. This avoids making +`project.minimizer = AvailableMinimizers.Bumps_dream` look like a valid +classical fitting mode. + +### Step 2 — Add dedicated DREAM sampling support to the Bumps minimizer (core repo) + +**File**: `core/src/easyscience/fitting/minimizers/minimizer_bumps.py` + +**2a.** Keep `supported_methods()` optimizer-only (`amoeba`, `newton`, +`lm`). Do not add `'dream'` there unless the EasyScience fitting +abstraction is later split into optimizer and sampler concepts. + +**2b.** Add a new method `sample()` to the `Bumps` class: + +```python +def sample( + self, + x: np.ndarray, + y: np.ndarray, + weights: np.ndarray, + samples: int = 10000, + burn: int = 2000, + thin: int = 10, + chains: int | None = None, + population: int | None = None, + model: Callable | None = None, + parameters: list | None = None, + progress_callback: Callable | None = None, + seed: int | None = None, + **kwargs, +) -> dict: + """Run Bayesian MCMC sampling using BUMPS DREAM sampler. + + Returns a dict with: + - 'draws': np.ndarray, shape (n_samples, n_params) — posterior samples + - 'param_names': list[str] — parameter names + - 'state': DreamState — raw BUMPS state for save/restore + - 'logp': np.ndarray — log-posterior values + """ +``` + +The implementation would: + +1. Build the `Curve` model + `FitProblem` (reuse `_make_model()` logic) +2. Translate user-friendly aliases to BUMPS DREAM settings and call + `bumps_fit(problem, method='dream', samples=samples, burn=burn, thin=thin, pop=population, ...)` +3. Extract `result.state.draw().points` and return structured dict +4. Preserve and restore the EasyScience global object stack state, + mirroring the existing `fit()` implementation +5. Handle multi-dataset via the same `MultiFitter._precompute_reshaping` + pattern + +**2c.** Do **not** modify `fit()` to handle `method='dream'`, and do +**not** delegate `fit(method='dream')` to `sample()`. `fit()` returns +`FitResults`; `sample()` returns posterior samples and sampler metadata. +Keeping the methods separate prevents incompatible return types from +leaking into `Fitter.fit()` and reflectometry-lib `MultiFitter.fit()`. + +Recommended alias mapping: + +| Public argument | BUMPS DREAM setting | Rationale | +| ---------------- | --------------------------- | ----------------------------------------------------------------------- | +| `samples` | `samples` | Clear user-facing chain length; prefer over optimizer-oriented `steps`. | +| `burn` | `burn` | Matches BUMPS and common MCMC terminology. | +| `thin` | `thin` | Matches BUMPS and common MCMC terminology. | +| `chains` | `pop` | User-friendly MCMC wording; maps to BUMPS population count. | +| `population` | `pop` | BUMPS-aware alias for advanced users. | +| `initialization` | `init` | More readable than `init`, but pass through to BUMPS. | +| `seed` | RNG seeding before sampling | Expose reproducibility without requiring users to know BUMPS internals. | + +If both `chains` and `population` are provided, raise `ValueError` +unless they match. Accept `steps` only as a deprecated alias for +`samples`, with a warning, because `steps` already means optimizer +budget in EasyScience `Bumps.fit()`. + +### Step 3 — Add `sample()` to reflectometry-lib `MultiFitter` + +**File**: `reflectometry-lib/src/easyreflectometry/fitting.py` + +Add a `sample()` method to `MultiFitter`: + +```python +def sample( + self, + data: sc.DataGroup, + samples: int = 10000, + burn: int = 2000, + thin: int = 10, + chains: int | None = None, + population: int | None = None, + seed: int | None = None, + objective: str | None = None, +) -> dict: + """Run Bayesian MCMC sampling on reflectometry data. + + :param data: DataGroup with reflectivity data. + :param samples: Number of retained DREAM samples requested from BUMPS. + :param burn: Burn-in steps. + :param thin: Thinning interval. + :param chains: User-friendly alias for BUMPS DREAM population count. + :param population: BUMPS DREAM population count (`pop`) for advanced users. + :param seed: Random seed for reproducibility. + :param objective: Zero-variance handling strategy. + :return: Dict with posterior samples, parameter names, and sampler state. + """ +``` + +Internally: + +1. Reuse `_prepare_fit_arrays` for data preparation +2. Mirror the EasyScience `Fitter.fit()` lifecycle for reshaping, fit + function wrapping, and restoration +3. Delegate to `self.easy_science_multi_fitter.minimizer.sample(...)` + for the MCMC +4. Handle multi-model / multi-contrast aggregation as one joint + posterior +5. Return structured posterior dict or `PosteriorResults` + +The `MultiFitter` currently stores `self.easy_science_multi_fitter` +which has `.minimizer` — we'll call `sample()` on it when the minimizer +is a `Bumps` instance. + +### Step 4 — Create Bayesian analysis module in reflectometry-lib + +**New file**: +`reflectometry-lib/src/easyreflectometry/analysis/__init__.py` **New +file**: `reflectometry-lib/src/easyreflectometry/analysis/bayesian.py` + +The `bayesian.py` module provides: + +```python +class PosteriorResults: + """Container for Bayesian posterior samples with analysis methods.""" + + draws: np.ndarray # (n_samples, n_params) + param_names: list[str] + logp: np.ndarray | None + sampler_state: Any | None + + def summary(self) -> str: + """Return formatted summary table with mean, sd, HDI for each parameter.""" + + def corner(self, **kwargs) -> None: + """Plot parameter correlation corner plot using the `corner` library.""" + + def credible_interval(self, alpha: float = 0.95) -> dict: + """Return {param_name: (lower, upper)} credible intervals.""" + + def gelman_rubin(self) -> dict: + """Compute R-hat convergence diagnostic.""" + +def posterior_summary(draws, param_names) -> str: ... +def plot_corner(draws, param_names, **kwargs) -> None: ... +def plot_trace(draws, param_names, **kwargs) -> None: ... +def credible_intervals(draws, param_names, alpha=0.95) -> dict: ... +``` + +**Posterior predictive functions** (reflectivity & SLD): + +```python +def posterior_predictive_reflectivity( + draws, param_names, model, q_values, n_samples=200 +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Return (median, lower_95, upper_95) reflectivity arrays.""" + +def posterior_sld_profile( + draws, param_names, model, n_samples=200 +) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """Return (z, median, lower_95, upper_95) SLD profile arrays.""" +``` + +Posterior predictive helpers must save original parameter values and +errors before applying any posterior draw, and restore them in a +`finally` block after prediction. Parameter lookup should use +EasyScience parameter `unique_name` values, matching the BUMPS names +after removing the minimizer prefix, rather than display names. This +avoids leaving the model mutated after plotting and avoids collisions +when repeated models or multi-contrast fits contain similarly named +parameters. + +### Step 5 — Dependencies + +Add to `pyproject.toml` in reflectometry-lib: + +```toml +[project.optional-dependencies] +bayesian = ["corner>=2.2", "arviz>=0.18"] +``` + +Make `corner` and `arviz` optional imports — the analysis module should +work with graceful fallbacks when they're not installed. + +### Step 6 — Update exports + +**File**: `reflectometry-lib/src/easyreflectometry/__init__.py` + +Add `PosteriorResults` and analysis functions to the public API if +desired. + +### Step 7 — Update the example notebook + +**File**: +`reflectometry-lib/docs/src/tutorials/advancedfitting/bayesian_bumps.py` + +Replace low-level BUMPS calls with the new high-level API: + +- `fitter.sample(data, samples=500, burn=100, thin=10)` instead of + manual `FitProblem` + `bumps_fit` +- `plot_corner(posterior['draws'], posterior['param_names'])` instead of + manual `corner.corner()` +- `posterior_summary(...)` instead of manual numpy statistics + +### Step 8 — Tests + +**File**: `reflectometry-lib/tests/test_bayesian.py` (new) + +```python +def test_sample_basic(): ... +def test_posterior_summary_format(): ... +def test_corner_plot_does_not_crash(): ... +def test_credible_intervals(): ... +def test_sample_seed_reproducibility(): ... +``` + +Also add a test in `core/tests/` for the `Bumps.sample()` method. + +## Architecture Diagram + +``` +User Code + │ + ├─ fitter.fit(data) ──► classical chi² minimization + ├─ fitter.sample(data, ...) ──► Bayesian DREAM MCMC + │ + ▼ +reflectometry-lib MultiFitter + │ ._prepare_fit_arrays() ← reused from fit() + │ delegates to ↓ + ▼ +easyscience Bumps minimizer + │ .fit(x, y, weights) ──► amoeba/newton/lm + │ .sample(x, y, weights, ...) ──► DREAM MCMC (NEW) + │ + ▼ +bumps.fitters.fit / FitProblem / Curve + └──► reflectivity model evaluation via fit_func + +Post-hoc analysis: + reflectometry-lib analysis.bayesian + ├── PosteriorResults (container) + ├── plot_corner() → corner.corner() + ├── posterior_summary() → numpy stats + └── posterior_predictive_reflectivity() → model.interface.fit_func() +``` + +## Risk Assessment + +| Risk | Mitigation | +| -------------------------------------------- | -------------------------------------------------------------------------------- | +| DREAM may not converge with default settings | Expose `samples`, `burn`, `thin`, `chains`/`population`; document best practices | +| MCMC is 10-100× slower than least-squares | Document that classical fit first is recommended; add progress callback | +| `corner` / `arviz` may not be installed | Make optional dependencies with graceful fallbacks | +| Multi-dataset sampling aggregation | Follow existing `MultiFitter._precompute_reshaping` pattern | +| BUMPS DREAM API changes | Pin bumps version; wrap in our API | +| Reproducibility | Expose `seed` parameter; document how to save/load DreamState | +| Model mutation during posterior prediction | Save/restore original parameter values and map draws by `unique_name` | + +## Files to Create / Modify + +### Create: + +1. `reflectometry-lib/src/easyreflectometry/analysis/__init__.py` +2. `reflectometry-lib/src/easyreflectometry/analysis/bayesian.py` +3. `reflectometry-lib/tests/test_bayesian.py` + +### Modify: + +4. `core/src/easyscience/fitting/available_minimizers.py` — no + `Bumps_dream`; optionally document that samplers are exposed + separately +5. `core/src/easyscience/fitting/minimizers/minimizer_bumps.py` — add + dedicated `sample()` method without adding `'dream'` to optimizer + methods +6. `reflectometry-lib/src/easyreflectometry/fitting.py` — add `sample()` + to `MultiFitter` +7. `reflectometry-lib/src/easyreflectometry/__init__.py` — optional: + export new classes +8. `reflectometry-lib/pyproject.toml` — add optional `bayesian` + dependencies +9. `reflectometry-lib/docs/src/tutorials/advancedfitting/bayesian_bumps.py` + — update to use new API + +## Implementation Order + +1. **core changes** (Steps 1-2): Add `Bumps.sample()` method while + keeping DREAM out of optimizer enum/method dispatch +2. **reflectometry-lib fitting** (Step 3): Add `MultiFitter.sample()` +3. **reflectometry-lib analysis** (Step 4): Create + `analysis/bayesian.py` with corner plot & stats +4. **Dependencies** (Step 5): Add optional deps to pyproject.toml +5. **Exports** (Step 6): Update `__init__.py` +6. **Example update** (Step 7): Update notebook to use new API +7. **Tests** (Step 8): Add test coverage diff --git a/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb b/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb new file mode 100644 index 00000000..1c7e1bf6 --- /dev/null +++ b/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb @@ -0,0 +1,734 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "649cf9aa", + "metadata": {}, + "source": [ + "# Bayesian Fitting with BUMPS\n", + "\n", + "This notebook demonstrates the full high-level Bayesian MCMC API in\n", + "EasyReflectometry.\n", + "\n", + "It covers:\n", + "\n", + "- Building a reflectometry model with realistic bounds (critical for MCMC).\n", + "- Classical optimisation first (good starting point for Bayesian sampling).\n", + "- High-level DREAM MCMC sampling via\n", + " ``MultiFitter.mcmc_sample()`` and ``PosteriorResults``.\n", + "- Posterior inspection: summary table, marginal distributions, corner plot,\n", + " trace plot, credible intervals, Gelman-Rubin R-hat.\n", + "- Posterior-predictive checks: reflectivity and SLD profile with 95 %\n", + " credible bands.\n", + "\n", + "All posterior plots are rendered as **interactive Plotly figures**, the\n", + "same ones shown by the EasyReflectometryApp Bayesian Posterior tab.\n", + "\n", + "**Note**: Requires ``arviz`` and ``plotly`` (install via\n", + "``pip install easyreflectometry[dev]``)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "61aa83ac", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:29:06.282846Z", + "iopub.status.busy": "2026-05-29T06:29:06.282846Z", + "iopub.status.idle": "2026-05-29T06:29:09.516170Z", + "shell.execute_reply": "2026-05-29T06:29:09.516170Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "All libraries imported successfully.\n" + ] + } + ], + "source": [ + "import warnings\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pooch\n", + "from easyscience.fitting import AvailableMinimizers\n", + "\n", + "from easyreflectometry.analysis.bayesian import PosteriorResults\n", + "from easyreflectometry.analysis.bayesian import plot_corner\n", + "from easyreflectometry.analysis.bayesian import plot_trace\n", + "from easyreflectometry.analysis.bayesian import posterior_predictive_reflectivity\n", + "from easyreflectometry.analysis.bayesian import posterior_predictive_sld_profile\n", + "from easyreflectometry.calculators import CalculatorFactory\n", + "from easyreflectometry.data.measurement import load\n", + "from easyreflectometry.fitting import MultiFitter\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import PercentageFwhm\n", + "from easyreflectometry.sample import Layer\n", + "from easyreflectometry.sample import Material\n", + "from easyreflectometry.sample import Multilayer\n", + "from easyreflectometry.sample import Sample\n", + "\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "print('All libraries imported successfully.')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "14986b98", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:29:09.517778Z", + "iopub.status.busy": "2026-05-29T06:29:09.517778Z", + "iopub.status.idle": "2026-05-29T06:29:09.958508Z", + "shell.execute_reply": "2026-05-29T06:29:09.958508Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data loaded with keys: ['data', 'coords', 'attrs']\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# ---- Load experimental data -------------------------------------------------\n", + "# Fetch the .ort test data from the easyscience/reflectometry data repository.\n", + "data_path = pooch.retrieve(\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n", + " known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n", + ")\n", + "data = load(data_path)\n", + "print('Data loaded with keys:', list(data.keys()))\n", + "\n", + "# Quick look at the data\n", + "qz = data['coords']['Qz_0'].values\n", + "r_data = data['data']['R_0'].values\n", + "\n", + "plt.figure(figsize=(7, 4))\n", + "plt.semilogy(qz, r_data, 'o', label='Data')\n", + "plt.xlabel('Q / Å⁻¹')\n", + "plt.ylabel('Reflectivity')\n", + "plt.title('Experimental data (example.ort)')\n", + "plt.legend()\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cd56a6eb", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:29:09.960548Z", + "iopub.status.busy": "2026-05-29T06:29:09.960548Z", + "iopub.status.idle": "2026-05-29T06:29:09.973595Z", + "shell.execute_reply": "2026-05-29T06:29:09.973595Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model created with the following free parameters:\n", + " background: value=1e-06, bounds=(1e-07, 1e-05)\n", + " sld: value=2.0, bounds=(0.5, 4.0)\n", + " thickness: value=250.0, bounds=(100, 400)\n", + " scale: value=1.0, bounds=(0.8, 1.2)\n" + ] + } + ], + "source": [ + "# ---- Create a monolayer model (Si / Film / D₂O) ----------------------------\n", + "si = Material(sld=2.07, isld=0.0, name='Si')\n", + "film = Material(sld=2.0, isld=0.0, name='Film')\n", + "d2o = Material(sld=6.36, isld=0.0, name='D2O')\n", + "\n", + "si_layer = Layer(material=si, thickness=0.0, roughness=3.0, name='Si')\n", + "film_layer = Layer(material=film, thickness=250.0, roughness=3.0, name='Film')\n", + "d2o_layer = Layer(material=d2o, thickness=0.0, roughness=3.0, name='D2O')\n", + "\n", + "sample = Sample(\n", + " Multilayer(si_layer),\n", + " Multilayer(film_layer),\n", + " Multilayer(d2o_layer),\n", + " name='Monolayer Sample',\n", + ")\n", + "\n", + "resolution = PercentageFwhm(0.02)\n", + "model = Model(\n", + " sample=sample,\n", + " scale=1.0,\n", + " background=1e-6,\n", + " resolution_function=resolution,\n", + " name='Monolayer Model',\n", + ")\n", + "\n", + "# ---- Make key parameters free with realistic bounds (essential for MCMC) -----\n", + "film_layer.thickness.fixed = False\n", + "film_layer.thickness.bounds = (100, 400)\n", + "\n", + "film.sld.fixed = False\n", + "film.sld.bounds = (0.5, 4.0)\n", + "\n", + "model.scale.fixed = False\n", + "model.scale.bounds = (0.8, 1.2)\n", + "\n", + "model.background.fixed = False\n", + "model.background.bounds = (1e-7, 1e-5)\n", + "\n", + "print('Model created with the following free parameters:')\n", + "for p in model.get_parameters():\n", + " if not p.fixed:\n", + " print(f' {p.name}: value={p.value}, bounds={p.bounds}')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "991e1169", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:29:09.974614Z", + "iopub.status.busy": "2026-05-29T06:29:09.974614Z", + "iopub.status.idle": "2026-05-29T06:29:09.982333Z", + "shell.execute_reply": "2026-05-29T06:29:09.982333Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fitter ready with minimizer: Bumps\n" + ] + } + ], + "source": [ + "# ---- Set up the calculator and fitter ---------------------------------------\n", + "interface = CalculatorFactory()\n", + "interface.switch('refnx') # or 'refl1d'\n", + "model.interface = interface\n", + "\n", + "fitter = MultiFitter(model)\n", + "# Use the BUMPS backend — DREAM sampling is only available through BUMPS.\n", + "fitter.switch_minimizer(AvailableMinimizers.Bumps)\n", + "\n", + "print('Fitter ready with minimizer:', fitter.easy_science_multi_fitter.minimizer.name)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "eb0f989e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:29:09.983788Z", + "iopub.status.busy": "2026-05-29T06:29:09.983788Z", + "iopub.status.idle": "2026-05-29T06:29:11.760084Z", + "shell.execute_reply": "2026-05-29T06:29:11.760084Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Classical fit successful: True\n", + "Reduced χ² ≈ 73.7860402885366\n" + ] + }, + { + "data": { + "image/png": "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b5OaY0/QrZ636WfIc0cYBr5EREQ6EoyaY+/niDMZO962jcLLaATiEt0b3IuSBIVkxWWTA+oiHJcaIiIioi7zlD+MyE9VSxzLam9yKpneSF/mlvSHGV8iIiKKirZtRO1h4EtERERR0baNqD0sdSAiIiIiXWDgS0RERES6wFIHIiIi0i2XS2tVm0zRK+oD3/3792POnDk+l1955RXMnj07rOMiIiKi8Np6rKJ5MQ67w73YhiztPPeyi1CQEO7RUShEfeA7dOhQ7NixQ52vqalB//79MWPGjHAPi4iIiMIc9Er/4UpbI7It0n84XvUf3nO6Ck+s24d5k3IwPTs73MOkINNVje9bb72FadOmISkpKdxDISIiojCWN0imV4Le/n0SkRRvQozRoE4LMxJRXdeIV/9Zpu5H0SXsge8nn3yCG264Afn5+TAYDHjjjTda3eeZZ55RmVqz2YyJEydi8+bNXfpZa9as8Sl7ICIiIv2QQHZfaTVe23YSu09XIcsSr2IPb3I50xKHoxX1OFBWE7axUpSWOtTW1mLMmDG49957cfPNN7e6ffXq1ViwYAGef/55FfQuW7YMM2fOVLW62U1fQYwdOxYOh6PVYz/44AMVUIvq6mps3LgRq1at6oG9IiIiokit562y2XG2xo6a+kYUZCQhLSHW576y7LLdoanML0WXsAe+s2bNUlsgTz/9NO6//37cc8896rIEwGvXrsWf//xnPPLII+o6Tw1vW958801cc801KmvcloaGBrV5SMAsXC6X2rpKHqtpWreeg3oHzrV+cK71hfPde209dl7V7VbVNSLLEoeEODPO2+yornPg4BkrhmQnq44OHnV2J2JjDLDEx3C+e4mOzlPYA9+22O12bN26FT/72c+arzMajZg+fTo2bdrU6TKHBx54oN37LV68GI8++mir68+ePYv6+np0Z0KqqqrUH03ZB4penGv94FzrC+e7d3JpGl74+AgqaurQNzUeBrgQY9CQGBeD2gan6uZw7FwthsZIGweDmt8zVQ0YkGZCqsGGsrILyTCKXFartfcHvuXl5XA6ncjJyfG5Xi7v27evw88jf6ikLvi1115r974SZEtphXfGt6CgAFlZWUhJSenWH0ypG5Ln4R/M6Ma51g/Otb5wvnunfaVWnLI6kJuWCHP8hbCnsI8RB6WNmdMFm92JBqcRMTEGlNfYkZFs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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# ---- Classical fit first ----------------------------------------------------\n", + "# A classical optimisation gives a good starting point and a sanity check\n", + "# before launching the (much more expensive) MCMC sampler.\n", + "\n", + "analysed = fitter.fit(data)\n", + "\n", + "print('Classical fit successful:', analysed.get('success', 'N/A'))\n", + "print('Reduced χ² ≈', analysed.get('reduced_chi', 'N/A'))\n", + "\n", + "# Plot the fit\n", + "r_model = analysed['R_0_model'].values\n", + "\n", + "plt.figure(figsize=(8, 5))\n", + "plt.semilogy(qz, r_data, 'o', label='Data', alpha=0.7)\n", + "plt.semilogy(qz, r_model, '-', label='Classical BUMPS fit', linewidth=2)\n", + "plt.xlabel('Q / Å⁻¹')\n", + "plt.ylabel('Reflectivity')\n", + "plt.title('Classical fit before Bayesian sampling')\n", + "plt.legend()\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20c9b0e0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:29:11.762093Z", + "iopub.status.busy": "2026-05-29T06:29:11.762093Z", + "iopub.status.idle": "2026-05-29T06:30:41.903328Z", + "shell.execute_reply": "2026-05-29T06:30:41.903328Z" + } + }, + "outputs": [ + { + "ename": "TypeError", + "evalue": "Fitter.mcmc_sample() got an unexpected keyword argument 'chains'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mTypeError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 10\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# ---- Bayesian MCMC sampling -------------------------------------------------\u001b[39;00m\n\u001b[32m 2\u001b[39m \u001b[38;5;66;03m# ``MultiFitter.mcmc_sample()`` delegates to the BUMPS DREAM sampler.\u001b[39;00m\n\u001b[32m 3\u001b[39m \u001b[38;5;66;03m# All keyword arguments are forwarded with user-friendly names:\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 7\u001b[39m \u001b[38;5;66;03m# ``chains`` ← DREAM population count (alias for ``pop``)\u001b[39;00m\n\u001b[32m 8\u001b[39m \u001b[38;5;66;03m# ``population``← BUMPS‑native ``pop`` for advanced users\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m10\u001b[39m posterior_dict = \u001b[43mfitter\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmcmc_sample\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 11\u001b[39m \u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 12\u001b[39m \u001b[43m \u001b[49m\u001b[43msamples\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m2000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Short for demo; use 20 k+ in production\u001b[39;49;00m\n\u001b[32m 13\u001b[39m \u001b[43m \u001b[49m\u001b[43mburn\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m500\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 14\u001b[39m \u001b[43m \u001b[49m\u001b[43mthin\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m10\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 15\u001b[39m \u001b[43m)\u001b[49m\n\u001b[32m 17\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m'\u001b[39m\u001b[33mDREAM sampling complete.\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m 18\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m'\u001b[39m\u001b[33m Posterior shape : \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mposterior_dict[\u001b[33m\"\u001b[39m\u001b[33mdraws\u001b[39m\u001b[33m\"\u001b[39m].shape\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~\\projects\\easy\\ERA\\reflectometry-lib\\src\\easyreflectometry\\fitting.py:451\u001b[39m, in \u001b[36mMultiFitter.mcmc_sample\u001b[39m\u001b[34m(self, data, samples, burn, thin, chains, population, objective, initializer, resume_state, progress_callback, abort_test)\u001b[39m\n\u001b[32m 449\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m initializer \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 450\u001b[39m sampler_kwargs[\u001b[33m'\u001b[39m\u001b[33minit\u001b[39m\u001b[33m'\u001b[39m] = initializer\n\u001b[32m--> \u001b[39m\u001b[32m451\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43measy_science_multi_fitter\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmcmc_sample\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 452\u001b[39m \u001b[43m \u001b[49m\u001b[43mx\u001b[49m\u001b[43m=\u001b[49m\u001b[43mx\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 453\u001b[39m \u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m=\u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 454\u001b[39m \u001b[43m \u001b[49m\u001b[43mweights\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdy\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 455\u001b[39m \u001b[43m \u001b[49m\u001b[43msamples\u001b[49m\u001b[43m=\u001b[49m\u001b[43msamples\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 456\u001b[39m \u001b[43m \u001b[49m\u001b[43mburn\u001b[49m\u001b[43m=\u001b[49m\u001b[43mburn\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 457\u001b[39m \u001b[43m \u001b[49m\u001b[43mthin\u001b[49m\u001b[43m=\u001b[49m\u001b[43mthin\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 458\u001b[39m \u001b[43m \u001b[49m\u001b[43mchains\u001b[49m\u001b[43m=\u001b[49m\u001b[43mchains\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 459\u001b[39m \u001b[43m \u001b[49m\u001b[43mpopulation\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpopulation\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 460\u001b[39m \u001b[43m \u001b[49m\u001b[43mresume_state\u001b[49m\u001b[43m=\u001b[49m\u001b[43mresume_state\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 461\u001b[39m \u001b[43m \u001b[49m\u001b[43msampler_kwargs\u001b[49m\u001b[43m=\u001b[49m\u001b[43msampler_kwargs\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 462\u001b[39m \u001b[43m \u001b[49m\u001b[43mprogress_callback\u001b[49m\u001b[43m=\u001b[49m\u001b[43mprogress_callback\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 463\u001b[39m \u001b[43m \u001b[49m\u001b[43mabort_test\u001b[49m\u001b[43m=\u001b[49m\u001b[43mabort_test\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 464\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[31mTypeError\u001b[39m: Fitter.mcmc_sample() got an unexpected keyword argument 'chains'" + ] + } + ], + "source": [ + "# ---- Bayesian MCMC sampling -------------------------------------------------\n", + "# ``MultiFitter.mcmc_sample()`` delegates to the BUMPS DREAM sampler.\n", + "# All keyword arguments are forwarded with user-friendly names:\n", + "# ``samples`` ← total retained samples\n", + "# ``burn`` ← burn‑in steps\n", + "# ``thin`` ← thinning interval\n", + "# ``population``← BUMPS‑native ``pop`` for advanced users\n", + "\n", + "posterior_dict = fitter.mcmc_sample(\n", + " data,\n", + " samples=2000, # Short for demo; use 20 k+ in production\n", + " burn=500,\n", + " thin=10,\n", + ")\n", + "\n", + "print('DREAM sampling complete.')\n", + "print(f' Posterior shape : {posterior_dict[\"draws\"].shape}')\n", + "print(f' Parameters : {posterior_dict[\"param_names\"]}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f23934a0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:41.905335Z", + "iopub.status.busy": "2026-05-29T06:30:41.905335Z", + "iopub.status.idle": "2026-05-29T06:30:41.908528Z", + "shell.execute_reply": "2026-05-29T06:30:41.908528Z" + } + }, + "outputs": [], + "source": [ + "# ---- Wrap in PosteriorResults -----------------------------------------------\n", + "# ``PosteriorResults`` gives you a convenient object with built-in analysis\n", + "# methods. You can also use the raw ``dict`` with the standalone functions\n", + "# — both styles are shown below.\n", + "\n", + "posterior = PosteriorResults(\n", + " draws=posterior_dict['draws'],\n", + " param_names=posterior_dict['param_names'],\n", + " logp=posterior_dict.get('logp'),\n", + " sampler_state=posterior_dict.get('state'),\n", + ")\n", + "\n", + "print(posterior)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef300cb1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:41.909855Z", + "iopub.status.busy": "2026-05-29T06:30:41.909855Z", + "iopub.status.idle": "2026-05-29T06:30:41.913882Z", + "shell.execute_reply": "2026-05-29T06:30:41.913882Z" + } + }, + "outputs": [], + "source": [ + "# ---- Posterior summary table ------------------------------------------------\n", + "# ``.summary()`` prints mean, standard deviation and 95 % HDI for each parameter.\n", + "\n", + "print(posterior.summary())" + ] + }, + { + "cell_type": "markdown", + "id": "d1f0a300", + "metadata": {}, + "source": [ + "## Interactive posterior plots\n", + "\n", + "The next three cells render the same Plotly figures that are shown on the\n", + "Bayesian Posterior tab of the EasyReflectometryApp:\n", + "\n", + "* **Marginals** — per-parameter distribution: histogram + smooth KDE marginal,\n", + " shaded 95% credible interval, and median / best-sample lines\n", + " (`plot_distribution` or `posterior.distribution()`)\n", + "* **Corner plot** — pairwise marginals + 2-D contours + scatter (`plot_corner`)\n", + "* **Traces** — per-chain trace + marginal histogram (`plot_trace`)\n", + "\n", + "The standalone helpers are called with ``return_figure=True`` so they return a\n", + "Plotly ``Figure`` that we render in the notebook with ``fig.show()``; the\n", + "``PosteriorResults`` methods return the figure directly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b9d3c2a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:41.914887Z", + "iopub.status.busy": "2026-05-29T06:30:41.914887Z", + "iopub.status.idle": "2026-05-29T06:30:43.463799Z", + "shell.execute_reply": "2026-05-29T06:30:43.463252Z" + } + }, + "outputs": [], + "source": [ + "# ---- Marginal posterior distributions ---------------------------------------\n", + "# One panel per free parameter, each overlaying the posterior histogram, a\n", + "# smooth Gaussian-KDE marginal, the shaded 95% credible interval, and the\n", + "# median / best-sample reference lines. Mirrors the \"Marginals\" sub-tab in\n", + "# the EasyReflectometryApp.\n", + "\n", + "# The PosteriorResults method passes ``logp`` through automatically so the\n", + "# best posterior sample (MAP) line is drawn:\n", + "fig = posterior.distribution()\n", + "fig.show()\n", + "\n", + "# Standalone equivalent (pass ``logp`` to get the best-sample line):\n", + "# fig = plot_distribution(\n", + "# posterior.draws,\n", + "# posterior.param_names,\n", + "# logp=posterior.logp,\n", + "# return_figure=True,\n", + "# )\n", + "# fig.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e759a2a0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:43.464801Z", + "iopub.status.busy": "2026-05-29T06:30:43.464801Z", + "iopub.status.idle": "2026-05-29T06:30:43.847435Z", + "shell.execute_reply": "2026-05-29T06:30:43.847435Z" + } + }, + "outputs": [], + "source": [ + "# ---- Corner plot ------------------------------------------------------------\n", + "# Pairwise correlations: marginal density on the diagonal, scatter samples\n", + "# and 2-D contours on the lower triangle. Mirrors the \"Corner Plot\" sub-tab.\n", + "\n", + "fig = plot_corner(posterior.draws, posterior.param_names)\n", + "fig.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "593f5ed3", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:43.852778Z", + "iopub.status.busy": "2026-05-29T06:30:43.852778Z", + "iopub.status.idle": "2026-05-29T06:30:43.905089Z", + "shell.execute_reply": "2026-05-29T06:30:43.905089Z" + } + }, + "outputs": [], + "source": [ + "# ---- Trace plot -------------------------------------------------------------\n", + "# Per-chain trace (left column) and marginal histogram (right column).\n", + "# Mirrors the \"Traces\" sub-tab in the EasyReflectometryApp.\n", + "\n", + "fig = plot_trace(posterior.draws, posterior.param_names, return_figure=True)\n", + "fig.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3049e68a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:43.906601Z", + "iopub.status.busy": "2026-05-29T06:30:43.906601Z", + "iopub.status.idle": "2026-05-29T06:30:43.912231Z", + "shell.execute_reply": "2026-05-29T06:30:43.911707Z" + } + }, + "outputs": [], + "source": [ + "# ---- Credible intervals -----------------------------------------------------\n", + "# Equal-tailed 95 % credible intervals for each parameter.\n", + "\n", + "ci_95 = posterior.credible_interval(alpha=0.95)\n", + "print('95 % credible intervals:')\n", + "for name, (lo, hi) in ci_95.items():\n", + " print(f' {name:<30s} [{lo:.4f}, {hi:.4f}]')\n", + "\n", + "# You can request narrower intervals too:\n", + "ci_50 = posterior.credible_interval(alpha=0.50)\n", + "print('\\n50 % credible intervals:')\n", + "for name, (lo, hi) in ci_50.items():\n", + " print(f' {name:<30s} [{lo:.4f}, {hi:.4f}]')\n", + "\n", + "# Standalone equivalent:\n", + "# ci = credible_intervals(posterior.draws, posterior.param_names, alpha=0.95)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "806bf47a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:43.913735Z", + "iopub.status.busy": "2026-05-29T06:30:43.913735Z", + "iopub.status.idle": "2026-05-29T06:30:44.004052Z", + "shell.execute_reply": "2026-05-29T06:30:44.004052Z" + } + }, + "outputs": [], + "source": [ + "# ---- Pairwise focus: thickness vs SLD ---------------------------------------\n", + "# A 2-parameter corner plot zooms in on the joint posterior for a specific\n", + "# pair. This is the same view the EasyReflectometryApp's \"2D Heatmap\"\n", + "# sub-tab uses to inspect parameter degeneracies.\n", + "\n", + "\n", + "params_of_interest = ['Layer_1_ThicknessParameter_0', 'Material_1_SldParameter_0']\n", + "idx = [posterior.param_names.index(p) for p in params_of_interest]\n", + "subset_draws = np.asarray(posterior.draws)[:, idx]\n", + "\n", + "fig = plot_corner(subset_draws, params_of_interest)\n", + "fig.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34e3cc3e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:44.006603Z", + "iopub.status.busy": "2026-05-29T06:30:44.006603Z", + "iopub.status.idle": "2026-05-29T06:30:44.010758Z", + "shell.execute_reply": "2026-05-29T06:30:44.010226Z" + } + }, + "outputs": [], + "source": [ + "# ---- Gelman-Rubin R‑hat convergence diagnostic ------------------------------\n", + "# Requires ``arviz`` and at least 2 chains. Values close to 1.0 indicate\n", + "# good convergence.\n", + "\n", + "try:\n", + " rhat = posterior.gelman_rubin()\n", + " print('Gelman-Rubin R‑hat:')\n", + " for name, r in rhat.items():\n", + " flag = ' ✓' if r < 1.1 else ' ⚠'\n", + " print(f' {name:<30s} R̂ = {r:.3f}{flag}')\n", + "except ValueError:\n", + " print(\n", + " 'Skipped: Gelman-Rubin R‑hat requires at least 2 chains. '\n", + " 'Run with multiple DREAM populations to obtain multi-chain draws.'\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7e5816e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:44.012760Z", + "iopub.status.busy": "2026-05-29T06:30:44.012760Z", + "iopub.status.idle": "2026-05-29T06:30:45.165079Z", + "shell.execute_reply": "2026-05-29T06:30:45.165079Z" + } + }, + "outputs": [], + "source": [ + "# ---- Posterior-predictive reflectivity --------------------------------------\n", + "# Propagate parameter uncertainty through the model to obtain median\n", + "# reflectivity and 95 % credible band.\n", + "\n", + "r_median, r_lower, r_upper = posterior_predictive_reflectivity(\n", + " posterior.draws,\n", + " posterior.param_names,\n", + " model,\n", + " qz,\n", + " n_samples=200,\n", + ")\n", + "\n", + "plt.figure(figsize=(9, 6))\n", + "plt.semilogy(qz, r_data, 'o', label='Data', alpha=0.6)\n", + "plt.semilogy(qz, r_median, '-', color='tab:orange', label='Posterior median')\n", + "plt.fill_between(\n", + " qz,\n", + " r_lower,\n", + " r_upper,\n", + " color='tab:orange',\n", + " alpha=0.3,\n", + " label='95 % credible interval',\n", + ")\n", + "plt.xlabel('Q / Å⁻¹')\n", + "plt.ylabel('Reflectivity')\n", + "plt.title('Bayesian Posterior-Predictive Check')\n", + "plt.legend()\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()\n", + "\n", + "print('The 95 % credible interval captures parameter uncertainty propagated through the model.')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef27b8ec", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:45.166642Z", + "iopub.status.busy": "2026-05-29T06:30:45.166642Z", + "iopub.status.idle": "2026-05-29T06:30:45.413146Z", + "shell.execute_reply": "2026-05-29T06:30:45.413146Z" + } + }, + "outputs": [], + "source": [ + "# ---- Posterior-predictive SLD profile ---------------------------------------\n", + "# The same idea applied to the scattering-length-density profile.\n", + "\n", + "z, sld_median, sld_lower, sld_upper = posterior_predictive_sld_profile(\n", + " posterior.draws,\n", + " posterior.param_names,\n", + " model,\n", + " n_samples=200,\n", + ")\n", + "\n", + "plt.figure(figsize=(8, 4))\n", + "plt.plot(z, sld_median, label='Posterior median SLD')\n", + "plt.fill_between(\n", + " z,\n", + " sld_lower,\n", + " sld_upper,\n", + " alpha=0.3,\n", + " label='95 % credible interval',\n", + ")\n", + "plt.xlabel('z / Å')\n", + "plt.ylabel('SLD / 10⁻⁶ Å⁻²')\n", + "plt.title('SLD Profile with Bayesian Uncertainty')\n", + "plt.legend()\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37e78b4e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-05-29T06:30:45.415149Z", + "iopub.status.busy": "2026-05-29T06:30:45.415149Z", + "iopub.status.idle": "2026-05-29T06:30:45.419700Z", + "shell.execute_reply": "2026-05-29T06:30:45.419700Z" + } + }, + "outputs": [], + "source": [ + "# ---- Summary ----------------------------------------------------------------\n", + "print('―' * 60)\n", + "print('Bayesian analysis complete!')\n", + "print('―' * 60)\n", + "print()\n", + "print('API surface demonstrated:')\n", + "print(' MultiFitter.mcmc_sample(data, samples=, burn=, thin=)')\n", + "print(' PosteriorResults(draws, param_names, logp=, sampler_state=)')\n", + "print(' .summary() — formatted parameter table')\n", + "print(' .distribution() — per-parameter marginal Plotly figure')\n", + "print(' .corner() — pairwise correlation Plotly figure')\n", + "print(' .trace() — MCMC chain trace plot')\n", + "print(' .credible_interval(alpha) — equal-tailed credible intervals')\n", + "print(' .gelman_rubin() — R̂ convergence diagnostic')\n", + "print()\n", + "print(' Standalone functions (work on raw dict; the plot helpers return')\n", + "print(' the same interactive Plotly figures as the App):')\n", + "print(' posterior_summary(draws, names)')\n", + "print(' plot_distribution(draws, names, logp=, return_figure=True)')\n", + "print(' plot_corner(draws, names)')\n", + "print(' plot_trace(draws, names, return_figure=True)')\n", + "print(' credible_intervals(draws, names, alpha)')\n", + "print(' posterior_predictive_reflectivity(draws, names, model, q, n)')\n", + "print(' posterior_predictive_sld_profile(draws, names, model, n)')\n", + "print()\n", + "print('The high-level API provides clean, safe access to BUMPS DREAM sampling.')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "era", + "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.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/tutorials/advancedfitting/example.ort b/docs/docs/tutorials/advancedfitting/example.ort new file mode 100644 index 00000000..968fa7bb --- /dev/null +++ b/docs/docs/tutorials/advancedfitting/example.ort @@ -0,0 +1,448 @@ +# # ORSO reflectivity data file | 0.1 standard | YAML encoding | https://www.reflectometry.org/ +# data_source: +# owner: +# name: Andrew Nelson +# affiliation: ANSTO +# contact: Andrew.Nelson@ansto.gov.au +# experiment: +# facility: ANSTO +# start_date: 2021-05-12 +# title: Example data file from refnx docs +# instrument: platypus +# probe: neutron +# sample: +# name: Polymer Film +# category: solid / liquid +# composition: Si / SiO2 / Film / D2O +# measurement: +# instrument_settings: +# wavelength: +# magnitude: 12 +# unit: angstrom +# incident_angle: +# magnitude: 3 +# unit: deg +# data_files: +# - Unknown.nxs +# scheme: angle-dispersive +# reduction: +# software: ess +# timestamp: 2022-01-27T15:33:59+01:00 +# corrections: +# - footprint +# - incident intensity +# - detector efficiency +# columns: +# - {name: Qz, unit: 1/angstrom, dimension: WW transfer} +# - {name: R, dimension: reflectivity} +# - {name: sR, dimension: error-reflectivity} +# - {name: sQz, unit: 1/angstrom, dimension: resolution-WW transfer} +## Qz RQz sR sQz +8.060220000e-03 7.095810000e-01 8.506760000e-02 1.407419648e-04 +8.136620000e-03 8.622810000e-01 1.123700000e-01 1.420996057e-04 +8.263750000e-03 9.086470000e-01 7.900470000e-02 1.443588017e-04 +8.370670000e-03 7.732920000e-01 7.927280000e-02 1.462583099e-04 +8.450330000e-03 1.057970000e+00 1.259590000e-01 1.476732801e-04 +8.530830000e-03 1.015660000e+00 1.132950000e-01 1.491031133e-04 +8.612170000e-03 7.347170000e-01 6.115660000e-02 1.505473850e-04 +8.694370000e-03 7.692160000e-01 6.170580000e-02 1.520069445e-04 +8.777430000e-03 1.115740000e+00 1.127300000e-01 1.534813672e-04 +8.861360000e-03 9.723030000e-01 8.971600000e-02 1.549710776e-04 +8.946160000e-03 7.512140000e-01 5.493930000e-02 1.564760759e-04 +9.031850000e-03 7.976490000e-01 5.671220000e-02 1.579963619e-04 +9.118440000e-03 9.221890000e-01 6.858410000e-02 1.595327850e-04 +9.205930000e-03 9.757550000e-01 7.293950000e-02 1.610844959e-04 +9.294320000e-03 8.195040000e-01 5.216170000e-02 1.626523440e-04 +9.383640000e-03 7.883200000e-01 4.794730000e-02 1.642367538e-04 +9.473890000e-03 7.947010000e-01 4.602240000e-02 1.658368761e-04 +9.565080000e-03 8.744000000e-01 5.151640000e-02 1.674535601e-04 +9.657210000e-03 8.396620000e-01 4.742850000e-02 1.690872306e-04 +9.750300000e-03 8.008720000e-01 4.399580000e-02 1.707370382e-04 +9.844360000e-03 1.117100000e+00 7.373300000e-02 1.724042569e-04 +9.939390000e-03 8.884110000e-01 4.954100000e-02 1.740884620e-04 +1.003540000e-02 7.791290000e-01 3.898730000e-02 1.757896536e-04 +1.013240000e-02 7.999680000e-01 3.899740000e-02 1.775086809e-04 +1.023040000e-02 8.431240000e-01 4.159800000e-02 1.792451193e-04 +1.032940000e-02 9.613320000e-01 4.925360000e-02 1.809989689e-04 +1.048680000e-02 8.805440000e-01 2.990830000e-02 1.837851690e-04 +1.063270000e-02 7.557350000e-01 3.208510000e-02 1.863700799e-04 +1.073590000e-02 9.712310000e-01 4.534850000e-02 1.881969711e-04 +1.084010000e-02 8.955490000e-01 3.905420000e-02 1.900425474e-04 +1.094540000e-02 8.625890000e-01 3.580720000e-02 1.919068087e-04 +1.105180000e-02 8.909920000e-01 3.612570000e-02 1.937906045e-04 +1.115930000e-02 9.003480000e-01 3.679240000e-02 1.956930853e-04 +1.126790000e-02 8.459270000e-01 3.218810000e-02 1.976151006e-04 +1.137760000e-02 9.431520000e-01 3.655330000e-02 1.995570749e-04 +1.148840000e-02 9.956310000e-01 3.901160000e-02 2.015185836e-04 +1.160040000e-02 9.695940000e-01 3.636230000e-02 2.034996267e-04 +1.171350000e-02 9.051810000e-01 3.204100000e-02 2.055014781e-04 +1.182780000e-02 8.933810000e-01 3.061190000e-02 2.075232887e-04 +1.194330000e-02 9.196020000e-01 3.146770000e-02 2.095659076e-04 +1.205990000e-02 9.189980000e-01 3.060970000e-02 2.116293349e-04 +1.217770000e-02 7.810560000e-01 2.355420000e-02 2.137135706e-04 +1.229680000e-02 8.649150000e-01 2.720250000e-02 2.158190394e-04 +1.241700000e-02 8.435160000e-01 2.550150000e-02 2.179457412e-04 +1.253850000e-02 9.984180000e-01 3.197220000e-02 2.200945253e-04 +1.266120000e-02 8.812600000e-01 2.602730000e-02 2.222645425e-04 +1.278520000e-02 8.835690000e-01 2.558620000e-02 2.244570667e-04 +1.291050000e-02 9.376700000e-01 2.746510000e-02 2.266716733e-04 +1.303700000e-02 1.019200000e+00 3.051070000e-02 2.289087870e-04 +1.316480000e-02 8.455260000e-01 2.262690000e-02 2.311684076e-04 +1.329390000e-02 8.738040000e-01 2.324680000e-02 2.334509599e-04 +1.342430000e-02 8.659530000e-01 2.249060000e-02 2.357568686e-04 +1.355610000e-02 8.779820000e-01 2.244910000e-02 2.380861337e-04 +1.368920000e-02 9.475450000e-01 2.489850000e-02 2.404387551e-04 +1.382370000e-02 8.881540000e-01 2.209620000e-02 2.428155821e-04 +1.395950000e-02 8.913620000e-01 2.181480000e-02 2.452161902e-04 +1.409670000e-02 8.884560000e-01 2.150920000e-02 2.476410039e-04 +1.423530000e-02 9.137170000e-01 2.225860000e-02 2.500908727e-04 +1.437530000e-02 8.103640000e-01 1.882890000e-02 2.525649471e-04 +1.451680000e-02 7.385480000e-01 1.688270000e-02 2.550645011e-04 +1.465970000e-02 6.865100000e-01 1.597420000e-02 2.575891102e-04 +1.480400000e-02 5.822400000e-01 1.350210000e-02 2.601396235e-04 +1.494980000e-02 4.468550000e-01 1.006260000e-02 2.627156166e-04 +1.509700000e-02 3.924610000e-01 9.155490000e-03 2.653175139e-04 +1.524580000e-02 3.205170000e-01 7.319590000e-03 2.679461649e-04 +1.539610000e-02 2.810060000e-01 6.399090000e-03 2.706011448e-04 +1.554790000e-02 2.401000000e-01 5.442390000e-03 2.732828784e-04 +1.570120000e-02 2.208810000e-01 5.024370000e-03 2.759917903e-04 +1.585610000e-02 1.920330000e-01 4.314410000e-03 2.787278805e-04 +1.601260000e-02 1.798490000e-01 4.051590000e-03 2.814919983e-04 +1.617070000e-02 1.600690000e-01 3.562020000e-03 2.842837190e-04 +1.633030000e-02 1.531290000e-01 3.467770000e-03 2.871038920e-04 +1.649160000e-02 1.342200000e-01 3.016000000e-03 2.899525174e-04 +1.665450000e-02 1.283300000e-01 2.888530000e-03 2.928300196e-04 +1.681900000e-02 1.247940000e-01 2.861820000e-03 2.957363988e-04 +1.698530000e-02 1.091270000e-01 2.483100000e-03 2.986720796e-04 +1.715320000e-02 1.044290000e-01 2.353920000e-03 3.016374867e-04 +1.732280000e-02 9.468300000e-02 2.091620000e-03 3.046326200e-04 +1.758810000e-02 8.969110000e-02 1.439380000e-03 3.093191777e-04 +1.784190000e-02 8.091440000e-02 1.872780000e-03 3.138010489e-04 +1.801850000e-02 7.465440000e-02 1.714220000e-03 3.169189092e-04 +1.819680000e-02 7.036610000e-02 1.634230000e-03 3.200686191e-04 +1.837700000e-02 6.904450000e-02 1.605300000e-03 3.232497539e-04 +1.855890000e-02 6.270550000e-02 1.461460000e-03 3.264631629e-04 +1.874270000e-02 5.939150000e-02 1.400070000e-03 3.297088462e-04 +1.892840000e-02 5.754770000e-02 1.360950000e-03 3.329872283e-04 +1.911590000e-02 5.138330000e-02 1.226680000e-03 3.362987340e-04 +1.930540000e-02 4.922670000e-02 1.178240000e-03 3.396437879e-04 +1.949670000e-02 4.521740000e-02 1.079620000e-03 3.430219654e-04 +1.969000000e-02 4.245560000e-02 1.022250000e-03 3.464349651e-04 +1.988520000e-02 4.126130000e-02 9.947810000e-04 3.498819376e-04 +2.008240000e-02 3.523330000e-02 8.777900000e-04 3.533637323e-04 +2.028150000e-02 3.352710000e-02 8.367470000e-04 3.568803492e-04 +2.048270000e-02 3.326840000e-02 8.339590000e-04 3.604326376e-04 +2.068590000e-02 3.166440000e-02 7.898860000e-04 3.640210222e-04 +2.089120000e-02 2.916000000e-02 7.496130000e-04 3.676450784e-04 +2.109850000e-02 2.652010000e-02 7.063830000e-04 3.713060800e-04 +2.130800000e-02 2.518290000e-02 6.713890000e-04 3.750036024e-04 +2.151950000e-02 2.387570000e-02 6.426930000e-04 3.787384951e-04 +2.173310000e-02 2.289290000e-02 6.330680000e-04 3.825111825e-04 +2.194900000e-02 2.086460000e-02 5.931280000e-04 3.863216648e-04 +2.216690000e-02 2.087710000e-02 5.920100000e-04 3.901703665e-04 +2.238710000e-02 1.822280000e-02 5.292050000e-04 3.940581370e-04 +2.260950000e-02 1.773460000e-02 5.251520000e-04 3.979849764e-04 +2.283410000e-02 1.587140000e-02 4.818770000e-04 4.019513092e-04 +2.306100000e-02 1.432550000e-02 4.516190000e-04 4.059575601e-04 +2.329020000e-02 1.427760000e-02 4.522490000e-04 4.100041538e-04 +2.352170000e-02 1.266240000e-02 4.198450000e-04 4.140919397e-04 +2.375550000e-02 1.221280000e-02 4.137860000e-04 4.182204929e-04 +2.399170000e-02 1.046080000e-02 3.745320000e-04 4.223906630e-04 +2.423020000e-02 1.061330000e-02 3.810520000e-04 4.266016005e-04 +2.447120000e-02 9.879030000e-03 3.641280000e-04 4.308567027e-04 +2.471450000e-02 8.372030000e-03 3.313930000e-04 4.351542710e-04 +2.496030000e-02 7.670480000e-03 3.077050000e-04 4.394943054e-04 +2.520860000e-02 7.344890000e-03 3.049780000e-04 4.438810525e-04 +2.545940000e-02 6.798650000e-03 2.898680000e-04 4.483102657e-04 +2.571270000e-02 5.916300000e-03 2.671780000e-04 4.527819450e-04 +2.596850000e-02 5.344980000e-03 2.515360000e-04 4.573003369e-04 +2.622690000e-02 5.122650000e-03 2.474120000e-04 4.618611950e-04 +2.648800000e-02 4.750310000e-03 2.379530000e-04 4.664730124e-04 +2.675160000e-02 4.307150000e-03 2.238500000e-04 4.711272958e-04 +2.701790000e-02 4.018170000e-03 2.200510000e-04 4.758325386e-04 +2.728680000e-02 3.539150000e-03 2.046530000e-04 4.805802475e-04 +2.755850000e-02 3.818190000e-03 2.079440000e-04 4.853789156e-04 +2.783290000e-02 2.864750000e-03 1.819210000e-04 4.902285431e-04 +2.811000000e-02 2.795800000e-03 1.766910000e-04 4.951206367e-04 +2.839000000e-02 2.621500000e-03 1.750020000e-04 5.000679362e-04 +2.867270000e-02 2.484770000e-03 1.694560000e-04 5.050619484e-04 +2.895830000e-02 2.420090000e-03 1.709250000e-04 5.101069199e-04 +2.924670000e-02 2.359260000e-03 1.700600000e-04 5.151986041e-04 +2.953810000e-02 1.978560000e-03 1.600210000e-04 5.203454942e-04 +2.983230000e-02 1.947200000e-03 1.582820000e-04 5.255475902e-04 +3.012960000e-02 1.735930000e-03 1.527310000e-04 5.307963989e-04 +3.042980000e-02 1.894590000e-03 1.603050000e-04 5.361004136e-04 +3.073300000e-02 1.696680000e-03 1.525740000e-04 5.414596341e-04 +3.103920000e-02 1.793690000e-03 1.592450000e-04 5.468698140e-04 +3.134860000e-02 1.786860000e-03 1.552020000e-04 5.523351998e-04 +3.166100000e-02 1.872010000e-03 1.583950000e-04 5.578557915e-04 +3.197660000e-02 1.688180000e-03 1.499840000e-04 5.634315891e-04 +3.229530000e-02 1.732370000e-03 1.543250000e-04 5.690625926e-04 +3.261730000e-02 1.537600000e-03 1.453960000e-04 5.747530487e-04 +3.294240000e-02 1.541340000e-03 1.497560000e-04 5.804987107e-04 +3.327080000e-02 1.700330000e-03 1.572080000e-04 5.863038252e-04 +3.360260000e-02 2.142240000e-03 1.728870000e-04 5.921683922e-04 +3.393760000e-02 1.944020000e-03 1.686890000e-04 5.980924118e-04 +3.422050000e-02 1.914000000e-03 1.077140000e-04 6.019483327e-04 +3.466450000e-02 1.986410000e-03 1.438280000e-04 6.103523719e-04 +3.502190000e-02 1.849740000e-03 1.279740000e-04 6.165099550e-04 +3.538500000e-02 1.932640000e-03 1.227080000e-04 6.227354838e-04 +3.571620000e-02 2.188990000e-03 1.495980000e-04 6.288506008e-04 +3.607730000e-02 2.314320000e-03 1.503450000e-04 6.351568151e-04 +3.646770000e-02 1.831770000e-03 1.041590000e-04 6.416328939e-04 +3.682630000e-02 1.797150000e-03 1.012920000e-04 6.480155472e-04 +3.716160000e-02 2.278740000e-03 1.417330000e-04 6.543599810e-04 +3.754210000e-02 2.183390000e-03 1.294600000e-04 6.609379784e-04 +3.794040000e-02 1.799000000e-03 9.624980000e-05 6.676221409e-04 +3.831280000e-02 1.828080000e-03 9.734640000e-05 6.742680840e-04 +3.868400000e-02 1.949850000e-03 1.082600000e-04 6.809692330e-04 +3.906920000e-02 2.042140000e-03 1.122450000e-04 6.877680540e-04 +3.947340000e-02 1.659460000e-03 8.395540000e-05 6.946772869e-04 +3.986330000e-02 1.620310000e-03 8.073380000e-05 7.016035062e-04 +4.026580000e-02 1.588620000e-03 7.617690000e-05 7.086189042e-04 +4.066080000e-02 1.582510000e-03 7.829120000e-05 7.156810150e-04 +4.107010000e-02 1.429900000e-03 7.034450000e-05 7.228323046e-04 +4.147810000e-02 1.336120000e-03 6.498820000e-05 7.300472933e-04 +4.187440000e-02 1.721900000e-03 9.200060000e-05 7.373174879e-04 +4.230490000e-02 1.404890000e-03 6.788170000e-05 7.446980943e-04 +4.273170000e-02 1.146480000e-03 5.315150000e-05 7.521381533e-04 +4.315460000e-02 1.030130000e-03 4.872800000e-05 7.596504046e-04 +4.358350000e-02 1.018640000e-03 4.851630000e-05 7.672348483e-04 +4.401550000e-02 1.060250000e-03 5.148880000e-05 7.748999775e-04 +4.445490000e-02 9.115610000e-04 4.517670000e-05 7.826415457e-04 +4.489830000e-02 7.795360000e-04 3.746760000e-05 7.904553063e-04 +4.534700000e-02 5.957570000e-04 2.867780000e-05 7.983539991e-04 +4.579470000e-02 6.476450000e-04 3.286690000e-05 8.063333774e-04 +4.625120000e-02 5.268390000e-04 2.666210000e-05 8.143891946e-04 +4.671240000e-02 4.365460000e-04 2.240950000e-05 8.225299441e-04 +4.717830000e-02 3.696050000e-04 1.929650000e-05 8.307471325e-04 +4.764690000e-02 3.315640000e-04 1.808550000e-05 8.390577463e-04 +4.812080000e-02 2.545840000e-04 1.440620000e-05 8.474405525e-04 +4.859940000e-02 2.090800000e-04 1.312430000e-05 8.559167841e-04 +4.908280000e-02 1.929370000e-04 1.261590000e-05 8.644779478e-04 +4.957100000e-02 1.309220000e-04 9.992220000e-06 8.731240437e-04 +5.006400000e-02 1.098230000e-04 9.080510000e-06 8.818635651e-04 +5.056190000e-02 9.565480000e-05 8.370090000e-06 8.906880186e-04 +5.106550000e-02 7.925720000e-05 7.699130000e-06 8.995974043e-04 +5.157340000e-02 7.956050000e-05 6.898560000e-06 9.086044619e-04 +5.208640000e-02 7.224950000e-05 6.102730000e-06 9.177006984e-04 +5.260450000e-02 7.825130000e-05 6.391360000e-06 9.268903603e-04 +5.312790000e-02 8.637120000e-05 6.561860000e-06 9.361734476e-04 +5.365650000e-02 1.331620000e-04 8.789840000e-06 9.455499603e-04 +5.419050000e-02 1.436320000e-04 8.178840000e-06 9.550241449e-04 +5.472980000e-02 1.586820000e-04 8.565340000e-06 9.645917550e-04 +5.527450000e-02 1.900070000e-04 9.755240000e-06 9.742612837e-04 +5.582470000e-02 2.354200000e-04 1.141720000e-05 9.840284844e-04 +5.638040000e-02 2.339320000e-04 1.034520000e-05 9.938933571e-04 +5.694170000e-02 2.433900000e-04 1.059550000e-05 1.003855902e-03 +5.750870000e-02 2.708580000e-04 1.125130000e-05 1.013924612e-03 +5.808130000e-02 3.109660000e-04 1.233550000e-05 1.024095240e-03 +5.865970000e-02 3.483270000e-04 1.357860000e-05 1.034367788e-03 +5.924390000e-02 3.329570000e-04 1.275590000e-05 1.044750747e-03 +5.983400000e-02 3.546590000e-04 1.322360000e-05 1.055235624e-03 +6.043000000e-02 3.544670000e-04 1.306610000e-05 1.065826667e-03 +6.103190000e-02 3.855150000e-04 1.408930000e-05 1.076523875e-03 +6.164000000e-02 3.325090000e-04 1.201760000e-05 1.087335742e-03 +6.225410000e-02 3.330870000e-04 1.197150000e-05 1.098253773e-03 +6.287440000e-02 3.303070000e-04 1.197230000e-05 1.109286464e-03 +6.350090000e-02 3.224690000e-04 1.162910000e-05 1.120433812e-03 +6.413370000e-02 2.744620000e-04 9.948790000e-06 1.131691573e-03 +6.477280000e-02 2.792430000e-04 1.025370000e-05 1.143063992e-03 +6.541840000e-02 2.389380000e-04 8.960850000e-06 1.154555315e-03 +6.607040000e-02 2.330280000e-04 8.726650000e-06 1.166165544e-03 +6.672900000e-02 1.863430000e-04 7.236140000e-06 1.177890432e-03 +6.739420000e-02 1.649490000e-04 6.619500000e-06 1.189738471e-03 +6.806600000e-02 1.332440000e-04 5.541570000e-06 1.201709662e-03 +6.874460000e-02 1.153760000e-04 4.971620000e-06 1.213799758e-03 +6.943000000e-02 8.250330000e-05 3.951150000e-06 1.226017252e-03 +7.012230000e-02 6.719230000e-05 3.570180000e-06 1.238357898e-03 +7.082150000e-02 5.033220000e-05 2.926230000e-06 1.250825942e-03 +7.152780000e-02 3.336120000e-05 2.509700000e-06 1.263421384e-03 +7.224110000e-02 2.290520000e-05 2.116380000e-06 1.276148471e-03 +7.293380000e-02 1.879440000e-05 1.860530000e-06 1.288068702e-03 +7.366830000e-02 1.553440000e-05 1.798210000e-06 1.301292643e-03 +7.440150000e-02 1.824520000e-05 1.951840000e-06 1.314359459e-03 +7.514050000e-02 2.235620000e-05 1.801000000e-06 1.327506960e-03 +7.587210000e-02 2.773620000e-05 1.890140000e-06 1.340293500e-03 +7.664020000e-02 3.931010000e-05 2.300950000e-06 1.354192651e-03 +7.739640000e-02 4.596690000e-05 2.332400000e-06 1.367582209e-03 +7.810610000e-02 5.541310000e-05 2.459260000e-06 1.379328330e-03 +7.886820000e-02 6.726050000e-05 2.729570000e-06 1.392692408e-03 +7.979530000e-02 7.650640000e-05 2.773090000e-06 1.409143772e-03 +8.072890000e-02 7.794720000e-05 2.857410000e-06 1.425518696e-03 +8.153340000e-02 8.505420000e-05 2.990400000e-06 1.439910454e-03 +8.233180000e-02 9.685690000e-05 3.222700000e-06 1.454408377e-03 +8.316050000e-02 9.462400000e-05 3.166210000e-06 1.469122877e-03 +8.402520000e-02 8.585640000e-05 2.824460000e-06 1.484058201e-03 +8.486480000e-02 8.101440000e-05 2.662540000e-06 1.499057224e-03 +8.573420000e-02 7.655920000e-05 2.549780000e-06 1.514268578e-03 +8.655530000e-02 7.385430000e-05 2.485840000e-06 1.529496917e-03 +8.743720000e-02 6.249710000e-05 2.190920000e-06 1.545009780e-03 +8.831630000e-02 5.495430000e-05 2.001270000e-06 1.560658534e-03 +8.911080000e-02 5.694540000e-05 2.081580000e-06 1.576247836e-03 +9.004160000e-02 4.229200000e-05 1.729710000e-06 1.592304265e-03 +9.095460000e-02 3.367540000e-05 1.458910000e-06 1.608462612e-03 +9.185040000e-02 2.545640000e-05 1.249630000e-06 1.624727124e-03 +9.274350000e-02 2.092210000e-05 1.159760000e-06 1.641140268e-03 +9.362890000e-02 1.531120000e-05 1.060140000e-06 1.657689303e-03 +9.455270000e-02 1.265650000e-05 9.577530000e-07 1.674480395e-03 +9.551780000e-02 9.331240000e-06 8.621390000e-07 1.691509297e-03 +9.647710000e-02 8.901520000e-06 7.828970000e-07 1.708691077e-03 +9.741890000e-02 1.079150000e-05 8.938470000e-07 1.725991763e-03 +9.842590000e-02 1.213680000e-05 9.113080000e-07 1.743589710e-03 +9.942010000e-02 1.540800000e-05 9.077770000e-07 1.761323549e-03 +1.004250000e-01 1.938030000e-05 9.602530000e-07 1.779248486e-03 +1.014340000e-01 2.091160000e-05 1.040890000e-06 1.797347534e-03 +1.024990000e-01 2.519250000e-05 1.078370000e-06 1.815722611e-03 +1.034980000e-01 2.818800000e-05 1.132170000e-06 1.834144401e-03 +1.045460000e-01 2.998100000e-05 1.178530000e-06 1.852837973e-03 +1.056080000e-01 3.056280000e-05 1.150490000e-06 1.871735383e-03 +1.066820000e-01 2.819030000e-05 1.099070000e-06 1.890832384e-03 +1.077580000e-01 2.692630000e-05 1.037400000e-06 1.910111989e-03 +1.088310000e-01 2.541920000e-05 1.012560000e-06 1.929574198e-03 +1.099160000e-01 2.282700000e-05 9.367100000e-07 1.949244491e-03 +1.110400000e-01 1.769170000e-05 7.904500000e-07 1.969169580e-03 +1.121170000e-01 1.547510000e-05 7.625400000e-07 1.989209328e-03 +1.132440000e-01 1.172580000e-05 6.688050000e-07 2.009529352e-03 +1.143190000e-01 1.050270000e-05 6.598050000e-07 2.029959788e-03 +1.154640000e-01 8.121530000e-06 5.847030000e-07 2.050708720e-03 +1.166450000e-01 4.988840000e-06 5.267190000e-07 2.071720941e-03 +1.177620000e-01 4.909990000e-06 5.360960000e-07 2.092830835e-03 +1.189250000e-01 5.304340000e-06 5.240840000e-07 2.114229497e-03 +1.201290000e-01 5.689050000e-06 4.912340000e-07 2.135895696e-03 +1.213460000e-01 6.444710000e-06 5.061070000e-07 2.157799706e-03 +1.225860000e-01 7.456190000e-06 5.058030000e-07 2.179954265e-03 +1.238230000e-01 8.248420000e-06 5.025600000e-07 2.202325401e-03 +1.250910000e-01 1.067490000e-05 5.845390000e-07 2.224964074e-03 +1.263610000e-01 1.131620000e-05 5.722280000e-07 2.247827816e-03 +1.276430000e-01 1.104400000e-05 5.762550000e-07 2.270937863e-03 +1.289320000e-01 1.062580000e-05 5.655340000e-07 2.294294212e-03 +1.302100000e-01 9.337660000e-06 5.254720000e-07 2.317858646e-03 +1.315470000e-01 8.922800000e-06 5.000680000e-07 2.341754314e-03 +1.328540000e-01 6.436110000e-06 4.446880000e-07 2.365841081e-03 +1.341620000e-01 6.181130000e-06 4.556250000e-07 2.390178397e-03 +1.355150000e-01 4.685680000e-06 4.034780000e-07 2.414821469e-03 +1.368240000e-01 4.667540000e-06 3.809290000e-07 2.439659885e-03 +1.382040000e-01 4.262500000e-06 3.763010000e-07 2.464842276e-03 +1.395890000e-01 3.884300000e-06 3.571580000e-07 2.490292204e-03 +1.409890000e-01 3.699480000e-06 3.796620000e-07 2.516022408e-03 +1.424300000e-01 3.659930000e-06 3.543380000e-07 2.542066861e-03 +1.438510000e-01 4.559390000e-06 3.665690000e-07 2.568357617e-03 +1.452630000e-01 4.580880000e-06 3.470620000e-07 2.594911663e-03 +1.467500000e-01 4.829290000e-06 3.503920000e-07 2.621826671e-03 +1.482250000e-01 4.930920000e-06 3.533300000e-07 2.649009215e-03 +1.497100000e-01 4.781000000e-06 3.612560000e-07 2.676489022e-03 +1.512250000e-01 4.644840000e-06 3.397110000e-07 2.704291571e-03 +1.527360000e-01 4.365080000e-06 3.324590000e-07 2.732374397e-03 +1.542610000e-01 3.807060000e-06 3.253780000e-07 2.760775718e-03 +1.558250000e-01 3.544150000e-06 3.053880000e-07 2.789516767e-03 +1.573660000e-01 2.632690000e-06 2.773550000e-07 2.818538093e-03 +1.589410000e-01 2.129680000e-06 2.570610000e-07 2.847903395e-03 +1.605110000e-01 2.509080000e-06 2.684170000e-07 2.877574452e-03 +1.621370000e-01 2.606260000e-06 2.683540000e-07 2.907627704e-03 +1.637550000e-01 2.467960000e-06 2.618110000e-07 2.937986711e-03 +1.654040000e-01 2.437690000e-06 2.457080000e-07 2.968710928e-03 +1.670350000e-01 2.818460000e-06 2.626350000e-07 2.999740899e-03 +1.687360000e-01 3.201910000e-06 2.677220000e-07 3.031182793e-03 +1.704100000e-01 3.020960000e-06 2.548660000e-07 3.062934688e-03 +1.721190000e-01 2.398550000e-06 2.324640000e-07 3.095064532e-03 +1.738390000e-01 2.458240000e-06 2.385460000e-07 3.127555337e-03 +1.755740000e-01 2.033660000e-06 2.231960000e-07 3.160419844e-03 +1.773360000e-01 1.684040000e-06 2.048280000e-07 3.193666546e-03 +1.790870000e-01 1.293830000e-06 2.007650000e-07 3.227265717e-03 +1.809070000e-01 1.439670000e-06 1.882070000e-07 3.261306534e-03 +1.827150000e-01 1.540200000e-06 1.999980000e-07 3.295708314e-03 +1.845350000e-01 1.338490000e-06 1.864820000e-07 3.330500781e-03 +1.863830000e-01 1.615990000e-06 1.863690000e-07 3.365705170e-03 +1.882470000e-01 1.493470000e-06 1.883860000e-07 3.401317233e-03 +1.901260000e-01 2.059680000e-06 1.920700000e-07 3.437345464e-03 +1.920380000e-01 1.690150000e-06 1.767700000e-07 3.473806849e-03 +1.939540000e-01 1.488370000e-06 1.792740000e-07 3.510675908e-03 +1.959000000e-01 1.403820000e-06 1.747280000e-07 3.547990861e-03 +1.978600000e-01 1.346890000e-06 1.705690000e-07 3.585734722e-03 +1.998450000e-01 9.477100000e-07 1.617790000e-07 3.623937217e-03 +2.018330000e-01 1.089550000e-06 1.735020000e-07 3.662568619e-03 +2.038630000e-01 1.246350000e-06 1.654660000e-07 3.701684134e-03 +2.059040000e-01 1.227370000e-06 1.644900000e-07 3.741254037e-03 +2.079590000e-01 1.168960000e-06 1.584560000e-07 3.781286820e-03 +2.100420000e-01 1.161720000e-06 1.521980000e-07 3.821807963e-03 +2.121400000e-01 1.216890000e-06 1.588540000e-07 3.862808973e-03 +2.142570000e-01 1.317570000e-06 1.546070000e-07 3.904298343e-03 +2.163990000e-01 1.032790000e-06 1.472920000e-07 3.946293059e-03 +2.185700000e-01 1.030660000e-06 1.514730000e-07 3.988805862e-03 +2.207810000e-01 5.945350000e-07 1.511720000e-07 4.031853738e-03 +2.229880000e-01 7.279960000e-07 1.429280000e-07 4.075394220e-03 +2.252160000e-01 7.806310000e-07 1.485250000e-07 4.119465528e-03 +2.274670000e-01 1.063390000e-06 1.499300000e-07 4.164076155e-03 +2.297410000e-01 6.526970000e-07 1.296680000e-07 4.209234596e-03 +2.320370000e-01 1.074380000e-06 1.484530000e-07 4.254932355e-03 +2.343560000e-01 8.687630000e-07 1.468620000e-07 4.301220393e-03 +2.366980000e-01 9.218680000e-07 1.450820000e-07 4.348060490e-03 +2.390640000e-01 6.471490000e-07 1.368090000e-07 4.395495113e-03 +2.414540000e-01 5.363930000e-07 1.356880000e-07 4.443524261e-03 +2.438670000e-01 6.337170000e-07 1.304030000e-07 4.492147934e-03 +2.463040000e-01 6.292700000e-07 1.365570000e-07 4.541408598e-03 +2.487660000e-01 6.332920000e-07 1.224530000e-07 4.591263788e-03 +2.512530000e-01 1.037050000e-06 1.354090000e-07 4.641713503e-03 +2.537640000e-01 8.252860000e-07 1.431610000e-07 4.692842675e-03 +2.563010000e-01 6.268250000e-07 1.192570000e-07 4.744608839e-03 +2.588630000e-01 5.252590000e-07 1.227410000e-07 4.797054460e-03 +2.614500000e-01 5.218320000e-07 1.309180000e-07 4.850137073e-03 +2.640640000e-01 3.916590000e-07 1.213700000e-07 4.903941609e-03 +2.667030000e-01 4.724390000e-07 1.399730000e-07 4.958425602e-03 +2.693690000e-01 5.595360000e-07 1.391510000e-07 5.013589053e-03 +2.720620000e-01 6.640700000e-07 1.448670000e-07 5.069474428e-03 +2.747820000e-01 4.593780000e-07 1.514660000e-07 5.126081726e-03 +2.775290000e-01 3.669610000e-07 1.435460000e-07 5.183453413e-03 +2.803030000e-01 5.315310000e-07 1.483080000e-07 5.241589490e-03 +2.831050000e-01 4.289140000e-07 1.361450000e-07 5.300447491e-03 +2.859350000e-01 5.520060000e-07 1.419830000e-07 5.360112348e-03 +2.887930000e-01 5.702640000e-07 1.529000000e-07 5.420541594e-03 +2.916800000e-01 5.047310000e-07 1.364290000e-07 5.481777696e-03 +2.945960000e-01 6.619230000e-07 1.407930000e-07 5.543863119e-03 +2.975410000e-01 7.601320000e-07 1.607210000e-07 5.606755399e-03 +3.005160000e-01 4.685270000e-07 1.348870000e-07 5.670454534e-03 +3.035200000e-01 4.428600000e-07 1.471340000e-07 5.735087923e-03 +3.065540000e-01 4.899790000e-07 1.404300000e-07 5.800528167e-03 +3.096190000e-01 3.601630000e-07 1.337280000e-07 5.866902666e-03 +3.127140000e-01 2.905630000e-07 1.480620000e-07 5.934168953e-03 +3.158410000e-01 5.199630000e-07 1.515620000e-07 6.002327027e-03 +3.189980000e-01 4.666650000e-07 1.562560000e-07 6.071461822e-03 +3.221870000e-01 3.888830000e-07 1.570110000e-07 6.141530870e-03 +3.254080000e-01 4.467780000e-07 1.555120000e-07 6.212576639e-03 +3.286620000e-01 3.238850000e-07 1.525860000e-07 6.284599127e-03 +3.319470000e-01 3.787360000e-07 1.581800000e-07 6.357640802e-03 +3.352660000e-01 3.881990000e-07 1.539110000e-07 6.431701663e-03 +3.386180000e-01 4.428770000e-07 1.490980000e-07 6.506824176e-03 +3.420030000e-01 2.477870000e-07 1.463210000e-07 6.582965876e-03 +3.454230000e-01 2.857690000e-07 1.351350000e-07 6.660211693e-03 +3.488760000e-01 4.759640000e-07 1.536640000e-07 6.738561630e-03 +3.523640000e-01 4.496390000e-07 1.539560000e-07 6.818015684e-03 +3.558870000e-01 2.471470000e-07 1.443220000e-07 6.898573857e-03 +3.594450000e-01 2.167640000e-07 1.491810000e-07 6.980363546e-03 +3.630390000e-01 5.205850000e-07 1.573690000e-07 7.063257354e-03 +3.666690000e-01 5.246970000e-07 1.545410000e-07 7.147425144e-03 +3.703350000e-01 3.644030000e-07 1.550240000e-07 7.232781985e-03 +3.740370000e-01 4.597520000e-07 1.653080000e-07 7.319370343e-03 +3.777770000e-01 5.359220000e-07 1.820430000e-07 7.407232683e-03 +3.815540000e-01 4.268070000e-07 1.770030000e-07 7.496411472e-03 +3.853690000e-01 4.036660000e-07 1.811100000e-07 7.586906710e-03 +3.892220000e-01 2.554540000e-07 1.597290000e-07 7.678718396e-03 +3.931130000e-01 9.269720000e-08 1.654020000e-07 7.771931464e-03 +3.970440000e-01 1.106720000e-07 1.808880000e-07 7.866503446e-03 +4.010140000e-01 4.521630000e-07 1.731910000e-07 7.962519276e-03 +4.050230000e-01 3.780660000e-07 1.515970000e-07 8.059978953e-03 +4.090730000e-01 3.091360000e-07 1.571950000e-07 8.158924942e-03 +4.131630000e-01 3.417740000e-07 1.448180000e-07 8.259357245e-03 +4.172940000e-01 3.449240000e-07 1.595810000e-07 8.361318327e-03 +4.214660000e-01 2.518400000e-07 1.597570000e-07 8.464850655e-03 +4.256800000e-01 4.017370000e-07 1.538140000e-07 8.569954228e-03 +4.299360000e-01 3.172790000e-07 1.691300000e-07 8.676713978e-03 +4.342350000e-01 5.506310000e-07 1.611420000e-07 8.785087440e-03 +4.385770000e-01 5.085100000e-07 1.649900000e-07 8.895159545e-03 +4.429620000e-01 6.025930000e-07 1.738350000e-07 9.006930294e-03 +4.473910000e-01 4.384540000e-07 1.653500000e-07 9.120484618e-03 +4.518650000e-01 3.387570000e-07 1.876390000e-07 9.235822519e-03 +4.563830000e-01 4.358460000e-07 1.978260000e-07 9.352943995e-03 +4.609460000e-01 3.855790000e-07 1.761430000e-07 9.471933979e-03 +4.655550000e-01 3.834150000e-07 1.884540000e-07 9.592834938e-03 diff --git a/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb b/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb index 02f675ba..1c482386 100644 --- a/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb +++ b/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb @@ -72,14 +72,7 @@ "id": "694b4e5e-2d1a-402e-aa3f-a26cc82f7774", "metadata": {}, "outputs": [], - "source": [ - "file_path = pooch.retrieve(\n", - " # URL to one of Pooch's test files\n", - " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/advancedfitting/multiple.ort',\n", - " known_hash='241bcb819cdae47fbbb310a99c2456c7332312719496b936a153dc7dee83e62c',\n", - ")\n", - "data = load(file_path)" - ] + "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/multiple.ort',\n known_hash='241bcb819cdae47fbbb310a99c2456c7332312719496b936a153dc7dee83e62c',\n)\ndata = load(file_path)" }, { "cell_type": "markdown", @@ -562,4 +555,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/docs/docs/tutorials/fitting/material_solvated.ipynb b/docs/docs/tutorials/fitting/material_solvated.ipynb index d393eb34..e64f7f7a 100644 --- a/docs/docs/tutorials/fitting/material_solvated.ipynb +++ b/docs/docs/tutorials/fitting/material_solvated.ipynb @@ -96,14 +96,7 @@ "id": "a95a39dd-d0eb-4029-9dc8-41e6e7918f66", "metadata": {}, "outputs": [], - "source": [ - "file_path = pooch.retrieve(\n", - " # URL to one of Pooch's test files\n", - " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/example.ort',\n", - " known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n", - ")\n", - "data = load(file_path)" - ] + "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n)\ndata = load(file_path)" }, { "cell_type": "markdown", @@ -372,4 +365,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/docs/docs/tutorials/fitting/monolayer.ipynb b/docs/docs/tutorials/fitting/monolayer.ipynb index e4a7b06d..a04bba8f 100644 --- a/docs/docs/tutorials/fitting/monolayer.ipynb +++ b/docs/docs/tutorials/fitting/monolayer.ipynb @@ -92,15 +92,7 @@ "id": "e392660e-6f02-4f0b-be86-4c8ea78883e0", "metadata": {}, "outputs": [], - "source": [ - "file_path = pooch.retrieve(\n", - " # URL to one of Pooch's test files\n", - " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/d70d2o.ort',\n", - " known_hash='3e4750536621be8eec493fa21a287e408d384f29cacb113b71d02690d99f0998',\n", - ")\n", - "data = load(file_path)\n", - "plot(data)" - ] + "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/d70d2o.ort',\n known_hash='3e4750536621be8eec493fa21a287e408d384f29cacb113b71d02690d99f0998',\n)\ndata = load(file_path)\nplot(data)" }, { "cell_type": "markdown", @@ -489,4 +481,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/docs/docs/tutorials/fitting/repeating.ipynb b/docs/docs/tutorials/fitting/repeating.ipynb index e062ac5d..156a1229 100644 --- a/docs/docs/tutorials/fitting/repeating.ipynb +++ b/docs/docs/tutorials/fitting/repeating.ipynb @@ -98,14 +98,7 @@ "id": "7121c7e9", "metadata": {}, "outputs": [], - "source": [ - "file_path = pooch.retrieve(\n", - " # URL to one of Pooch's test files\n", - " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/repeating_layers.ort',\n", - " known_hash='a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90',\n", - ")\n", - "data = load(file_path)" - ] + "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/repeating_layers.ort',\n known_hash='a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90',\n)\ndata = load(file_path)" }, { "cell_type": "markdown", @@ -346,4 +339,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/docs/docs/tutorials/fitting/simple_fitting.ipynb b/docs/docs/tutorials/fitting/simple_fitting.ipynb index 8eea570a..27d1e485 100644 --- a/docs/docs/tutorials/fitting/simple_fitting.ipynb +++ b/docs/docs/tutorials/fitting/simple_fitting.ipynb @@ -94,14 +94,7 @@ "id": "7d851064-605c-4f80-a510-197bcdbff2ea", "metadata": {}, "outputs": [], - "source": [ - "file_path = pooch.retrieve(\n", - " # URL to one of Pooch's test files\n", - " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/example.ort',\n", - " known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n", - ")\n", - "data = load(file_path)" - ] + "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n)\ndata = load(file_path)" }, { "cell_type": "markdown", @@ -645,4 +638,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/docs/docs/tutorials/simulation/resolution_functions.ipynb b/docs/docs/tutorials/simulation/resolution_functions.ipynb index 48a6893a..b5bd017e 100644 --- a/docs/docs/tutorials/simulation/resolution_functions.ipynb +++ b/docs/docs/tutorials/simulation/resolution_functions.ipynb @@ -101,27 +101,7 @@ "id": "609174e5-1371-412d-a29f-cb05bfe36df0", "metadata": {}, "outputs": [], - "source": [ - "file_path_0 = pooch.retrieve(\n", - " # URL to one of Pooch's test files\n", - " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort',\n", - " known_hash='f8a3e7007b83f0de4e2c761134e7d1c55027f0099528bd56f746b50349369f50',\n", - ")\n", - "file_path_1 = pooch.retrieve(\n", - " # URL to one of Pooch's test files\n", - " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort',\n", - " known_hash='9d81a512cbe45f923806ad307e476b27535614b2e08a2bf0f4559ab608a34f7a',\n", - ")\n", - "file_path_10 = pooch.retrieve(\n", - " # URL to one of Pooch's test files\n", - " url='https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort',\n", - " known_hash='991395c0b6a91bf60c12d234c645143dcac1cab929944fc4e452020d44b787ad',\n", - ")\n", - "dict_reference = {}\n", - "dict_reference['0'] = load(file_path_0)\n", - "dict_reference['1'] = load(file_path_1)\n", - "dict_reference['10'] = load(file_path_10)" - ] + "source": "file_path_0 = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-0.0.ort',\n known_hash='f8a3e7007b83f0de4e2c761134e7d1c55027f0099528bd56f746b50349369f50',\n)\nfile_path_1 = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-1.0.ort',\n known_hash='9d81a512cbe45f923806ad307e476b27535614b2e08a2bf0f4559ab608a34f7a',\n)\nfile_path_10 = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-10.0.ort',\n known_hash='991395c0b6a91bf60c12d234c645143dcac1cab929944fc4e452020d44b787ad',\n)\ndict_reference = {}\ndict_reference['0'] = load(file_path_0)\ndict_reference['1'] = load(file_path_1)\ndict_reference['10'] = load(file_path_10)" }, { "cell_type": "code", @@ -465,4 +445,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/pixi.lock b/pixi.lock index 0399edad..6b3a38bc 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,4 +1,8 @@ -version: 6 +version: 7 +platforms: +- name: linux-64 +- name: osx-arm64 +- name: win-64 environments: default: channels: @@ -9,36 +13,79 @@ environments: packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py313h18e8e13_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_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/linux-64/cffi-2.0.0-py313hf46b229_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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-py313h3dea7bd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_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/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h18e8e13_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.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-py313hf159716_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -54,7 +101,6 @@ 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/linux-64/icu-78.3-h33c6efd_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda @@ -80,42 +126,14 @@ environments: - 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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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-py313h3dea7bd_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_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.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -129,16 +147,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda @@ -146,18 +160,13 @@ environments: - 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.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -166,9 +175,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda @@ -181,177 +188,167 @@ environments: - 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.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/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-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/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-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/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-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/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-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/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2f/c8/005d1de3af80f54411703d1263a0b9d31276411ec9f273d9432c59b17299/arviz_plots-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.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/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.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/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.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/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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 - - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.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/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-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/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.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/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-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/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.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/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.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/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-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/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.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/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h7208f8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.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-py313hde1f3bb_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -367,7 +364,6 @@ 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/osx-arm64/icu-78.3-hef89b57_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda @@ -393,36 +389,14 @@ environments: - 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 - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py313h65a2061_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_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.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -436,18 +410,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda @@ -455,18 +423,13 @@ environments: - 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.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -475,9 +438,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda @@ -490,175 +451,214 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py313h7208f8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_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/osx-arm64/cffi-2.0.0-py313h224173a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py313h65a2061_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_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.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/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/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/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-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/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - - 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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-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/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-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/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-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/2f/c8/005d1de3af80f54411703d1263a0b9d31276411ec9f273d9432c59b17299/arviz_plots-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-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/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/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 - - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.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/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - 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/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-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/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-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/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.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/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/95/685e5a31f6c4cc24826d3a0df9e6a4bcf13d00f90d0c1f1060f13f5c16f8/python_bidi-0.6.9-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.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/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.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/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-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/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-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/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz - - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.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/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.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/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_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/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.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/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: ./ win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h2a31948_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.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-py313h3ebfc14_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -699,26 +699,14 @@ environments: - 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.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.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.1-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-py313hd650c13_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_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/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -731,26 +719,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/questionary-2.1.1-pyhd8ed1ab_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_1.conda @@ -758,8 +738,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -768,169 +746,201 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/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/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.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/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.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/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py313h2a31948_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_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/win-64/cffi-2.0.0-py313h5ea7bf4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_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/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.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.1-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-py313hd650c13_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/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/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.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/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-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/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-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/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-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/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-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/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2f/c8/005d1de3af80f54411703d1263a0b9d31276411ec9f273d9432c59b17299/arviz_plots-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-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/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.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/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.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/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-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/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.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/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-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/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-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/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.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/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.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/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-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/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-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/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/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/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/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.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 - - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.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/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-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/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-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/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-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/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/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 - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl - - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl py-311-env: channels: - url: https://conda.anaconda.org/nodefaults/ @@ -940,34 +950,78 @@ environments: packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py311h6b1f9c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py311h902ca64_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311h66f275b_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/linux-64/cffi-2.0.0-py311h03d9500_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py311h2005dd1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py311hc665b79_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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/libxcrypt-4.4.36-hd590300_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-py311h3778330_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py311h49ec1c0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py311haee01d2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py311h902ca64_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py311hdf6b40b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h57d2397_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py311h902ca64_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py311h49ec1c0_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/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py311h6b1f9c4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py311h902ca64_1.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-py311h66f275b_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py311h2005dd1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py311hc665b79_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -983,7 +1037,6 @@ 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/linux-64/icu-78.3-h33c6efd_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda @@ -1009,43 +1062,14 @@ environments: - 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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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/libxcrypt-4.4.36-hd590300_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-py311h3778330_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py311h49ec1c0_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.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -1059,34 +1083,25 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py311haee01d2_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py311h902ca64_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py311hdf6b40b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-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.11-8_cp311.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h57d2397_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py311h902ca64_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -1095,9 +1110,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py311h49ec1c0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda @@ -1110,175 +1123,163 @@ environments: - 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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - pypi: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-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/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-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/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-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/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/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-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/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-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/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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 - - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/44/0b/0615dbedb98f5b32a35a53290fbdc6e22306968109278d7e58df82d7a9f6/numba-0.65.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.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/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-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/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - 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/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-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/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-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/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.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/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-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/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.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/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.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/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h9408147_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py311h36d4fbb_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py311h71babbd_1.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-py311hdc60ec4_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py311hd10dc20_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py311hbb43c59_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py311h8948835_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -1294,7 +1295,6 @@ 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/osx-arm64/icu-78.3-hef89b57_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda @@ -1320,35 +1320,14 @@ environments: - 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 - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py311hc290fe0_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py311hc949640_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.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -1362,36 +1341,25 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py311he363849_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py311hf7c400d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py311h38a6bc0_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py311hce6e4fa_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py311h9049b8e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-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.11-8_cp311.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h745ac33_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py311h71babbd_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -1400,9 +1368,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py311hc949640_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda @@ -1415,173 +1381,209 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h9408147_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py311h36d4fbb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py311h71babbd_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py311hdc60ec4_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/osx-arm64/cffi-2.0.0-py311hd10dc20_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py311hbb43c59_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py311h8948835_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py311hc290fe0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py311hc949640_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py311he363849_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py311hf7c400d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py311h38a6bc0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py311hce6e4fa_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py311h9049b8e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h745ac33_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py311h71babbd_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py311hc949640_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.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + - pypi: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-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/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-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/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/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-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/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-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/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-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/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/0b/b9d1911cfefa61399821dfb37f486d83e0f42630a8d12f7194270c417002/llvmlite-0.47.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/9a/a9383ff12698d5306522122e7007d9fd58f903e00a7715506e3ff5be1678/python_bidi-0.6.9-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-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/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/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/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.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/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz - 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/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/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.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/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.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/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.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/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/96/b3/650500c2eab4534d98e9166f4298e0f3c69c742afdf24e6eabccd1f16ad8/numba-0.65.1-cp311-cp311-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/9a/a9383ff12698d5306522122e7007d9fd58f903e00a7715506e3ff5be1678/python_bidi-0.6.9-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/b3/650500c2eab4534d98e9166f4298e0f3c69c742afdf24e6eabccd1f16ad8/numba-0.65.1-cp311-cp311-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-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/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.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/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.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/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl - - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-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/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.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/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.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/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.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/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-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/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.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/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py311h71c1bcc_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py311hf51aa87_1.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-py311hc5da9e4_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py311h3485c13_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py311h2098ed6_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py311h5dfdfe8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -1622,25 +1624,14 @@ environments: - 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.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.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.53.1-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-py311h3f79411_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py311h3485c13_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/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -1653,25 +1644,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py311hf893f09_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py311hf51aa87_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py311hcd7b28f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-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.11-8_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hff25285_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_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_1.conda @@ -1679,8 +1662,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py311hf51aa87_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -1689,169 +1670,198 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py311h3485c13_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/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/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.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/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.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/argon2-cffi-bindings-25.1.0-py311h3485c13_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py311h71c1bcc_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py311hf51aa87_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py311hc5da9e4_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/win-64/cffi-2.0.0-py311h3485c13_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py311h2098ed6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py311h5dfdfe8_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/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.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.53.1-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-py311h3f79411_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py311h3485c13_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py311hf893f09_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py311hf51aa87_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py311hcd7b28f_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hff25285_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py311hf51aa87_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py311h3485c13_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/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/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.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + - pypi: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-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/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-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/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-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/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/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-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/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-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/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/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/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-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/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/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 - - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/9a/af61ec03b3116c161fd7a06b9e8a265729a8718458333e8ffbb06d9a3978/numba-0.65.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.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/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-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/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.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/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.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/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-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/db/09/a0ab6a246a7ede89e817d749a941df34f27a74bedf15551da51e86ae105e/pycairo-1.29.0-cp311-cp311-win_amd64.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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ac/a6/1ca2d290381911c17975ba00353671217bb9ed6c56f395871b274435b020/python_bidi-0.6.9-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.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/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/a6/1ca2d290381911c17975ba00353671217bb9ed6c56f395871b274435b020/python_bidi-0.6.9-cp311-cp311-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/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-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/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-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/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bd/9a/af61ec03b3116c161fd7a06b9e8a265729a8718458333e8ffbb06d9a3978/numba-0.65.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.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/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.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/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/09/a0ab6a246a7ede89e817d749a941df34f27a74bedf15551da51e86ae105e/pycairo-1.29.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl - - pypi: ./ + - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-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/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-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/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl py-313-env: channels: - url: https://conda.anaconda.org/nodefaults/ @@ -1861,36 +1871,79 @@ environments: packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py313h18e8e13_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_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/linux-64/cffi-2.0.0-py313hf46b229_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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-py313h3dea7bd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_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/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h18e8e13_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.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-py313hf159716_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -1906,7 +1959,6 @@ 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/linux-64/icu-78.3-h33c6efd_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda @@ -1932,42 +1984,14 @@ environments: - 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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/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.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.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.53.1-h0c1763c_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-py313h3dea7bd_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_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.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -1981,16 +2005,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda @@ -1998,18 +2018,13 @@ environments: - 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.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -2018,9 +2033,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda @@ -2033,177 +2046,167 @@ environments: - 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.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/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-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/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-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/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-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/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-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/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/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/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-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/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2f/c8/005d1de3af80f54411703d1263a0b9d31276411ec9f273d9432c59b17299/arviz_plots-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.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/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.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/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.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/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.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/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.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/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-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/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.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/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.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/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-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/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-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/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.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/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.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/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-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/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-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/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-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/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz - - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/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 - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: ./ osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h7208f8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.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-py313hde1f3bb_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -2219,7 +2222,6 @@ 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/osx-arm64/icu-78.3-hef89b57_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/importlib_resources-7.1.0-pyhd8ed1ab_0.conda @@ -2245,36 +2247,14 @@ environments: - 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 - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py313h65a2061_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_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.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -2288,18 +2268,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda @@ -2307,18 +2281,13 @@ environments: - 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.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -2327,9 +2296,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda @@ -2342,175 +2309,214 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py313h7208f8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_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/osx-arm64/cffi-2.0.0-py313h224173a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/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.5-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.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.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.53.1-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-py313h65a2061_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_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.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/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/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/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-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/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - - 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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-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/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-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/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-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/2f/c8/005d1de3af80f54411703d1263a0b9d31276411ec9f273d9432c59b17299/arviz_plots-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-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/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + - 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/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-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/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-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/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/95/685e5a31f6c4cc24826d3a0df9e6a4bcf13d00f90d0c1f1060f13f5c16f8/python_bidi-0.6.9-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.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/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.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/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-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/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-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/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-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/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz - - 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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.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/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.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/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_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/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.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/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: ./ win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h2a31948_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.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-py313h3ebfc14_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -2551,26 +2557,14 @@ environments: - 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.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.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.1-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-py313hd650c13_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_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/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -2583,26 +2577,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/questionary-2.1.1-pyhd8ed1ab_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_1.conda @@ -2610,8 +2596,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -2620,207 +2604,272 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/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/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.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/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.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/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py313h2a31948_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_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/win-64/cffi-2.0.0-py313h5ea7bf4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_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/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.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.1-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-py313hd650c13_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/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/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.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/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.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: . + - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-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/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-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/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-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/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/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/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-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/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-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 - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.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/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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-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/2f/c8/005d1de3af80f54411703d1263a0b9d31276411ec9f273d9432c59b17299/arviz_plots-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-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/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.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/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz + - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.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/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/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 + - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-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/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-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/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.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/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-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/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-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/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.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/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-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 - - pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz - - pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-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/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-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/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-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/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-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/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-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/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-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/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.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/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.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/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-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/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.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/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.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/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.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 - - pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl - - pypi: ./ - user: - channels: - - url: https://conda.anaconda.org/nodefaults/ - - url: https://conda.anaconda.org/conda-forge/ + - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-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/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-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/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl + user: + channels: + - url: https://conda.anaconda.org/nodefaults/ + - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py313h18e8e13_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_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/cffi-2.0.0-py313hf46b229_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_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/libgomp-15.2.0-he0feb66_18.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/libsodium-1.0.21-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_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/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_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/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py313h07c4f96_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h18e8e13_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.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-py313hf159716_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/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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -2861,33 +2910,14 @@ environments: - 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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_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/libgomp-15.2.0-he0feb66_18.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/libsodium-1.0.21-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_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/libzlib-1.3.2-h25fd6f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_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.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/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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -2901,16 +2931,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda @@ -2918,18 +2944,13 @@ environments: - 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.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 - - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -2938,9 +2959,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda @@ -2953,138 +2972,127 @@ environments: - 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.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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-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/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/33/eb/f9f1ded8e4db9638f9530c3782eb01f5ab04945f4cb9e597a51c203fa4c5/diffpy_pdffit2-1.6.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-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/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/2e/ba6b404a8a0e7c954cdf0fdd2b21723dc41def60f16b69b9b1cbb2e22d91/crysfml-0.6.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/64/2e/ba6b404a8a0e7c954cdf0fdd2b21723dc41def60f16b69b9b1cbb2e22d91/crysfml-0.6.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-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/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/33/eb/f9f1ded8e4db9638f9530c3782eb01f5ab04945f4cb9e597a51c203fa4c5/diffpy_pdffit2-1.6.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - 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/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-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/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h7208f8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.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-py313hde1f3bb_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/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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -3125,28 +3133,14 @@ environments: - 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 - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-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/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.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/libsodium-1.0.21-h1a92334_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_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.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/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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -3160,18 +3154,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda @@ -3179,18 +3167,13 @@ environments: - 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.13-8_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/noarch/questionary-2.1.1-pyhd8ed1ab_0.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_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 - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -3199,9 +3182,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda @@ -3214,136 +3195,163 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py313h7208f8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_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/cffi-2.0.0-py313h224173a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-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/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.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/libsodium-1.0.21-h1a92334_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_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/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_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.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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-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/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/35/8c/a917ef8741729ef8b3228f815240f4859717e600f9498359a6c411ed6992/crysfml-0.6.2-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/35/8c/a917ef8741729ef8b3228f815240f4859717e600f9498359a6c411ed6992/crysfml-0.6.2-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d3/80/722402571443031989e87a5861f26f2e897778768906dff1f998b42f235d/diffpy_pdffit2-1.6.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl - - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d3/80/722402571443031989e87a5861f26f2e897778768906dff1f998b42f235d/diffpy_pdffit2-1.6.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_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/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl - - 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/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-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/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - 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.4.0-py313h2a31948_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.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-py313h3ebfc14_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.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.4.22-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_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 - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda @@ -3384,25 +3392,14 @@ environments: - 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.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.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.1-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-py313hd650c13_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.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_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/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.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.2-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 @@ -3415,26 +3412,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.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.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/questionary-2.1.1-pyhd8ed1ab_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_1.conda @@ -3442,8 +3431,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda @@ -3452,128 +3439,157 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/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/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.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/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.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/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py313h2a31948_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_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/win-64/cffi-2.0.0-py313h5ea7bf4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_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/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.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.1-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-py313hd650c13_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_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/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/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/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.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/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-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/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/fe/b426215b6843ee70285521e2dd52f08a32096fe1d68e5b92fbdaffdf8ca3/diffpy_pdffit2-1.6.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/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/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/ed/01/e1da2f721c3a8feec311a16924c42fb28c37445528df0747f47dec5232d5/crysfml-0.6.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/15/fe/b426215b6843ee70285521e2dd52f08a32096fe1d68e5b92fbdaffdf8ca3/diffpy_pdffit2-1.6.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.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/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ed/01/e1da2f721c3a8feec311a16924c42fb28c37445528df0747f47dec5232d5/crysfml-0.6.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl - - 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/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-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/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda build_number: 20 @@ -3589,216 +3605,15 @@ packages: purls: [] size: 28948 timestamp: 1770939786096 -- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 - md5: aaa2a381ccc56eac91d63b6c1240312f +- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_2.conda + sha256: b81f852f13a1d148f6ad7e2a29ab375eb1558b73c9bfa38792d98ea7fb414cff + md5: 6e36e9d2b535c3fbe2e093108df26695 depends: - - cpython - - python-gil - license: MIT - license_family: MIT - purls: [] - size: 8191 - timestamp: 1744137672556 -- pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl - name: aiohappyeyeballs - version: 2.6.1 - sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: aiohttp - version: 3.13.5 - sha256: 3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc - requires_dist: - - aiohappyeyeballs>=2.5.0 - - aiosignal>=1.4.0 - - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' - - attrs>=17.3.0 - - frozenlist>=1.1.1 - - multidict>=4.5,<7.0 - - propcache>=0.2.0 - - yarl>=1.17.0,<2.0 - - aiodns>=3.3.0 ; extra == 'speedups' - - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' - - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' - - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl - name: aiohttp - version: 3.13.5 - sha256: e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c - requires_dist: - - aiohappyeyeballs>=2.5.0 - - aiosignal>=1.4.0 - - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' - - attrs>=17.3.0 - - frozenlist>=1.1.1 - - multidict>=4.5,<7.0 - - propcache>=0.2.0 - - yarl>=1.17.0,<2.0 - - aiodns>=3.3.0 ; extra == 'speedups' - - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' - - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' - - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl - name: aiohttp - version: 3.13.5 - sha256: a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9 - requires_dist: - - aiohappyeyeballs>=2.5.0 - - aiosignal>=1.4.0 - - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' - - attrs>=17.3.0 - - frozenlist>=1.1.1 - - multidict>=4.5,<7.0 - - propcache>=0.2.0 - - yarl>=1.17.0,<2.0 - - aiodns>=3.3.0 ; extra == 'speedups' - - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' - - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' - - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl - name: aiohttp - version: 3.13.5 - sha256: 69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83 - requires_dist: - - aiohappyeyeballs>=2.5.0 - - aiosignal>=1.4.0 - - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' - - attrs>=17.3.0 - - frozenlist>=1.1.1 - - multidict>=4.5,<7.0 - - propcache>=0.2.0 - - yarl>=1.17.0,<2.0 - - aiodns>=3.3.0 ; extra == 'speedups' - - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' - - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' - - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl - name: aiohttp - version: 3.13.5 - sha256: d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8 - requires_dist: - - aiohappyeyeballs>=2.5.0 - - aiosignal>=1.4.0 - - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' - - attrs>=17.3.0 - - frozenlist>=1.1.1 - - multidict>=4.5,<7.0 - - propcache>=0.2.0 - - yarl>=1.17.0,<2.0 - - aiodns>=3.3.0 ; extra == 'speedups' - - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' - - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' - - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: aiohttp - version: 3.13.5 - sha256: 3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665 - requires_dist: - - aiohappyeyeballs>=2.5.0 - - aiosignal>=1.4.0 - - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' - - attrs>=17.3.0 - - frozenlist>=1.1.1 - - multidict>=4.5,<7.0 - - propcache>=0.2.0 - - yarl>=1.17.0,<2.0 - - aiodns>=3.3.0 ; extra == 'speedups' - - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' - - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' - - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - name: aiosignal - version: 1.4.0 - sha256: 053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e - requires_dist: - - frozenlist>=1.1.0 - - typing-extensions>=4.2 ; python_full_version < '3.13' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl - name: annotated-doc - version: 0.0.4 - sha256: 571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320 - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 - md5: 2934f256a8acfe48f6ebb4fce6cde29c - depends: - - python >=3.9 - - typing-extensions >=4.0.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/annotated-types?source=hash-mapping - size: 18074 - timestamp: 1733247158254 -- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - sha256: f09aed24661cd45ba54a43772504f05c0698248734f9ae8cd289d314ac89707e - md5: af2df4b9108808da3dc76710fe50eae2 - depends: - - exceptiongroup >=1.0.2 - - idna >=2.8 - - python >=3.10 - - typing_extensions >=4.5 - - python - constrains: - - trio >=0.32.0 - - uvloop >=0.22.1 - - winloop >=0.2.3 - license: MIT - license_family: MIT - purls: - - pkg:pypi/anyio?source=compressed-mapping - size: 146764 - timestamp: 1774359453364 -- conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda - sha256: 8f032b140ea4159806e4969a68b4a3c0a7cab1ad936eb958a2b5ffe5335e19bf - md5: 54898d0f524c9dee622d44bbb081a8ab - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/appnope?source=hash-mapping - size: 10076 - timestamp: 1733332433806 -- pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - name: arabic-reshaper - version: 3.0.1 - sha256: 41c5adc2420f85758eada7e880251c4b6a2adbd83377bd27e5d4eba71f648bc7 - requires_dist: - - fonttools>=4.0 ; extra == 'with-fonttools' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda - sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad - md5: 8ac12aff0860280ee0cff7fa2cf63f3b - depends: - - argon2-cffi-bindings - - python >=3.9 - - typing-extensions - constrains: - - argon2_cffi ==999 - license: MIT - license_family: MIT - purls: - - pkg:pypi/argon2-cffi?source=hash-mapping - size: 18715 - timestamp: 1749017288144 -- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-25.1.0-py311h49ec1c0_2.conda - sha256: b81f852f13a1d148f6ad7e2a29ab375eb1558b73c9bfa38792d98ea7fb414cff - md5: 6e36e9d2b535c3fbe2e093108df26695 - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.0.1 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - __glibc >=2.17,<3.0.a0 + - cffi >=1.0.1 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 license: MIT license_family: MIT purls: @@ -3820,5132 +3635,5549 @@ packages: - pkg:pypi/argon2-cffi-bindings?source=hash-mapping size: 35943 timestamp: 1762509452935 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h9408147_2.conda - sha256: c15fc1aa6184185f1b31670a16b76d59f484d3efa25b467f28d59088cf0085c0 - md5: 501c2a606787bcd55b9ddee776208333 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py311h6b1f9c4_0.conda + sha256: a23717f9dc06924b988b92ba8dca0f4cb9154cac954457a40adb2f19d57896de + md5: aa8c3009fd8903bebdcb22fbcb4c0dea depends: - - __osx >=11.0 - - cffi >=1.0.1 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 34403 - timestamp: 1762509963182 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda - sha256: 05ea6fa7109235cfb4fc24526bae1fe82d88bbb5e697ab3945c313f5f041af5b - md5: e23e087109b2096db4cf9a3985bab329 + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 245341 + timestamp: 1777848716685 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py313h18e8e13_0.conda + sha256: f92ccaef33713bb66d563e8e829dcdb643e1de8c0d29246a999943c68bb8eb6e + md5: cfe95f3eab50bc3dbd4c3a03f2674bd8 depends: - - __osx >=11.0 - - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 33947 - timestamp: 1762510144907 -- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_2.conda - sha256: 787e9a5f0a5624fee2af4f58c823bd7933b8ca560e794dd9a821e990b31d2d49 - md5: 5fde0926a521a37d454a5e3162cb6e51 + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 241034 + timestamp: 1777848719564 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py311h902ca64_1.conda + sha256: 978ce415ca2868f8415ce54a9c33fe2d2c9efd6a78dc1a6fe79a4bc3d055fe66 + md5: 28b014780cae10a8435d012ef08c0202 depends: - - cffi >=1.0.1 - - python >=3.11,<3.12.0a0 + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT + constrains: + - __glibc >=2.17 + license: Apache-2.0 + license_family: APACHE purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 38502 - timestamp: 1762509668838 -- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda - sha256: 3f8a1affdfeb2be5289d709e365fc6e386d734773895215cf8cbc5100fa6af9a - md5: eabb4b677b54874d7d6ab775fdaa3d27 + - pkg:pypi/bcrypt?source=hash-mapping + size: 292775 + timestamp: 1762497725251 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.conda + sha256: cdf7496797af275f8bb5edd270aa06303dde623c7dd3a5941b0ce085d8f4cdc5 + md5: b59ec3796cba93d0db5f71e361176f27 depends: - - cffi >=1.0.1 - - python >=3.13,<3.14.0a0 + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT + constrains: + - __glibc >=2.17 + license: Apache-2.0 + license_family: APACHE purls: - - pkg:pypi/argon2-cffi-bindings?source=hash-mapping - size: 38779 - timestamp: 1762509796090 -- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda - sha256: 792da8131b1b53ff667bd6fc617ea9087b570305ccb9913deb36b8e12b3b5141 - md5: 85c4f19f377424eafc4ed7911b291642 + - pkg:pypi/bcrypt?source=hash-mapping + size: 292710 + timestamp: 1762497710166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311h66f275b_1.conda + sha256: c36eb061d9ead85f97644cfb740d485dba9b8823357f35c17851078e95e975c1 + md5: 86daecb8e4ed1042d5dc6efbe0152590 depends: - - python >=3.10 - - python-dateutil >=2.7.0 - - python-tzdata - - python - license: Apache-2.0 - license_family: APACHE + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 hb03c661_1 + license: MIT + license_family: MIT purls: - - pkg:pypi/arrow?source=hash-mapping - size: 113854 - timestamp: 1760831179410 -- pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl - name: asciichartpy - version: 1.5.25 - sha256: 33c417a3c8ef7d0a11b98eb9ea6dd9b2c1b17559e539b207a17d26d4302d0258 - requires_dist: - - setuptools - - flake8 ; extra == 'qa' -- pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl - name: ase - version: 3.28.0 - sha256: 0e24056302d7307b7247f90de281de15e3031c14cf400bedb1116c3b0d0e50b8 - requires_dist: - - numpy>=1.21.6 - - scipy>=1.8.1 - - matplotlib>=3.5.2 - - sphinx ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinxcontrib-video ; extra == 'docs' - - sphinx-gallery ; extra == 'docs' - - pillow ; extra == 'docs' - - pytest>=7.4.0 ; extra == 'test' - - pytest-xdist>=3.2.0 ; extra == 'test' - - spglib>=1.9 ; extra == 'spglib' - - mypy ; extra == 'lint' - - ruff ; extra == 'lint' - - types-docutils ; extra == 'lint' - - types-pymysql ; extra == 'lint' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl - name: asn1crypto - version: 1.5.1 - sha256: db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67 -- pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl - name: asteval - version: 1.0.8 - sha256: 6c64385c6ff859a474953c124987c7ee8354d781c76509b2c598741c4d1d28e9 - requires_dist: - - build ; extra == 'dev' - - twine ; extra == 'dev' - - sphinx ; extra == 'doc' - - pytest ; extra == 'test' - - pytest-cov ; extra == 'test' - - coverage ; extra == 'test' - - asteval[dev,doc,test] ; extra == 'all' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda - sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 - md5: 9673a61a297b00016442e022d689faa6 + - pkg:pypi/brotli?source=hash-mapping + size: 367573 + timestamp: 1764017405384 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + sha256: dadec2879492adede0a9af0191203f9b023f788c18efd45ecac676d424c458ae + md5: 6c4d3597cf43f3439a51b2b13e29a4ba depends: - - python >=3.10 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 constrains: - - astroid >=2,<5 - license: Apache-2.0 - license_family: Apache + - libbrotlicommon 1.2.0 hb03c661_1 + license: MIT + license_family: MIT purls: - - pkg:pypi/asttokens?source=hash-mapping - size: 28797 - timestamp: 1763410017955 -- conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda - sha256: ea8486637cfb89dc26dc9559921640cd1d5fd37e5e02c33d85c94572139f2efe - md5: b85e84cb64c762569cc1a760c2327e0a + - pkg:pypi/brotli?source=hash-mapping + size: 367721 + timestamp: 1764017371123 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 + md5: d2ffd7602c02f2b316fd921d39876885 depends: - - python >=3.10 - - typing_extensions >=4.0.0 - - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 260182 + timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e + md5: 920bb03579f15389b9e512095ad995b7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 207882 + timestamp: 1765214722852 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_1.conda + sha256: 3ad13377356c86d3a945ae30e9b8c8734300925ef81a3cb0a9db0d755afbe7bb + md5: 3912e4373de46adafd8f1e97e4bd166b + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 license: MIT license_family: MIT purls: - - pkg:pypi/async-lru?source=hash-mapping - size: 22949 - timestamp: 1773926359134 -- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab - md5: c6b0543676ecb1fb2d7643941fe375f2 + - pkg:pypi/cffi?source=hash-mapping + size: 303338 + timestamp: 1761202960110 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda + sha256: 2162a91819945c826c6ef5efe379e88b1df0fe9a387eeba23ddcf7ebeacd5bd6 + md5: d0616e7935acab407d1543b28c446f6f depends: - - python >=3.10 - - python + - __glibc >=2.17,<3.0.a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - pycparser + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 license: MIT license_family: MIT purls: - - pkg:pypi/attrs?source=hash-mapping - size: 64927 - timestamp: 1773935801332 -- pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl - name: autopep8 - version: 2.3.2 - sha256: ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128 - requires_dist: - - pycodestyle>=2.12.0 - - tomli ; python_full_version < '3.11' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda - sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770 - md5: f1976ce927373500cc19d3c0b2c85177 + - pkg:pypi/cffi?source=hash-mapping + size: 298357 + timestamp: 1761202966461 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py311h2005dd1_0.conda + sha256: 838cde68e70f4e0dc6458613316fa0e50eee5cf86cafb1cee4b7ca1a701a8436 + md5: 96f6e551c7ff30c95c1c8b4d2d1c5c9f depends: - - python >=3.10 - - python + - __glibc >=2.17,<3.0.a0 + - cffi >=2.0 + - libgcc >=14 + - openssl >=3.5.6,<4.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 constrains: - - pytz >=2015.7 - license: BSD-3-Clause + - __glibc >=2.17 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT license_family: BSD purls: - - pkg:pypi/babel?source=compressed-mapping - size: 7684321 - timestamp: 1772555330347 -- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py311h6b1f9c4_0.conda - sha256: a23717f9dc06924b988b92ba8dca0f4cb9154cac954457a40adb2f19d57896de - md5: aa8c3009fd8903bebdcb22fbcb4c0dea + - pkg:pypi/cryptography?source=compressed-mapping + size: 1913519 + timestamp: 1777966158377 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda + sha256: 7c10cdde9f46d4b990e9c90ffb72bfb672b56d2ecae243d95582c9a9fc417ebf + md5: 2310978ddde7f742d0b7a642d4894388 depends: - - python - __glibc >=2.17,<3.0.a0 + - cffi >=2.0 - libgcc >=14 - - python_abi 3.11.* *_cp311 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause AND MIT AND EPL-2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - __glibc >=2.17 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD purls: - - pkg:pypi/backports-zstd?source=compressed-mapping - size: 245341 - timestamp: 1777848716685 -- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py313h18e8e13_0.conda - sha256: f92ccaef33713bb66d563e8e829dcdb643e1de8c0d29246a999943c68bb8eb6e - md5: cfe95f3eab50bc3dbd4c3a03f2674bd8 + - pkg:pypi/cryptography?source=hash-mapping + size: 1916864 + timestamp: 1777966204947 +- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py311hc665b79_0.conda + sha256: e69be2be543c4d4898895d8aebe758bc683c5a1198583ad676f5719782a07131 + md5: 400e4667a12884216df869cad5fb004b depends: - python - libgcc >=14 + - libstdcxx >=14 - __glibc >=2.17,<3.0.a0 - - python_abi 3.13.* *_cp313 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause AND MIT AND EPL-2.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT purls: - - pkg:pypi/backports-zstd?source=compressed-mapping - size: 241034 - timestamp: 1777848719564 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py311h36d4fbb_0.conda - sha256: 2e6399a3663e7e20134a208cd09befc44ce1e9efe94d1d36d8911db04f186270 - md5: 3ee7e8ae532366221658b80baee36dc0 + - pkg:pypi/debugpy?source=hash-mapping + size: 2733654 + timestamp: 1769744984842 +- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda + sha256: 8d76d4eeb5a8e3c5666880b465593fdf4a44f47fbbe89ff5b8f9abbe43026326 + md5: e94dbbec2589f3b1d821f43a4bf2ab45 depends: - python - - __osx >=11.0 - - python_abi 3.11.* *_cp311 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause AND MIT AND EPL-2.0 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT purls: - - pkg:pypi/backports-zstd?source=compressed-mapping - size: 244743 - timestamp: 1777848723707 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py313h7208f8c_0.conda - sha256: 257b234caffc760e48c8d7e642d6d0d8bac4341ca19c7df098421358eff636a1 - md5: 84e922fe79295051f6925d7f8d71c98c - depends: - - python - - __osx >=11.0 - - python_abi 3.13.* *_cp313 - - zstd >=1.5.7,<1.6.0a0 - license: BSD-3-Clause AND MIT AND EPL-2.0 - purls: - - pkg:pypi/backports-zstd?source=compressed-mapping - size: 242188 - timestamp: 1777848729476 -- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py311h71c1bcc_0.conda - sha256: f551f6d7f1a62be18e4165772d300d21e0bd925956bcd9c2a605cabf9a2835d0 - md5: a0b8a2723108680aa6bb85fe3e413e76 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause AND MIT AND EPL-2.0 - purls: - - pkg:pypi/backports-zstd?source=hash-mapping - size: 243538 - timestamp: 1777848744191 -- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py313h2a31948_0.conda - sha256: b9c1465c03cb6ff79fb4f2feb21955b9b89783f868c900cad45dbcb8d81ab429 - md5: ef16917e1231df5c6f2ba245c8fe35e4 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zstd >=1.5.7,<1.6.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause AND MIT AND EPL-2.0 - purls: - - pkg:pypi/backports-zstd?source=compressed-mapping - size: 240632 - timestamp: 1777848740331 -- pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - name: backrefs - version: '7.0' - sha256: f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a - requires_dist: - - regex ; extra == 'extras' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl - name: backrefs - version: '7.0' - sha256: a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475 - requires_dist: - - regex ; extra == 'extras' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py311h902ca64_1.conda - sha256: 978ce415ca2868f8415ce54a9c33fe2d2c9efd6a78dc1a6fe79a4bc3d055fe66 - md5: 28b014780cae10a8435d012ef08c0202 + - pkg:pypi/debugpy?source=hash-mapping + size: 2872698 + timestamp: 1769744980407 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a + md5: c80d8a3b84358cb967fa81e7075fbc8a depends: - - python - - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - python_abi 3.11.* *_cp311 - constrains: - - __glibc >=2.17 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/bcrypt?source=hash-mapping - size: 292775 - timestamp: 1762497725251 -- conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.conda - sha256: cdf7496797af275f8bb5edd270aa06303dde623c7dd3a5941b0ce085d8f4cdc5 - md5: b59ec3796cba93d0db5f71e361176f27 - depends: - - python - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.13.* *_cp313 - constrains: - - __glibc >=2.17 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/bcrypt?source=hash-mapping - size: 292710 - timestamp: 1762497710166 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py311h71babbd_1.conda - sha256: 79594899d1f2472a6d95c3d489268bb9103ba6bab043d9faca379d989074aeb3 - md5: 00ad8eed3ec75b6ec425526968c72e61 - depends: - - python - - python 3.11.* *_cpython - - __osx >=11.0 - - python_abi 3.11.* *_cp311 - constrains: - - __osx >=11.0 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/bcrypt?source=hash-mapping - size: 268519 - timestamp: 1762497846273 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.conda - sha256: c8e3b95ad2665dc16ac37888fb91dd481fa759bad1fbf799451d12d6bfaec600 - md5: 4b14cba28eaf98170c2ddd7e900efa07 - depends: - - python - - __osx >=11.0 - - python 3.13.* *_cp313 - - python_abi 3.13.* *_cp313 - constrains: - - __osx >=11.0 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/bcrypt?source=hash-mapping - size: 267465 - timestamp: 1762497730829 -- conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py311hf51aa87_1.conda - sha256: b4cc5ee88f5d19381923519b1f6a1bbc73752f5b1528e4a1df0a3a802e497d13 - md5: fd25556009eae0faab9fc6286150d477 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/bcrypt?source=hash-mapping - size: 170642 - timestamp: 1762497737769 -- conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.conda - sha256: 21baf1316a8160fd3b83e7128803735b107f47c58ba5d5305a372fd6c679d42b - md5: 2a664b5cc93fbc9d7ca595b206a299f0 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/bcrypt?source=hash-mapping - size: 170702 - timestamp: 1762497739995 -- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda - sha256: bf1e71c3c0a5b024e44ff928225a0874fc3c3356ec1a0b6fe719108e6d1288f6 - md5: 5267bef8efea4127aacd1f4e1f149b6e - depends: - - python >=3.10 - - soupsieve >=1.2 - - typing-extensions + - libstdcxx >=14 license: MIT license_family: MIT - purls: - - pkg:pypi/beautifulsoup4?source=hash-mapping - size: 90399 - timestamp: 1764520638652 -- pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl - name: bidict - version: 0.23.1 - sha256: 5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5 - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda - sha256: f8ff1f98423674278964a46c93a1766f9e91960d44efd91c6c3ed56a33813f46 - md5: 7c5ebdc286220e8021bf55e6384acd67 - depends: - - python >=3.10 - - webencodings - - python - constrains: - - tinycss2 >=1.1.0,<1.5 - license: Apache-2.0 AND MIT - purls: - - pkg:pypi/bleach?source=hash-mapping - size: 142008 - timestamp: 1770719370680 -- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda - sha256: 7c07a865e5e4cca233cc4e0eb3f0f5ff6c90776461687b4fb0b1764133e1fd61 - md5: f11a319b9700b203aa14c295858782b6 + purls: [] + size: 12723451 + timestamp: 1773822285671 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 + md5: b38117a3c920364aff79f870c984b4a3 depends: - - bleach ==6.3.0 pyhcf101f3_1 - - tinycss2 - license: Apache-2.0 AND MIT + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-or-later purls: [] - size: 4409 - timestamp: 1770719370682 -- pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl - name: blinker - version: 1.9.0 - sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py311h66f275b_1.conda - sha256: c36eb061d9ead85f97644cfb740d485dba9b8823357f35c17851078e95e975c1 - md5: 86daecb8e4ed1042d5dc6efbe0152590 + size: 134088 + timestamp: 1754905959823 +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + sha256: 3e307628ca3527448dd1cb14ad7bb9d04d1d28c7d4c5f97ba196ae984571dd25 + md5: fb53fb07ce46a575c5d004bbc96032c2 depends: - __glibc >=2.17,<3.0.a0 + - keyutils >=1.6.3,<2.0a0 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 - libgcc >=14 - libstdcxx >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - libbrotlicommon 1.2.0 hb03c661_1 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 367573 - timestamp: 1764017405384 -- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda - sha256: dadec2879492adede0a9af0191203f9b023f788c18efd45ecac676d424c458ae - md5: 6c4d3597cf43f3439a51b2b13e29a4ba + purls: [] + size: 1386730 + timestamp: 1769769569681 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c + md5: 18335a698559cdbcd86150a48bf54ba6 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 constrains: - - libbrotlicommon 1.2.0 hb03c661_1 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 367721 - timestamp: 1764017371123 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py311hdc60ec4_1.conda - sha256: 617545ec0e97d35ed2ff7852f2581a20c0dda80b366d0c42a43706687f971ba8 - md5: 150cbf381febcf0a5e470a8d066e1bc0 - depends: - - __osx >=11.0 - - libcxx >=19 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - constrains: - - libbrotlicommon 1.2.0 hc919400_1 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 359588 - timestamp: 1764018467340 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_1.conda - sha256: 2e21dccccd68bedd483300f9ab87a425645f6776e6e578e10e0dd98c946e1be9 - md5: b03732afa9f4f54634d94eb920dfb308 + - binutils_impl_linux-64 2.45.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 728002 + timestamp: 1774197446916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + sha256: a7a4481a4d217a3eadea0ec489826a69070fcc3153f00443aa491ed21527d239 + md5: 6f7b4302263347698fd24565fbf11310 depends: - - __osx >=11.0 - - libcxx >=19 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 constrains: - - libbrotlicommon 1.2.0 hc919400_1 - license: MIT - license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 359568 - timestamp: 1764018359470 -- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py311hc5da9e4_1.conda - sha256: 1803c838946d79ef6485ae8c7dafc93e28722c5999b059a34118ef758387a4c9 - md5: b0c459f98ac5ea504a9d9df6242f7ee1 + - libabseil-static =20260107.1=cxx17* + - abseil-cpp =20260107.1 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 1384817 + timestamp: 1770863194876 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e + md5: 72c8fd1af66bd67bf580645b426513ed depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - libbrotlicommon 1.2.0 hfd05255_1 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 license: MIT license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 335333 - timestamp: 1764018370925 -- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_1.conda - sha256: 3558006cd6e836de8dff53cbe5f0b9959f96ea6a6776b4e14f1c524916dd956c - md5: 916a39a0261621b8c33e9db2366dd427 + purls: [] + size: 79965 + timestamp: 1764017188531 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b + md5: 366b40a69f0ad6072561c1d09301c886 depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - libbrotlicommon 1.2.0 hfd05255_1 + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 license: MIT license_family: MIT - purls: - - pkg:pypi/brotli?source=hash-mapping - size: 335605 - timestamp: 1764018132514 -- pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl - name: build - 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.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.10' -- pypi: https://files.pythonhosted.org/packages/7b/67/b1944235474aac3f0b0e1b232ce49547f9f9461ca4b943df1b88da5d3f1d/bumps-1.0.4-py3-none-any.whl - name: bumps - version: 1.0.4 - sha256: 78b8cfaf9fbcbf2fd77f6d4a2f8c906b0e03a794804ba6caf64d56d6f6cce4d4 - requires_dist: - - numpy - - scipy - - h5py - - dill - - cloudpickle - - matplotlib - - blinker - - aiohttp - - python-socketio - - plotly - - mpld3 - - msgpack - - uncertainties - - build ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - ruff ; extra == 'dev' - - wheel ; extra == 'dev' - - setuptools ; extra == 'dev' - - sphinx ; extra == 'dev' - - versioningit ; extra == 'dev' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 - md5: d2ffd7602c02f2b316fd921d39876885 + purls: [] + size: 34632 + timestamp: 1764017199083 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d + md5: 4ffbb341c8b616aa2494b6afb26a0c5f depends: - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 - libgcc >=14 - license: bzip2-1.0.6 - license_family: BSD + license: MIT + license_family: MIT purls: [] - size: 260182 - timestamp: 1771350215188 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - sha256: 540fe54be35fac0c17feefbdc3e29725cce05d7367ffedfaaa1bdda234b019df - md5: 620b85a3f45526a8bc4d23fd78fc22f0 + size: 298378 + timestamp: 1764017210931 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 + md5: c277e0a4d549b03ac1e9d6cbbe3d017b depends: - - __osx >=11.0 - license: bzip2-1.0.6 + - ncurses + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause license_family: BSD purls: [] - size: 124834 - timestamp: 1771350416561 -- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - sha256: 76dfb71df5e8d1c4eded2dbb5ba15bb8fb2e2b0fe42d94145d5eed4c75c35902 - md5: 4cb8e6b48f67de0b018719cdf1136306 + size: 134676 + timestamp: 1738479519902 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 + md5: 172bf1cd1ff8629f2b1179945ed45055 depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: bzip2-1.0.6 + - libgcc-ng >=12 + license: BSD-2-Clause license_family: BSD purls: [] - size: 56115 - timestamp: 1771350256444 -- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e - md5: 920bb03579f15389b9e512095ad995b7 + size: 112766 + timestamp: 1702146165126 +- 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.8.0.* license: MIT license_family: MIT purls: [] - size: 207882 - timestamp: 1765214722852 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - sha256: 2995f2aed4e53725e5efbc28199b46bf311c3cab2648fc4f10c2227d6d5fa196 - md5: bcb3cba70cf1eec964a03b4ba7775f01 + size: 77241 + timestamp: 1777846112704 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 + md5: a360c33a5abe61c07959e449fa1453eb depends: - - __osx >=11.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 180327 - timestamp: 1765215064054 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-h4c7d964_0.conda - sha256: 6f4ff81534c19e76acf52fcabf4a258088a932b8f1ac56e9a59e98f6051f8e46 - md5: 56fb2c6c73efc627b40c77d14caecfba + size: 58592 + timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda + sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 + md5: 0aa00f03f9e39fb9876085dee11a85d4 depends: - - __win - license: ISC + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==15.2.0=*_18 + - libgomp 15.2.0 he0feb66_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL purls: [] - size: 131388 - timestamp: 1776865633471 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-hbd8a1cb_0.conda - sha256: c9dbcc8039a52023660d6d1bbf87594a93dd69c6ac5a2a44323af2c92976728d - md5: e18ad67cf881dcadee8b8d9e2f8e5f73 + size: 1041788 + timestamp: 1771378212382 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 + md5: d5e96b1ed75ca01906b3d2469b4ce493 depends: - - __unix - license: ISC + - libgcc 15.2.0 he0feb66_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL purls: [] - 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 - md5: 9b347a7ec10940d3f7941ff6c460b551 + size: 27526 + timestamp: 1771378224552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 + md5: 239c5e9546c38a1e884d69effcf4c882 depends: - - cached_property >=1.5.2,<1.5.3.0a0 - license: BSD-3-Clause - license_family: BSD + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL purls: [] - size: 4134 - timestamp: 1615209571450 -- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 - sha256: 6dbf7a5070cc43d90a1e4c2ec0c541c69d8e30a0e25f50ce9f6e4a432e42c5d7 - md5: 576d629e47797577ab0f1b351297ef4a + size: 603262 + timestamp: 1771378117851 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d + md5: b88d90cad08e6bc8ad540cb310a761fb depends: - - python >=3.6 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/cached-property?source=hash-mapping - size: 11065 - timestamp: 1615209567874 -- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda - sha256: 989db6e5957c4b44fa600c68c681ec2f36a55e48f7c7f1c073d5e91caa8cd878 - md5: 929471569c93acefb30282a22060dcd5 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + size: 113478 + timestamp: 1775825492909 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 + md5: 2c21e66f50753a083cbe6b80f38268fa depends: - - python >=3.10 - license: ISC - purls: - - pkg:pypi/certifi?source=compressed-mapping - size: 135656 - timestamp: 1776866680878 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py311h03d9500_1.conda - sha256: 3ad13377356c86d3a945ae30e9b8c8734300925ef81a3cb0a9db0d755afbe7bb - md5: 3912e4373de46adafd8f1e97e4bd166b + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 92400 + timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + sha256: 663444d77a42f2265f54fb8b48c5450bfff4388d9c0f8253dd7855f0d993153f + md5: 2a45e7f8af083626f009645a6481f12d depends: - __glibc >=2.17,<3.0.a0 - - libffi >=3.5.2,<3.6.0a0 + - c-ares >=1.34.6,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 - libgcc >=14 - - pycparser - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 303338 - timestamp: 1761202960110 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda - sha256: 2162a91819945c826c6ef5efe379e88b1df0fe9a387eeba23ddcf7ebeacd5bd6 - md5: d0616e7935acab407d1543b28c446f6f + purls: [] + size: 663344 + timestamp: 1773854035739 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 + md5: d864d34357c3b65a4b731f78c0801dc4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-only + license_family: GPL + purls: [] + size: 33731 + timestamp: 1750274110928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda + sha256: 64e5c80cbce4680a2d25179949739a6def695d72c40ca28f010711764e372d97 + md5: 7af961ef4aa2c1136e11dd43ded245ab + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: ISC + purls: [] + size: 277661 + timestamp: 1772479381288 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda + sha256: 54cdcd3214313b62c2a8ee277e6f42150d9b748264c1b70d958bf735e420ef8d + md5: 7dc38adcbf71e6b38748e919e16e0dce + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + size: 954962 + timestamp: 1777986471789 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda + sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e + md5: 1b08cd684f34175e4514474793d44bcb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.2.0 he0feb66_18 + constrains: + - libstdcxx-ng ==15.2.0=*_18 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + size: 5852330 + timestamp: 1771378262446 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda + sha256: bc1b08c92626c91500fd9f26f2c797f3eb153b627d53e9c13cd167f1e12b2829 + md5: 38ffe67b78c9d4de527be8315e5ada2c + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 40297 + timestamp: 1775052476770 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b + md5: 0f03292cc56bf91a077a134ea8747118 depends: - __glibc >=2.17,<3.0.a0 - - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - - pycparser - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 license: MIT license_family: MIT - purls: - - pkg:pypi/cffi?source=hash-mapping - size: 298357 - timestamp: 1761202966461 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py311hd10dc20_1.conda - sha256: 1ffde698463d6e7ed571bdb85cb17bfc1d3a107c026d20047995512dcc2749ec - md5: 4d7f6780e36f18e7601811dddf3bbec5 + purls: [] + size: 895108 + timestamp: 1753948278280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - pycparser + - libgcc-ng >=12 + license: LGPL-2.1-or-later + purls: [] + size: 100393 + timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 63629 + timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py311h3778330_1.conda + sha256: 710e207b2e91308a34bcfe547c60ad86c1fa294827266ba18548c1fe1a9d8333 + md5: f9efdf9b0f3d0cc309d56af6edf2a6b0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/cffi?source=hash-mapping - size: 294848 - timestamp: 1761203196617 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_1.conda - sha256: 1fa69651f5e81c25d48ac42064db825ed1a3e53039629db69f86b952f5ce603c - md5: 050374657d1c7a4f2ea443c0d0cbd9a0 + - pkg:pypi/markupsafe?source=hash-mapping + size: 26756 + timestamp: 1772445078834 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda + sha256: 72ed7c0216541d65a17b171bf2eec4a3b81e9158d8ed48e59e1ecd3ae302d263 + md5: aeb9b9da79fd0258b3db091d1fefcd71 depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - pycparser + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/cffi?source=hash-mapping - size: 291376 - timestamp: 1761203583358 -- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py311h3485c13_1.conda - sha256: c9caca6098e3d92b1a269159b759d757518f2c477fbbb5949cb9fee28807c1f1 - md5: f02335db0282d5077df5bc84684f7ff9 + - pkg:pypi/markupsafe?source=hash-mapping + size: 26100 + timestamp: 1772445154165 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py311h49ec1c0_0.conda + sha256: 59123ba4af69c1e7ca6b1b8ffcc40f2aff9a635814495bda3f4f555b3b929165 + md5: 0347d2804701019ac005528df9eb502c depends: - - pycparser + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 - python >=3.11,<3.12.0a0 - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/cffi?source=hash-mapping - size: 297941 - timestamp: 1761203850323 -- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_1.conda - sha256: f867a11f42bb64a09b232e3decf10f8a8fe5194d7e3a216c6bac9f40483bd1c6 - md5: 55b44664f66a2caf584d72196aa98af9 + - pkg:pypi/msgspec?source=hash-mapping + size: 217556 + timestamp: 1776337480302 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_0.conda + sha256: a44d23085117672e4e19629ea3e6c53f516984322513c4bfdd052b1c7c6fc8ad + md5: 15c679f7133347d68058b688f3519cea depends: - - pycparser + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 - python >=3.13,<3.14.0a0 - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/cffi?source=hash-mapping - size: 292681 - timestamp: 1761203203673 -- pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - name: cfgv - version: 3.5.0 - sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl - name: chardet - version: 7.4.3 - sha256: 7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-win_amd64.whl - name: chardet - version: 7.4.3 - sha256: e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl - name: chardet - version: 7.4.3 - sha256: 4fbff1907925b0c5a1064cffb5e040cd5e338585c9c552625f30de6bc2f3107a - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-win_amd64.whl - name: chardet - version: 7.4.3 - sha256: ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: chardet - version: 7.4.3 - sha256: bfc134b70c846c21ead8e43ada3ae1a805fff732f6922f8abcf2ff27b8f6493d - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: chardet - version: 7.4.3 - sha256: 4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - sha256: 3f9483d62ce24ecd063f8a5a714448445dc8d9e201147c46699fc0033e824457 - md5: a9167b9571f3baa9d448faa2139d1089 + - pkg:pypi/msgspec?source=hash-mapping + size: 219767 + timestamp: 1776337423071 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 + md5: fc21868a1a5aacc937e7a18747acb8a5 depends: - - python >=3.10 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: X11 AND BSD-3-Clause + purls: [] + size: 918956 + timestamp: 1777422145199 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda + sha256: d1a673d1418d9e956b6e4e46c23e72a511c5c1d45dc5519c947457427036d5e2 + md5: baffb1570b3918c784d4490babc52fbf + depends: + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.28,<3.0.a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libuv >=1.51.0,<2.0a0 + - c-ares >=1.34.6,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - libsqlite >=3.52.0,<4.0a0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* + - zstd >=1.5.7,<1.6.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 license: MIT license_family: MIT - purls: - - pkg:pypi/charset-normalizer?source=compressed-mapping - size: 58872 - timestamp: 1775127203018 -- pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl - name: click - version: 8.3.3 - sha256: a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 - requires_dist: - - colorama ; sys_platform == 'win32' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - name: cloudpickle - version: 3.1.2 - sha256: 9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 - md5: 962b9857ee8e7018c22f2776ffa0b2d7 + purls: [] + size: 18829340 + timestamp: 1774514313036 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda + sha256: c0ef482280e38c71a08ad6d71448194b719630345b0c9c60744a2010e8a8e0cb + md5: da1b85b6a87e141f5140bb9924cecab0 depends: - - python >=3.9 + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3167099 + timestamp: 1775587756857 +- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py311haee01d2_0.conda + sha256: 8d9325af538a8f56013e42bbb91a4dc6935aece34476e20bafacf6007b571e86 + md5: 2ed8f6fe8b51d8e19f7621941f7bb95f + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.11.* *_cp311 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/colorama?source=hash-mapping - size: 27011 - timestamp: 1733218222191 -- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 - md5: 2da13f2b299d8e1995bafbbe9689a2f7 + - pkg:pypi/psutil?source=hash-mapping + size: 231786 + timestamp: 1769678156460 +- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + sha256: f19fd682d874689dfde20bf46d7ec1a28084af34583e0405685981363af47c91 + md5: 25fe6e02c2083497b3239e21b49d8093 depends: - - python >=3.9 - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/comm?source=hash-mapping - size: 14690 - timestamp: 1753453984907 -- pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl - name: contourpy - version: 1.3.3 - sha256: 23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381 - requires_dist: - - numpy>=1.25 - - furo ; extra == 'docs' - - sphinx>=7.2 ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - bokeh ; extra == 'bokeh' - - selenium ; extra == 'bokeh' - - contourpy[bokeh,docs] ; extra == 'mypy' - - bokeh ; extra == 'mypy' - - docutils-stubs ; extra == 'mypy' - - mypy==1.17.0 ; extra == 'mypy' - - types-pillow ; extra == 'mypy' - - contourpy[test-no-images] ; extra == 'test' - - matplotlib ; extra == 'test' - - pillow ; extra == 'test' - - pytest ; extra == 'test-no-images' - - pytest-cov ; extra == 'test-no-images' - - pytest-rerunfailures ; extra == 'test-no-images' - - pytest-xdist ; extra == 'test-no-images' - - wurlitzer ; extra == 'test-no-images' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - name: contourpy - version: 1.3.3 - sha256: 1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263 - requires_dist: - - numpy>=1.25 - - furo ; extra == 'docs' - - sphinx>=7.2 ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - bokeh ; extra == 'bokeh' - - selenium ; extra == 'bokeh' - - contourpy[bokeh,docs] ; extra == 'mypy' - - bokeh ; extra == 'mypy' - - docutils-stubs ; extra == 'mypy' - - mypy==1.17.0 ; extra == 'mypy' - - types-pillow ; extra == 'mypy' - - contourpy[test-no-images] ; extra == 'test' - - matplotlib ; extra == 'test' - - pillow ; extra == 'test' - - pytest ; extra == 'test-no-images' - - pytest-cov ; extra == 'test-no-images' - - pytest-rerunfailures ; extra == 'test-no-images' - - pytest-xdist ; extra == 'test-no-images' - - wurlitzer ; extra == 'test-no-images' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: contourpy - version: 1.3.3 - sha256: 4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9 - requires_dist: - - numpy>=1.25 - - furo ; extra == 'docs' - - sphinx>=7.2 ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - bokeh ; extra == 'bokeh' - - selenium ; extra == 'bokeh' - - contourpy[bokeh,docs] ; extra == 'mypy' - - bokeh ; extra == 'mypy' - - docutils-stubs ; extra == 'mypy' - - mypy==1.17.0 ; extra == 'mypy' - - types-pillow ; extra == 'mypy' - - contourpy[test-no-images] ; extra == 'test' - - matplotlib ; extra == 'test' - - pillow ; extra == 'test' - - pytest ; extra == 'test-no-images' - - pytest-cov ; extra == 'test-no-images' - - pytest-rerunfailures ; extra == 'test-no-images' - - pytest-xdist ; extra == 'test-no-images' - - wurlitzer ; extra == 'test-no-images' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: contourpy - version: 1.3.3 - sha256: 51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db - requires_dist: - - numpy>=1.25 - - furo ; extra == 'docs' - - sphinx>=7.2 ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - bokeh ; extra == 'bokeh' - - selenium ; extra == 'bokeh' - - contourpy[bokeh,docs] ; extra == 'mypy' - - bokeh ; extra == 'mypy' - - docutils-stubs ; extra == 'mypy' - - mypy==1.17.0 ; extra == 'mypy' - - types-pillow ; extra == 'mypy' - - contourpy[test-no-images] ; extra == 'test' - - matplotlib ; extra == 'test' - - pillow ; extra == 'test' - - pytest ; extra == 'test-no-images' - - pytest-cov ; extra == 'test-no-images' - - pytest-rerunfailures ; extra == 'test-no-images' - - pytest-xdist ; extra == 'test-no-images' - - wurlitzer ; extra == 'test-no-images' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl - name: contourpy - version: 1.3.3 - sha256: d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1 - requires_dist: - - numpy>=1.25 - - furo ; extra == 'docs' - - sphinx>=7.2 ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - bokeh ; extra == 'bokeh' - - selenium ; extra == 'bokeh' - - contourpy[bokeh,docs] ; extra == 'mypy' - - bokeh ; extra == 'mypy' - - docutils-stubs ; extra == 'mypy' - - mypy==1.17.0 ; extra == 'mypy' - - types-pillow ; extra == 'mypy' - - contourpy[test-no-images] ; extra == 'test' - - matplotlib ; extra == 'test' - - pillow ; extra == 'test' - - pytest ; extra == 'test-no-images' - - pytest-cov ; extra == 'test-no-images' - - pytest-rerunfailures ; extra == 'test-no-images' - - pytest-xdist ; extra == 'test-no-images' - - wurlitzer ; extra == 'test-no-images' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl - name: contourpy - version: 1.3.3 - sha256: 3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42 - requires_dist: - - numpy>=1.25 - - furo ; extra == 'docs' - - sphinx>=7.2 ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - bokeh ; extra == 'bokeh' - - selenium ; extra == 'bokeh' - - contourpy[bokeh,docs] ; extra == 'mypy' - - bokeh ; extra == 'mypy' - - docutils-stubs ; extra == 'mypy' - - mypy==1.17.0 ; extra == 'mypy' - - types-pillow ; extra == 'mypy' - - contourpy[test-no-images] ; extra == 'test' - - matplotlib ; extra == 'test' - - pillow ; extra == 'test' - - pytest ; extra == 'test-no-images' - - pytest-cov ; extra == 'test-no-images' - - pytest-rerunfailures ; extra == 'test-no-images' - - pytest-xdist ; extra == 'test-no-images' - - wurlitzer ; extra == 'test-no-images' - requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda - sha256: 2ceb1f7edb5e5943f7af69e4d6b3339b2e2580e7c9eef76efed4d18d05ae49db - md5: 12baf87bfa62ac066dbe2526f5d89de1 + - pkg:pypi/psutil?source=hash-mapping + size: 228663 + timestamp: 1769678153829 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py311h902ca64_0.conda + sha256: ac3faa31154a85f4b1a4618957b4a06623ff2c6a85f8eac20efe9dbac5757d91 + md5: b6cfa054aec29586d383f9e666bd0e9a depends: - - python >=3.10 - - colorama >=0.4.6 - - dunamai >=1.7.0 - - funcy >=1.17 - - jinja2 >=3.1.5 - - jinja2-ansible-filters >=1.3.1 - - packaging >=23.0 - - pathspec >=0.9.0 - - plumbum >=1.6.9 - - prompt-toolkit <3.0.52 - - pydantic >=2.4.2 - - pygments >=2.7.1 - - pyyaml >=5.3.1 - - questionary >=1.8.1 - - eval-type-backport >=0.1.3,<0.3.0 - - platformdirs >=4.3.6 - - typing_extensions >=4.0.0,<5.0.0 - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/copier?source=hash-mapping - size: 58609 - timestamp: 1777562504238 -- pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl - name: coverage - version: 7.13.5 - sha256: 941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3 - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl - name: coverage - version: 7.13.5 - sha256: 145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587 - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl - name: coverage - version: 7.13.5 - sha256: 631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - name: coverage - version: 7.13.5 - sha256: ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - name: coverage - version: 7.13.5 - sha256: 78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3 - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl - name: coverage - version: 7.13.5 - sha256: 258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9 - requires_dist: - - tomli ; python_full_version <= '3.11' and extra == 'toml' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda - noarch: generic - sha256: 836b92c209d4b6b9fb28bd51bd788b22a0c5492ae95eec2724e65a15ed4ab2e1 - md5: 3a8a8b87e72f95b54689fb588e154ec9 - depends: - - python >=3.13,<3.14.0a0 - - python_abi * *_cp313 - license: Python-2.0 - purls: [] - size: 48530 - timestamp: 1775613723457 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py311h2005dd1_0.conda - sha256: 838cde68e70f4e0dc6458613316fa0e50eee5cf86cafb1cee4b7ca1a701a8436 - md5: 96f6e551c7ff30c95c1c8b4d2d1c5c9f - depends: + - typing-extensions >=4.6.0,!=4.7.0 - __glibc >=2.17,<3.0.a0 - - cffi >=2.0 - libgcc >=14 - - openssl >=3.5.6,<4.0a0 - - python >=3.11,<3.12.0a0 - python_abi 3.11.* *_cp311 constrains: - __glibc >=2.17 - license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT - license_family: BSD + license: MIT + license_family: MIT purls: - - pkg:pypi/cryptography?source=compressed-mapping - size: 1913519 - timestamp: 1777966158377 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-48.0.0-py313heb322e3_0.conda - sha256: 7c10cdde9f46d4b990e9c90ffb72bfb672b56d2ecae243d95582c9a9fc417ebf - md5: 2310978ddde7f742d0b7a642d4894388 + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1884122 + timestamp: 1778084220486 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + sha256: d6705962afa2655cc22edcd075ce1f7e67c4407c005137d6d63c0dc5eaa76f47 + md5: ef0f9efec6ec28b17e22f787c7672c67 depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 - __glibc >=2.17,<3.0.a0 - - cffi >=2.0 - libgcc >=14 - - openssl >=3.5.6,<4.0a0 - - python >=3.13,<3.14.0a0 - python_abi 3.13.* *_cp313 constrains: - __glibc >=2.17 - license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT - license_family: BSD + license: MIT + license_family: MIT purls: - - pkg:pypi/cryptography?source=hash-mapping - size: 1916864 - timestamp: 1777966204947 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py311hbb43c59_0.conda - sha256: 6e59412439c8b51ee5f05b3c50b05b43991fa544a895226168fab63be9ba28f9 - md5: cbc7fb4eb2049a4a6cd2c452afd2f7bd + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1893749 + timestamp: 1778084222642 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py311hdf6b40b_1.conda + sha256: 4b89f502dc58df0abb27d4677ca4b0ef5230f5c28e24b80eef3dc0c9c968e225 + md5: fb7e00fed4fddee97e6cf0bd3ae9a27e depends: - - __osx >=11.0 - - cffi >=2.0 - - openssl >=3.5.6,<4.0a0 + - __glibc >=2.17,<3.0.a0 + - cffi >=1.4.1 + - libgcc >=14 + - libsodium >=1.0.21,<1.0.22.0a0 - python >=3.11,<3.12.0a0 - python_abi 3.11.* *_cp311 - constrains: - - __osx >=11.0 - license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT - license_family: BSD + - six + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/cryptography?source=hash-mapping - size: 1855763 - timestamp: 1777966249758 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda - sha256: 2adca86f420558eaaad8b8a7ecffadc4a23f980c02716d0fd5bee3f51879e76b - md5: 730fd8d2542ece52b71bc4fbb80ceb10 + - pkg:pypi/pynacl?source=hash-mapping + size: 1151915 + timestamp: 1772171237221 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda + sha256: 51e80a7bef95025ad47a92acb69ee0e78f01e107655c86fe76abcde2ac688166 + md5: c4426edfc5514a2c9be6871557bce52b depends: - - __osx >=11.0 - - cffi >=2.0 - - openssl >=3.5.6,<4.0a0 + - __glibc >=2.17,<3.0.a0 + - cffi >=1.4.1 + - libgcc >=14 + - libsodium >=1.0.21,<1.0.22.0a0 - python >=3.13,<3.14.0a0 - python_abi 3.13.* *_cp313 - constrains: - - __osx >=11.0 - license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT - license_family: BSD + - six + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/cryptography?source=hash-mapping - size: 1854514 - timestamp: 1777966313532 -- conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py311h2098ed6_0.conda - sha256: a6fca5c54f019724d08fc3d9e54c60ca8c0c8aa8b54571fec3e713bbb64a55c3 - md5: 27def055167d830e4ce6c6821eaee591 + - pkg:pypi/pynacl?source=hash-mapping + size: 1191901 + timestamp: 1772171244923 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda + sha256: bf6a32c69889d38482436a786bea32276756cedf0e9805cc856ffd088e8d00f0 + md5: a5ebcefec0c12a333bcd6d7bf3bddc1f depends: - - cffi >=2.0 - - openssl >=3.5.6,<4.0a0 - - python >=3.11,<3.12.0a0 + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.2,<6.0a0 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libuuid >=2.41.3,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT - license_family: BSD - purls: - - pkg:pypi/cryptography?source=hash-mapping - size: 1645132 - timestamp: 1777966325193 -- conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda - sha256: 1fbf66094059379d72f06d3cbb9df5277352a95d25a68d55d1dd60fa6acca5c6 - md5: c216777102e0b49ed307e181df3903d2 + license: Python-2.0 + purls: [] + size: 30949404 + timestamp: 1772730362552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda + build_number: 100 + sha256: 7f77eb57648f545c1f58e10035d0d9d66b0a0efb7c4b58d3ed89ec7269afdde1 + md5: 05051be49267378d2fcd12931e319ac3 depends: - - cffi >=2.0 - - openssl >=3.5.6,<4.0a0 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT - license_family: BSD - purls: - - pkg:pypi/cryptography?source=hash-mapping - size: 1646298 - timestamp: 1777966340031 -- pypi: https://files.pythonhosted.org/packages/35/8c/a917ef8741729ef8b3228f815240f4859717e600f9498359a6c411ed6992/crysfml-0.6.2-cp313-cp313-macosx_14_0_arm64.whl - name: crysfml - version: 0.6.2 - sha256: 0010858646240f6c64e2711c33ccfffd94ee6602859babd1186785e28d6c5c11 - requires_dist: - - numpy - requires_python: '>=3.11,<3.15' -- pypi: https://files.pythonhosted.org/packages/64/2e/ba6b404a8a0e7c954cdf0fdd2b21723dc41def60f16b69b9b1cbb2e22d91/crysfml-0.6.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: crysfml - version: 0.6.2 - sha256: 734e2180b2c427e77cc9cf9cee4a3651ad563adab28dfe79051f004489f8546c - requires_dist: - - numpy - requires_python: '>=3.11,<3.15' -- pypi: https://files.pythonhosted.org/packages/ed/01/e1da2f721c3a8feec311a16924c42fb28c37445528df0747f47dec5232d5/crysfml-0.6.2-cp313-cp313-win_amd64.whl - name: crysfml - version: 0.6.2 - sha256: b32ddbf4010ce61a535182d58c12c410ed4a0f5de2259e7ef605e7fbcce40be3 - requires_dist: - - numpy - requires_python: '>=3.11,<3.15' -- pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl - name: cryspy - version: 0.11.0 - sha256: 0b650655a0fbdc3cfcb28826c2ab9fbc5f491e32e1ea9a47d9b75976cd43f26f - requires_dist: - - numpy - - scipy - - pycifstar - - matplotlib -- pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl - name: cssselect2 - version: 0.9.0 - sha256: 6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563 - requires_dist: - - tinycss2 - - webencodings - - sphinx ; extra == 'doc' - - furo ; extra == 'doc' - - pytest ; extra == 'test' - - ruff ; extra == 'test' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl - name: cyclebane - version: 24.10.0 - sha256: 902dd318667e4a222afc270cc5bc72c67d5d6047d2e0e1c36018885fb80f5e5d - requires_dist: - - networkx - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - name: cycler - version: 0.12.1 - sha256: 85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 - requires_dist: - - ipython ; extra == 'docs' - - matplotlib ; extra == 'docs' - - numpydoc ; extra == 'docs' - - sphinx ; extra == 'docs' - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-xdist ; extra == 'tests' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl - name: darkdetect - version: 0.8.0 - sha256: a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85 - requires_dist: - - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'macos-listener' - requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl - name: dask - version: 2026.3.0 - sha256: be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d - requires_dist: - - click>=8.1 - - cloudpickle>=3.0.0 - - fsspec>=2021.9.0 - - packaging>=20.0 - - partd>=1.4.0 - - pyyaml>=5.3.1 - - toolz>=0.12.0 - - importlib-metadata>=4.13.0 ; python_full_version < '3.12' - - numpy>=1.24 ; extra == 'array' - - dask[array] ; extra == 'dataframe' - - pandas>=2.0 ; extra == 'dataframe' - - pyarrow>=16.0 ; extra == 'dataframe' - - distributed>=2026.3.0,<2026.3.1 ; extra == 'distributed' - - bokeh>=3.1.0 ; extra == 'diagnostics' - - jinja2>=2.10.3 ; extra == 'diagnostics' - - dask[array,dataframe,diagnostics,distributed] ; extra == 'complete' - - pyarrow>=16.0 ; extra == 'complete' - - lz4>=4.3.2 ; extra == 'complete' - - pandas[test] ; extra == 'test' - - pytest ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-mock ; extra == 'test' - - pytest-rerunfailures ; extra == 'test' - - pytest-timeout ; extra == 'test' - - pytest-xdist ; extra == 'test' - - pre-commit ; extra == 'test' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py311hc665b79_0.conda - sha256: e69be2be543c4d4898895d8aebe758bc683c5a1198583ad676f5719782a07131 - md5: 400e4667a12884216df869cad5fb004b - depends: - - python + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - - libstdcxx >=14 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libuuid >=2.42,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 37358322 + timestamp: 1775614712638 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda + sha256: c9a6cd2c290d7c3d2b30ea34a0ccda30f770e8ddb2937871f2c404faf60d0050 + md5: a24add9a3bababee946f3bc1c829acfe + depends: - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 - python_abi 3.11.* *_cp311 + - yaml >=0.2.5,<0.3.0a0 license: MIT license_family: MIT purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2733654 - timestamp: 1769744984842 -- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.20-py313h5d5ffb9_0.conda - sha256: 8d76d4eeb5a8e3c5666880b465593fdf4a44f47fbbe89ff5b8f9abbe43026326 - md5: e94dbbec2589f3b1d821f43a4bf2ab45 + - pkg:pypi/pyyaml?source=hash-mapping + size: 206190 + timestamp: 1770223702917 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda + sha256: ef7df29b38ef04ec67a8888a4aa039973eaa377e8c4b59a7be0a1c50cd7e4ac6 + md5: f256753e840c3cd3766488c9437a8f8b depends: - - python - - libstdcxx >=14 - - libgcc >=14 - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 - python_abi 3.13.* *_cp313 + - yaml >=0.2.5,<0.3.0a0 license: MIT license_family: MIT purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2872698 - timestamp: 1769744980407 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py311h8948835_0.conda - sha256: 093b015e9abf27fb4d3b4f7e52417d35cd69a99fab8b95ec5c6c3983275c46ba - md5: 150c921424bc9f08c0378f8a6ae58d05 + - pkg:pypi/pyyaml?source=compressed-mapping + size: 201616 + timestamp: 1770223543730 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h57d2397_2.conda + sha256: 4137226663b353919396f8cf239971cfa54107cd077cf3b65e979ba3e1f8a037 + md5: 759edfe34f07c5c4565b6c5ec0c7fb17 depends: - python - - __osx >=11.0 - - libcxx >=19 - - python 3.11.* *_cpython + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - zeromq >=4.3.5,<4.4.0a0 - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2668163 - timestamp: 1769745020016 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda - sha256: 372464b345220758769f49e76d125933008abec7048df4077a851adcc79da1e8 - md5: b3a832c19cfa5dfcce7575750ef693ed + - pkg:pypi/pyzmq?source=hash-mapping + size: 383627 + timestamp: 1771716979818 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda + noarch: python + sha256: be66c1f85c3b48137200d62c12d918f4f8ad329423daef04fed292818efd3c28 + md5: 082985717303dab433c976986c674b35 depends: - python - - libcxx >=19 - - python 3.13.* *_cp313 - - __osx >=11.0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - zeromq >=4.3.5,<4.4.0a0 + - _python_abi3_support 1.* + - cpython >=3.12 + license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 2761021 - timestamp: 1769744996428 -- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py311h5dfdfe8_0.conda - sha256: 661e5c582b1f853a46a78d4bb6e55f2bfdac66e68d015e111f1580a11c28abbf - md5: 683be2cd10e80a367790b3083ce529b7 + - pkg:pypi/pyzmq?source=hash-mapping + size: 211567 + timestamp: 1771716961404 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 + md5: d7d95fc8287ea7bf33e0e7116d2b95ec + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 345073 + timestamp: 1765813471974 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py311h902ca64_0.conda + sha256: bf5e6197fb08b8c6e421ca0126e966b7c3ae62b84d7b98523356b4fd5ae6f8ae + md5: 3893f7b40738f9fe87510cb4468cdda5 depends: - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 - python_abi 3.11.* *_cp311 + constrains: + - __glibc >=2.17 license: MIT license_family: MIT purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 3940002 - timestamp: 1769745017274 -- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda - sha256: f6fe9cbd9e3d3c09f173eeb43676a58bc6169e97da0d190e0201e40828a3a62b - md5: 75eb3091b05924429a3a8d2a9bbdfac2 + - pkg:pypi/rpds-py?source=hash-mapping + size: 383153 + timestamp: 1764543197251 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda + sha256: 076d26e51c62c8ecfca6eb19e3c1febdd7632df1990a7aa53da5df5e54482b1c + md5: 779e3307a0299518713765b83a36f4b1 depends: - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 - python_abi 3.13.* *_cp313 - license: MIT + constrains: + - __glibc >=2.17 + license: MIT license_family: MIT purls: - - pkg:pypi/debugpy?source=hash-mapping - size: 4003589 - timestamp: 1769745018248 -- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 - md5: 9ce473d1d1be1cc3810856a48b3fab32 + - pkg:pypi/rpds-py?source=hash-mapping + size: 383230 + timestamp: 1764543223529 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda + noarch: python + sha256: 98c771464ed93a2733bae460c39cfa9640feb20972d2e651b44e85db296942eb + md5: 3c8f229055ad244fa3a97c6c45b77099 depends: - - python >=3.9 - license: BSD-2-Clause + - python + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 9266480 + timestamp: 1778119386343 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac + md5: cffd3bdd58090148f4cfcd831f4b26ab + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 + license: TCL license_family: BSD + purls: [] + size: 3301196 + timestamp: 1769460227866 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py311h49ec1c0_0.conda + sha256: cd8bc08ca661291cfbb05581b79f7e6a6971a072a54a335abcea5460c1230880 + md5: 73b44a114241e564deb5846e7394bf19 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/decorator?source=hash-mapping - size: 14129 - timestamp: 1740385067843 -- conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 - sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be - md5: 961b3a227b437d82ad7054484cfa71b2 + - pkg:pypi/tornado?source=compressed-mapping + size: 876817 + timestamp: 1774358035290 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda + sha256: 9e8497e1ecca77d03c6be2d3b5f901dfe0ab99686af4fb94ab418b7d449ac547 + md5: 6c0b0ae017b5bfd9c8d718217efd8f14 depends: - - python >=3.6 - license: PSF-2.0 - license_family: PSF + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/defusedxml?source=hash-mapping - size: 24062 - timestamp: 1615232388757 -- pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl - name: dfo-ls - version: 1.6.5 - sha256: d147d42e471e240f9abf8bc38351a88f555ea6a8fcfd83119bbbf93c36f75ab2 - requires_dist: - - setuptools - - numpy - - scipy>=1.11 - - pandas - - pytest ; extra == 'dev' - - sphinx ; extra == 'dev' - - sphinx-rtd-theme ; extra == 'dev' - - trustregion>=1.1 ; extra == 'trustregion' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/15/fe/b426215b6843ee70285521e2dd52f08a32096fe1d68e5b92fbdaffdf8ca3/diffpy_pdffit2-1.6.0-cp313-cp313-win_amd64.whl - name: diffpy-pdffit2 - version: 1.6.0 - sha256: f1bba407eac8ff8ef1264ad5a1d3dff840e2d0a5f71e9fb0c0b9b06d5a8eb139 - requires_dist: - - diffpy-structure - requires_python: '>=3.12,<3.15' -- pypi: https://files.pythonhosted.org/packages/33/eb/f9f1ded8e4db9638f9530c3782eb01f5ab04945f4cb9e597a51c203fa4c5/diffpy_pdffit2-1.6.0.tar.gz - name: diffpy-pdffit2 - version: 1.6.0 - sha256: 11a65466f8790f5ac7ae45f2f3fc0d5d116d156d274bcfc079df653123d080e2 - requires_dist: - - diffpy-structure - requires_python: '>=3.12,<3.15' -- pypi: https://files.pythonhosted.org/packages/d3/80/722402571443031989e87a5861f26f2e897778768906dff1f998b42f235d/diffpy_pdffit2-1.6.0-cp313-cp313-macosx_11_0_arm64.whl - name: diffpy-pdffit2 - version: 1.6.0 - sha256: 8837088196ddabd688deb77740c3f03ddc33f0df6964a8e3507701c32b67b8b2 - requires_dist: - - diffpy-structure - requires_python: '>=3.12,<3.15' -- pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl - name: diffpy-structure - version: 3.4.0 - sha256: bd0f06a96635d80316f51ebc0a08003bdeb2cb48c9bb18cbed1455ac60645e48 - requires_dist: - - numpy - - pycifrw - - diffpy-utils - requires_python: '>=3.12,<3.15' -- pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl - name: diffpy-utils - version: 3.7.2 - sha256: 6100600736791a8e4638e3dd476704f4dabe3cab75bcb5c60c83c16a2032519a - requires_dist: - - numpy - - xraydb - - scipy - requires_python: '>=3.10,<3.15' -- pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - name: dill - version: 0.4.1 - sha256: 1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d - requires_dist: - - objgraph>=1.7.2 ; extra == 'graph' - - gprof2dot>=2022.7.29 ; extra == 'profile' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - name: distlib - version: 0.4.0 - sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 -- pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl - name: dnspython - version: 2.8.0 - sha256: 01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af - requires_dist: - - black>=25.1.0 ; extra == 'dev' - - coverage>=7.0 ; extra == 'dev' - - flake8>=7 ; extra == 'dev' - - hypercorn>=0.17.0 ; extra == 'dev' - - mypy>=1.17 ; extra == 'dev' - - pylint>=3 ; extra == 'dev' - - pytest-cov>=6.2.0 ; extra == 'dev' - - pytest>=8.4 ; extra == 'dev' - - quart-trio>=0.12.0 ; extra == 'dev' - - sphinx-rtd-theme>=3.0.0 ; extra == 'dev' - - sphinx>=8.2.0 ; extra == 'dev' - - twine>=6.1.0 ; extra == 'dev' - - wheel>=0.45.0 ; extra == 'dev' - - cryptography>=45 ; extra == 'dnssec' - - h2>=4.2.0 ; extra == 'doh' - - httpcore>=1.0.0 ; extra == 'doh' - - httpx>=0.28.0 ; extra == 'doh' - - aioquic>=1.2.0 ; extra == 'doq' - - idna>=3.10 ; extra == 'idna' - - trio>=0.30 ; extra == 'trio' - - wmi>=1.5.1 ; sys_platform == 'win32' and extra == 'wmi' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl - name: docstring-parser-fork - version: 0.0.14 - sha256: 4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af - requires_dist: - - docstring-parser[docs] ; extra == 'dev' - - docstring-parser[test] ; extra == 'dev' - - pre-commit>=2.16.0 ; python_full_version >= '3.9' and extra == 'dev' - - pydoctor>=25.4.0 ; extra == 'docs' - - pytest ; extra == 'test' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl - name: docstripy - version: 0.7.2 - sha256: c4ba35de6c1b1c51f7afad4a46d8953aad55dce1a490d198f7e98c8c63efefda - requires_dist: - - nbformat - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda - sha256: 930f21584babebdabc8310f894557d032211f6cb427f7028c7c92cab6fe6cc1f - md5: dbb824a3a87ac1e2ce02f8227be67e74 + - pkg:pypi/tornado?source=hash-mapping + size: 882996 + timestamp: 1774358035145 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad + md5: a77f85f77be52ff59391544bfe73390a depends: - - importlib-metadata >=1.6.0 - - packaging >=20.9 - - python >=3.10 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + size: 85189 + timestamp: 1753484064210 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h41580af_10.conda + sha256: 325d370b28e2b9cc1f765c5b4cdb394c91a5d958fbd15da1a14607a28fee09f6 + md5: 755b096086851e1193f3b10347415d7c + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - krb5 >=1.22.2,<1.23.0a0 + - libsodium >=1.0.21,<1.0.22.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + size: 311150 + timestamp: 1772476812121 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 601375 + timestamp: 1764777111296 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda + sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 + md5: aaa2a381ccc56eac91d63b6c1240312f + depends: + - cpython + - python-gil + license: MIT + license_family: MIT + purls: [] + size: 8191 + timestamp: 1744137672556 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 + md5: 2934f256a8acfe48f6ebb4fce6cde29c + depends: + - python >=3.9 + - typing-extensions >=4.0.0 license: MIT license_family: MIT purls: - - pkg:pypi/dunamai?source=hash-mapping - size: 30866 - timestamp: 1775597880526 -- pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl - name: easydiffraction - version: 0.16.0 - sha256: 2691a1e175974ca79e0ec3c219d92b77f277c38fb3b0b8d25f6f7e99696bf70f - requires_dist: - - asciichartpy - - asteval - - bumps - - colorama - - crysfml - - cryspy - - darkdetect - - dfo-ls - - diffpy-pdffit2 - - diffpy-utils - - essdiffraction - - gemmi - - lmfit - - numpy - - pandas - - plotly - - pooch - - py3dmol - - rich - - scipy - - sympy - - tabulate - - typeguard - - typer - - uncertainties - - varname - - build ; extra == 'dev' - - copier ; extra == 'dev' - - docstripy ; extra == 'dev' - - format-docstring ; extra == 'dev' - - gitpython ; extra == 'dev' - - interrogate ; extra == 'dev' - - jinja2 ; extra == 'dev' - - jupyterquiz ; extra == 'dev' - - jupytext ; extra == 'dev' - - mike ; extra == 'dev' - - mkdocs ; extra == 'dev' - - mkdocs-autorefs ; extra == 'dev' - - mkdocs-jupyter ; extra == 'dev' - - mkdocs-markdownextradata-plugin ; extra == 'dev' - - mkdocs-material ; extra == 'dev' - - mkdocs-plugin-inline-svg ; extra == 'dev' - - mkdocstrings-python ; extra == 'dev' - - nbmake ; extra == 'dev' - - nbqa ; extra == 'dev' - - nbstripout ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pydoclint ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-benchmark ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - pytest-xdist ; extra == 'dev' - - pyyaml ; extra == 'dev' - - radon ; extra == 'dev' - - ruff ; extra == 'dev' - - spdx-headers ; extra == 'dev' - - validate-pyproject[all] ; extra == 'dev' - - versioningit ; extra == 'dev' - requires_python: '>=3.12' -- pypi: ./ - name: easyreflectometry - version: 1.5.0+devdirty9 - sha256: 6cb81566f4ddb1401f1f6cfdac62844e904c9e177f74d0526912a44ae0489a16 - requires_dist: - - bumps - - easyscience - - orsopy - - pooch - - refl1d>=1.0.0 - - refnx - - scipp - - svglib<1.6 ; sys_platform == 'darwin' or sys_platform == 'linux' - - xhtml2pdf - - build ; extra == 'dev' - - copier ; extra == 'dev' - - docstripy ; extra == 'dev' - - format-docstring ; extra == 'dev' - - gitpython ; extra == 'dev' - - interrogate ; extra == 'dev' - - jinja2 ; extra == 'dev' - - jupyterquiz ; extra == 'dev' - - jupytext ; extra == 'dev' - - mike ; extra == 'dev' - - mkdocs ; extra == 'dev' - - mkdocs-autorefs ; extra == 'dev' - - mkdocs-jupyter ; extra == 'dev' - - mkdocs-markdownextradata-plugin ; extra == 'dev' - - mkdocs-material ; extra == 'dev' - - mkdocs-plugin-inline-svg ; extra == 'dev' - - mkdocstrings-python ; extra == 'dev' - - nbmake ; extra == 'dev' - - nbqa ; extra == 'dev' - - nbstripout ; extra == 'dev' - - plopp ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pydoclint ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - pytest-xdist ; extra == 'dev' - - pyyaml ; extra == 'dev' - - radon ; extra == 'dev' - - ruff ; extra == 'dev' - - spdx-headers ; extra == 'dev' - - validate-pyproject[all] ; extra == 'dev' - - versioningit ; extra == 'dev' - requires_python: '>=3.11' - editable: true -- pypi: https://files.pythonhosted.org/packages/4a/8a/4cb9367a86f2b9526727ee94e5e6a3d86f9f7d7d947927b444e5bcd56a89/easyscience-2.3.1-py3-none-any.whl - name: easyscience - 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 == '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' - - spdx-headers ; extra == 'dev' - - validate-pyproject[all] ; extra == 'dev' - - versioningit ; extra == 'dev' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl - name: email-validator - version: 2.3.0 - sha256: 80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 - requires_dist: - - dnspython>=2.0.0 - - idna>=2.0.0 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl - name: essdiffraction - version: 26.5.1 - sha256: 8a6c779078c71be250714619214069221ab7968a69580d4e4d3f4b3e9a1a53ad - requires_dist: - - dask>=2022.1.0 - - essreduce>=26.4.0 - - graphviz - - numpy>=2 - - plopp>=26.2.0 - - pythreejs>=2.4.1 - - sciline>=25.4.1 - - scipp>=25.11.0 - - scippneutron>=26.3.0 - - scippnexus>=23.12.0 - - tof>=25.12.0 - - ncrystal[cif]>=4.1.0 - - spglib!=2.7 - - pandas>=2.1.2 ; extra == 'test' - - pooch>=1.5 ; extra == 'test' - - pytest>=7.0 ; extra == 'test' - - ipywidgets>=8.1.7 ; extra == 'test' - - autodoc-pydantic ; extra == 'docs' - - ipykernel ; extra == 'docs' - - ipympl ; extra == 'docs' - - ipython!=8.7.0 ; extra == 'docs' - - myst-parser ; extra == 'docs' - - nbsphinx ; extra == 'docs' - - pandas ; extra == 'docs' - - pooch ; extra == 'docs' - - pydata-sphinx-theme>=0.14 ; extra == 'docs' - - sphinx ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-design ; extra == 'docs' - - sphinxcontrib-bibtex ; extra == 'docs' - - pyarrow ; extra == 'docs' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl - name: essreduce - version: 26.4.1 - sha256: 1758a18fffca9c7c2a6fa9547cf87bf45f9d52fc3ccbdffcf7524f71bc060424 - requires_dist: - - sciline>=25.11.0 - - scipp>=26.3.1 - - scippneutron>=25.11.1 - - scippnexus>=25.6.0 - - graphviz>=0.20 ; extra == 'test' - - ipywidgets>=8.1 ; extra == 'test' - - matplotlib>=3.10.7 ; extra == 'test' - - numba>=0.63 ; extra == 'test' - - pooch>=1.9.0 ; extra == 'test' - - pytest>=7.0 ; extra == 'test' - - scipy>=1.14 ; extra == 'test' - - tof>=25.12.0 ; extra == 'test' - - autodoc-pydantic ; extra == 'docs' - - graphviz>=0.20 ; extra == 'docs' - - ipykernel ; extra == 'docs' - - ipython!=8.7.0 ; extra == 'docs' - - ipywidgets>=8.1 ; extra == 'docs' - - myst-parser ; extra == 'docs' - - nbsphinx ; extra == 'docs' - - numba>=0.63 ; extra == 'docs' - - plopp ; extra == 'docs' - - pydata-sphinx-theme>=0.14 ; extra == 'docs' - - sphinx>=7 ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-design ; extra == 'docs' - - tof>=25.12.0 ; extra == 'docs' - requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda - sha256: 05ffdcb83903c159bfbb78a07fbcce6fd6dda41df9c55ed75e0eb1db5528048f - md5: 879479fda1dddb002fdc4885cea33740 - depends: - - eval_type_backport >=0.2.2,<0.2.3.0a0 - - python >=3.9 - license: MIT - license_family: MIT - purls: [] - size: 6662 - timestamp: 1734857849281 -- conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda - sha256: 2d721421a60676216e10837a240c75e2190e093920a4016a469fa9a62c95ab5f - md5: 8681d7f876da5e66a1c7fce424509383 + - pkg:pypi/annotated-types?source=hash-mapping + size: 18074 + timestamp: 1733247158254 +- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda + sha256: f09aed24661cd45ba54a43772504f05c0698248734f9ae8cd289d314ac89707e + md5: af2df4b9108808da3dc76710fe50eae2 depends: - - python >=3.9 + - exceptiongroup >=1.0.2 + - idna >=2.8 + - python >=3.10 + - typing_extensions >=4.5 + - python constrains: - - eval-type-backport >=0.2.2,<0.2.3.0a0 + - trio >=0.32.0 + - uvloop >=0.22.1 + - winloop >=0.2.3 license: MIT license_family: MIT purls: - - pkg:pypi/eval-type-backport?source=hash-mapping - size: 11520 - timestamp: 1734857840035 -- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 - md5: 8e662bd460bda79b1ea39194e3c4c9ab + - pkg:pypi/anyio?source=compressed-mapping + size: 146764 + timestamp: 1774359453364 +- conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda + sha256: 8f032b140ea4159806e4969a68b4a3c0a7cab1ad936eb958a2b5ffe5335e19bf + md5: 54898d0f524c9dee622d44bbb081a8ab + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/appnope?source=hash-mapping + size: 10076 + timestamp: 1733332433806 +- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad + md5: 8ac12aff0860280ee0cff7fa2cf63f3b + depends: + - argon2-cffi-bindings + - python >=3.9 + - typing-extensions + constrains: + - argon2_cffi ==999 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi?source=hash-mapping + size: 18715 + timestamp: 1749017288144 +- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + sha256: 792da8131b1b53ff667bd6fc617ea9087b570305ccb9913deb36b8e12b3b5141 + md5: 85c4f19f377424eafc4ed7911b291642 depends: - python >=3.10 - - typing_extensions >=4.6.0 - license: MIT and PSF-2.0 + - python-dateutil >=2.7.0 + - python-tzdata + - python + license: Apache-2.0 + license_family: APACHE purls: - - pkg:pypi/exceptiongroup?source=hash-mapping - size: 21333 - timestamp: 1763918099466 -- pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - name: execnet - version: 2.1.2 - sha256: 67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec - requires_dist: - - hatch ; extra == 'testing' - - pre-commit ; extra == 'testing' - - pytest ; extra == 'testing' - - tox ; extra == 'testing' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad - md5: ff9efb7f7469aed3c4a8106ffa29593c + - pkg:pypi/arrow?source=hash-mapping + size: 113854 + timestamp: 1760831179410 +- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.1-pyhd8ed1ab_0.conda + sha256: ee4da0f3fe9d59439798ee399ef3e482791e48784873d546e706d0935f9ff010 + md5: 9673a61a297b00016442e022d689faa6 + depends: + - python >=3.10 + constrains: + - astroid >=2,<5 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/asttokens?source=hash-mapping + size: 28797 + timestamp: 1763410017955 +- conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda + sha256: ea8486637cfb89dc26dc9559921640cd1d5fd37e5e02c33d85c94572139f2efe + md5: b85e84cb64c762569cc1a760c2327e0a depends: - python >=3.10 + - typing_extensions >=4.0.0 + - python license: MIT license_family: MIT purls: - - pkg:pypi/executing?source=hash-mapping - size: 30753 - timestamp: 1756729456476 -- pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl - name: filelock - version: 3.29.0 - sha256: 96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl - name: fonttools - version: 4.62.1 - sha256: 7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056 - requires_dist: - - lxml>=4.0 ; extra == 'lxml' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' - - zopfli>=0.1.4 ; extra == 'woff' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' - - lz4>=1.7.4.2 ; extra == 'graphite' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' - - pycairo ; extra == 'interpolatable' - - matplotlib ; extra == 'plot' - - sympy ; extra == 'symfont' - - xattr ; sys_platform == 'darwin' and extra == 'type1' - - skia-pathops>=0.5.0 ; extra == 'pathops' - - uharfbuzz>=0.45.0 ; extra == 'repacker' - - lxml>=4.0 ; extra == 'all' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' - - zopfli>=0.1.4 ; extra == 'all' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' - - lz4>=1.7.4.2 ; extra == 'all' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' - - pycairo ; extra == 'all' - - matplotlib ; extra == 'all' - - sympy ; extra == 'all' - - xattr ; sys_platform == 'darwin' and extra == 'all' - - skia-pathops>=0.5.0 ; extra == 'all' - - uharfbuzz>=0.45.0 ; extra == 'all' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl - name: fonttools - version: 4.62.1 - sha256: c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79 - requires_dist: - - lxml>=4.0 ; extra == 'lxml' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' - - zopfli>=0.1.4 ; extra == 'woff' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' - - lz4>=1.7.4.2 ; extra == 'graphite' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' - - pycairo ; extra == 'interpolatable' - - matplotlib ; extra == 'plot' - - sympy ; extra == 'symfont' - - xattr ; sys_platform == 'darwin' and extra == 'type1' - - skia-pathops>=0.5.0 ; extra == 'pathops' - - uharfbuzz>=0.45.0 ; extra == 'repacker' - - lxml>=4.0 ; extra == 'all' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' - - zopfli>=0.1.4 ; extra == 'all' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' - - lz4>=1.7.4.2 ; extra == 'all' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' - - pycairo ; extra == 'all' - - matplotlib ; extra == 'all' - - sympy ; extra == 'all' - - xattr ; sys_platform == 'darwin' and extra == 'all' - - skia-pathops>=0.5.0 ; extra == 'all' - - uharfbuzz>=0.45.0 ; extra == 'all' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl - name: fonttools - version: 4.62.1 - sha256: 40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7 - requires_dist: - - lxml>=4.0 ; extra == 'lxml' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' - - zopfli>=0.1.4 ; extra == 'woff' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' - - lz4>=1.7.4.2 ; extra == 'graphite' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' - - pycairo ; extra == 'interpolatable' - - matplotlib ; extra == 'plot' - - sympy ; extra == 'symfont' - - xattr ; sys_platform == 'darwin' and extra == 'type1' - - skia-pathops>=0.5.0 ; extra == 'pathops' - - uharfbuzz>=0.45.0 ; extra == 'repacker' - - lxml>=4.0 ; extra == 'all' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' - - zopfli>=0.1.4 ; extra == 'all' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' - - lz4>=1.7.4.2 ; extra == 'all' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' - - pycairo ; extra == 'all' - - matplotlib ; extra == 'all' - - sympy ; extra == 'all' - - xattr ; sys_platform == 'darwin' and extra == 'all' - - skia-pathops>=0.5.0 ; extra == 'all' - - uharfbuzz>=0.45.0 ; extra == 'all' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: fonttools - version: 4.62.1 - sha256: 1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b - requires_dist: - - lxml>=4.0 ; extra == 'lxml' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' - - zopfli>=0.1.4 ; extra == 'woff' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' - - lz4>=1.7.4.2 ; extra == 'graphite' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' - - pycairo ; extra == 'interpolatable' - - matplotlib ; extra == 'plot' - - sympy ; extra == 'symfont' - - xattr ; sys_platform == 'darwin' and extra == 'type1' - - skia-pathops>=0.5.0 ; extra == 'pathops' - - uharfbuzz>=0.45.0 ; extra == 'repacker' - - lxml>=4.0 ; extra == 'all' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' - - zopfli>=0.1.4 ; extra == 'all' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' - - lz4>=1.7.4.2 ; extra == 'all' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' - - pycairo ; extra == 'all' - - matplotlib ; extra == 'all' - - sympy ; extra == 'all' - - xattr ; sys_platform == 'darwin' and extra == 'all' - - skia-pathops>=0.5.0 ; extra == 'all' - - uharfbuzz>=0.45.0 ; extra == 'all' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl - name: fonttools - version: 4.62.1 - sha256: e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2 - requires_dist: - - lxml>=4.0 ; extra == 'lxml' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' - - zopfli>=0.1.4 ; extra == 'woff' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' - - lz4>=1.7.4.2 ; extra == 'graphite' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' - - pycairo ; extra == 'interpolatable' - - matplotlib ; extra == 'plot' - - sympy ; extra == 'symfont' - - xattr ; sys_platform == 'darwin' and extra == 'type1' - - skia-pathops>=0.5.0 ; extra == 'pathops' - - uharfbuzz>=0.45.0 ; extra == 'repacker' - - lxml>=4.0 ; extra == 'all' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' - - zopfli>=0.1.4 ; extra == 'all' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' - - lz4>=1.7.4.2 ; extra == 'all' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' - - pycairo ; extra == 'all' - - matplotlib ; extra == 'all' - - sympy ; extra == 'all' - - xattr ; sys_platform == 'darwin' and extra == 'all' - - skia-pathops>=0.5.0 ; extra == 'all' - - uharfbuzz>=0.45.0 ; extra == 'all' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: fonttools - version: 4.62.1 - sha256: 6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1 - requires_dist: - - lxml>=4.0 ; extra == 'lxml' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' - - zopfli>=0.1.4 ; extra == 'woff' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' - - lz4>=1.7.4.2 ; extra == 'graphite' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' - - pycairo ; extra == 'interpolatable' - - matplotlib ; extra == 'plot' - - sympy ; extra == 'symfont' - - xattr ; sys_platform == 'darwin' and extra == 'type1' - - skia-pathops>=0.5.0 ; extra == 'pathops' - - uharfbuzz>=0.45.0 ; extra == 'repacker' - - lxml>=4.0 ; extra == 'all' - - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' - - zopfli>=0.1.4 ; extra == 'all' - - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' - - lz4>=1.7.4.2 ; extra == 'all' - - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' - - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' - - pycairo ; extra == 'all' - - matplotlib ; extra == 'all' - - sympy ; extra == 'all' - - xattr ; sys_platform == 'darwin' and extra == 'all' - - skia-pathops>=0.5.0 ; extra == 'all' - - uharfbuzz>=0.45.0 ; extra == 'all' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl - name: format-docstring - version: 0.2.7 - sha256: c9d50eafebe0f260e3270ca662ff3a0ed4050f64d95e352f8c5f88d9aede42d6 - requires_dist: - - click>=8.0 - - jupyter-notebook-parser>=0.1.4 - - tomli>=1.1.0 ; python_full_version < '3.11' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda - sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 - md5: d3549fd50d450b6d9e7dddff25dd2110 + - pkg:pypi/async-lru?source=hash-mapping + size: 22949 + timestamp: 1773926359134 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab + md5: c6b0543676ecb1fb2d7643941fe375f2 depends: - - cached-property >=1.3.0 - - python >=3.9,<4 - license: MPL-2.0 - license_family: MOZILLA + - python >=3.10 + - python + license: MIT + license_family: MIT purls: - - pkg:pypi/fqdn?source=hash-mapping - size: 16705 - timestamp: 1733327494780 -- pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl - name: freetype-py - version: 2.5.1 - sha256: 0b7f8e0342779f65ca13ef8bc103938366fecade23e6bb37cb671c2b8ad7f124 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl - name: frozenlist - version: 1.8.0 - sha256: ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl - name: frozenlist - version: 1.8.0 - sha256: f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - name: frozenlist - version: 1.8.0 - sha256: 2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl - name: frozenlist - version: 1.8.0 - sha256: fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - name: frozenlist - version: 1.8.0 - sha256: fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl - name: frozenlist - version: 1.8.0 - sha256: 878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl - name: fsspec - version: 2026.4.0 - sha256: 11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2 - requires_dist: - - adlfs ; extra == 'abfs' - - adlfs ; extra == 'adl' - - pyarrow>=1 ; extra == 'arrow' - - dask ; extra == 'dask' - - distributed ; extra == 'dask' - - pre-commit ; extra == 'dev' - - ruff>=0.5 ; extra == 'dev' - - numpydoc ; extra == 'doc' - - sphinx ; extra == 'doc' - - sphinx-design ; extra == 'doc' - - sphinx-rtd-theme ; extra == 'doc' - - yarl ; extra == 'doc' - - dropbox ; extra == 'dropbox' - - dropboxdrivefs ; extra == 'dropbox' - - requests ; extra == 'dropbox' - - adlfs ; extra == 'full' - - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'full' - - dask ; extra == 'full' - - distributed ; extra == 'full' - - dropbox ; extra == 'full' - - dropboxdrivefs ; extra == 'full' - - fusepy ; extra == 'full' - - gcsfs>2024.2.0 ; extra == 'full' - - libarchive-c ; extra == 'full' - - ocifs ; extra == 'full' - - panel ; extra == 'full' - - paramiko ; extra == 'full' - - pyarrow>=1 ; extra == 'full' - - pygit2 ; extra == 'full' - - requests ; extra == 'full' - - s3fs>2024.2.0 ; extra == 'full' - - smbprotocol ; extra == 'full' - - tqdm ; extra == 'full' - - fusepy ; extra == 'fuse' - - gcsfs>2024.2.0 ; extra == 'gcs' - - pygit2 ; extra == 'git' - - requests ; extra == 'github' - - gcsfs ; extra == 'gs' - - panel ; extra == 'gui' - - pyarrow>=1 ; extra == 'hdfs' - - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'http' - - libarchive-c ; extra == 'libarchive' - - ocifs ; extra == 'oci' - - s3fs>2024.2.0 ; extra == 's3' - - paramiko ; extra == 'sftp' - - smbprotocol ; extra == 'smb' - - paramiko ; extra == 'ssh' - - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test' - - numpy ; extra == 'test' - - pytest ; extra == 'test' - - pytest-asyncio!=0.22.0 ; extra == 'test' - - pytest-benchmark ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-mock ; extra == 'test' - - pytest-recording ; extra == 'test' - - pytest-rerunfailures ; extra == 'test' - - requests ; extra == 'test' - - aiobotocore>=2.5.4,<3.0.0 ; extra == 'test-downstream' - - dask[dataframe,test] ; extra == 'test-downstream' - - moto[server]>4,<5 ; extra == 'test-downstream' - - pytest-timeout ; extra == 'test-downstream' - - xarray ; extra == 'test-downstream' - - adlfs ; extra == 'test-full' - - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test-full' - - backports-zstd ; python_full_version < '3.14' and extra == 'test-full' - - cloudpickle ; extra == 'test-full' - - dask ; extra == 'test-full' - - distributed ; extra == 'test-full' - - dropbox ; extra == 'test-full' - - dropboxdrivefs ; extra == 'test-full' - - fastparquet ; extra == 'test-full' - - fusepy ; extra == 'test-full' - - gcsfs ; extra == 'test-full' - - jinja2 ; extra == 'test-full' - - kerchunk ; extra == 'test-full' - - libarchive-c ; extra == 'test-full' - - lz4 ; extra == 'test-full' - - notebook ; extra == 'test-full' - - numpy ; extra == 'test-full' - - ocifs ; extra == 'test-full' - - pandas<3.0.0 ; extra == 'test-full' - - panel ; extra == 'test-full' - - paramiko ; extra == 'test-full' - - pyarrow ; extra == 'test-full' - - pyarrow>=1 ; extra == 'test-full' - - pyftpdlib ; extra == 'test-full' - - pygit2 ; extra == 'test-full' - - pytest ; extra == 'test-full' - - pytest-asyncio!=0.22.0 ; extra == 'test-full' - - pytest-benchmark ; extra == 'test-full' - - pytest-cov ; extra == 'test-full' - - pytest-mock ; extra == 'test-full' - - pytest-recording ; extra == 'test-full' - - pytest-rerunfailures ; extra == 'test-full' - - python-snappy ; extra == 'test-full' - - requests ; extra == 'test-full' - - smbprotocol ; extra == 'test-full' - - tqdm ; extra == 'test-full' - - urllib3 ; extra == 'test-full' - - zarr ; extra == 'test-full' - - zstandard ; python_full_version < '3.14' and extra == 'test-full' - - tqdm ; extra == 'tqdm' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda - sha256: 4a3e3e86b7b49aaa2a0faa57da2bcf39f7ee858499e8335132f83bfeed79191e - md5: 84f8955e99a8944fdee49da39edb0add + - pkg:pypi/attrs?source=hash-mapping + size: 64927 + timestamp: 1773935801332 +- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770 + md5: f1976ce927373500cc19d3c0b2c85177 depends: - - python >=3.9 + - python >=3.10 + - python + constrains: + - pytz >=2015.7 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/funcy?source=hash-mapping - size: 30249 - timestamp: 1734381235500 -- pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: gemmi - version: 0.7.5 - sha256: 750b4d9751aaf1460ac4f0f45308ddced25f47bcf7a30355eb3b1f779f03952a - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl - name: gemmi - version: 0.7.5 - sha256: c7d8b08c33fe6ba375223306149092440c69cbfbd55c3d3e3436e5fb315a225d - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl - name: gemmi - version: 0.7.5 - sha256: ad1f72ffa24adbfaf259e11471f6f071a668667f6ca846051f3bfea024fd337d - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - name: ghp-import - version: 2.1.0 - sha256: 8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 - requires_dist: - - python-dateutil>=2.8.1 - - twine ; extra == 'dev' - - markdown ; extra == 'dev' - - flake8 ; extra == 'dev' - - wheel ; extra == 'dev' -- pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl - name: gitdb - version: 4.0.12 - sha256: 67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf - requires_dist: - - smmap>=3.0.1,<6 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - name: gitpython - version: 3.1.50 - sha256: d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9 - requires_dist: - - gitdb>=4.0.1,<5 - - typing-extensions>=3.10.0.2 ; python_full_version < '3.10' - - coverage[toml] ; extra == 'test' - - ddt>=1.1.1,!=1.4.3 ; extra == 'test' - - mock ; python_full_version < '3.8' and extra == 'test' - - mypy==1.18.2 ; python_full_version >= '3.9' and extra == 'test' - - pre-commit ; extra == 'test' - - pytest>=7.3.1 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-instafail ; extra == 'test' - - pytest-mock ; extra == 'test' - - pytest-sugar ; extra == 'test' - - typing-extensions ; python_full_version < '3.11' and extra == 'test' - - sphinx>=7.4.7,<8 ; extra == 'doc' - - sphinx-rtd-theme ; extra == 'doc' - - sphinx-autodoc-typehints ; extra == 'doc' - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl - name: graphviz - version: '0.21' - sha256: 54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42 - requires_dist: - - build ; extra == 'dev' - - wheel ; extra == 'dev' - - twine ; extra == 'dev' - - flake8 ; extra == 'dev' - - flake8-pyproject ; extra == 'dev' - - pep8-naming ; extra == 'dev' - - tox>=3 ; extra == 'dev' - - pytest>=7,<8.1 ; extra == 'test' - - pytest-mock>=3 ; extra == 'test' - - pytest-cov ; extra == 'test' - - coverage ; extra == 'test' - - sphinx>=5,<7 ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - sphinx-rtd-theme>=0.2.5 ; extra == 'docs' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - name: greenlet - version: 3.5.0 - sha256: 2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13 - requires_dist: - - sphinx ; extra == 'docs' - - furo ; extra == 'docs' - - objgraph ; extra == 'test' - - psutil ; extra == 'test' - - setuptools ; extra == 'test' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl - name: greenlet - version: 3.5.0 - sha256: 728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5 - requires_dist: - - sphinx ; extra == 'docs' - - furo ; extra == 'docs' - - objgraph ; extra == 'test' - - psutil ; extra == 'test' - - setuptools ; extra == 'test' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - name: griffelib - version: 2.0.2 - sha256: 925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1 - requires_dist: - - pip>=24.0 ; extra == 'pypi' - - platformdirs>=4.2 ; extra == 'pypi' - - wheel>=0.42 ; extra == 'pypi' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda - sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb - md5: b8993c19b0c32a2f7b66cbb58ca27069 + - pkg:pypi/babel?source=compressed-mapping + size: 7684321 + timestamp: 1772555330347 +- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.14.3-pyha770c72_0.conda + sha256: bf1e71c3c0a5b024e44ff928225a0874fc3c3356ec1a0b6fe719108e6d1288f6 + md5: 5267bef8efea4127aacd1f4e1f149b6e depends: - python >=3.10 - - typing_extensions - - python + - soupsieve >=1.2 + - typing-extensions license: MIT license_family: MIT purls: - - pkg:pypi/h11?source=compressed-mapping - size: 39069 - timestamp: 1767729720872 -- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 - md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + - pkg:pypi/beautifulsoup4?source=hash-mapping + size: 90399 + timestamp: 1764520638652 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.3.0-pyhcf101f3_1.conda + sha256: f8ff1f98423674278964a46c93a1766f9e91960d44efd91c6c3ed56a33813f46 + md5: 7c5ebdc286220e8021bf55e6384acd67 depends: - python >=3.10 - - hyperframe >=6.1,<7 - - hpack >=4.1,<5 + - webencodings - python - license: MIT - license_family: MIT + constrains: + - tinycss2 >=1.1.0,<1.5 + license: Apache-2.0 AND MIT purls: - - pkg:pypi/h2?source=hash-mapping - size: 95967 - timestamp: 1756364871835 -- pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl - name: h5py - version: 3.16.0 - sha256: fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3 - requires_dist: - - numpy>=1.21.2 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl - name: h5py - version: 3.16.0 - sha256: 42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb - requires_dist: - - numpy>=1.21.2 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl - name: h5py - version: 3.16.0 - sha256: 9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402 - requires_dist: - - numpy>=1.21.2 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl - name: h5py - version: 3.16.0 - sha256: c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447 - requires_dist: - - numpy>=1.21.2 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl - name: h5py - version: 3.16.0 - sha256: 18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad - requires_dist: - - numpy>=1.21.2 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl - name: h5py - version: 3.16.0 - sha256: 17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999 - requires_dist: - - numpy>=1.21.2 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda - sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba - md5: 0a802cb9888dd14eeefc611f05c40b6e + - pkg:pypi/bleach?source=hash-mapping + size: 142008 + timestamp: 1770719370680 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.3.0-hbca2aae_1.conda + sha256: 7c07a865e5e4cca233cc4e0eb3f0f5ff6c90776461687b4fb0b1764133e1fd61 + md5: f11a319b9700b203aa14c295858782b6 depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/hpack?source=hash-mapping - size: 30731 - timestamp: 1737618390337 -- pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl - name: html5lib - version: '1.1' - sha256: 0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d - requires_dist: - - six>=1.9 - - webencodings - - genshi ; extra == 'all' - - chardet>=2.2 ; extra == 'all' - - lxml ; platform_python_implementation == 'CPython' and extra == 'all' - - chardet>=2.2 ; extra == 'chardet' - - genshi ; extra == 'genshi' - - lxml ; platform_python_implementation == 'CPython' and extra == 'lxml' - requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' -- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda - sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b - md5: 4f14640d58e2cc0aa0819d9d8ba125bb + - bleach ==6.3.0 pyhcf101f3_1 + - tinycss2 + license: Apache-2.0 AND MIT + purls: [] + size: 4409 + timestamp: 1770719370682 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-h4c7d964_0.conda + sha256: 6f4ff81534c19e76acf52fcabf4a258088a932b8f1ac56e9a59e98f6051f8e46 + md5: 56fb2c6c73efc627b40c77d14caecfba depends: - - python >=3.9 - - h11 >=0.16 - - h2 >=3,<5 - - sniffio 1.* - - anyio >=4.0,<5.0 - - certifi - - python + - __win + license: ISC + purls: [] + 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: 131039 + timestamp: 1776865545798 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_1.tar.bz2 + noarch: python + sha256: 561e6660f26c35d137ee150187d89767c988413c978e1b712d53f27ddf70ea17 + md5: 9b347a7ec10940d3f7941ff6c460b551 + depends: + - cached_property >=1.5.2,<1.5.3.0a0 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/httpcore?source=hash-mapping - size: 49483 - timestamp: 1745602916758 -- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda - sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 - md5: d6989ead454181f4f9bc987d3dc4e285 + purls: [] + size: 4134 + timestamp: 1615209571450 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_1.tar.bz2 + sha256: 6dbf7a5070cc43d90a1e4c2ec0c541c69d8e30a0e25f50ce9f6e4a432e42c5d7 + md5: 576d629e47797577ab0f1b351297ef4a depends: - - anyio - - certifi - - httpcore 1.* - - idna - - python >=3.9 + - python >=3.6 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/httpx?source=hash-mapping - size: 63082 - timestamp: 1733663449209 -- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 - md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + - pkg:pypi/cached-property?source=hash-mapping + size: 11065 + timestamp: 1615209567874 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda + sha256: 989db6e5957c4b44fa600c68c681ec2f36a55e48f7c7f1c073d5e91caa8cd878 + md5: 929471569c93acefb30282a22060dcd5 depends: - - python >=3.9 - license: MIT - license_family: MIT + - python >=3.10 + license: ISC purls: - - pkg:pypi/hyperframe?source=hash-mapping - size: 17397 - timestamp: 1737618427549 -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a - md5: c80d8a3b84358cb967fa81e7075fbc8a + - pkg:pypi/certifi?source=compressed-mapping + size: 135656 + timestamp: 1776866680878 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + sha256: 3f9483d62ce24ecd063f8a5a714448445dc8d9e201147c46699fc0033e824457 + md5: a9167b9571f3baa9d448faa2139d1089 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 + - python >=3.10 license: MIT license_family: MIT - purls: [] - size: 12723451 - timestamp: 1773822285671 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda - sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 - md5: f1182c91c0de31a7abd40cedf6a5ebef + purls: + - pkg:pypi/charset-normalizer?source=compressed-mapping + size: 58872 + timestamp: 1775127203018 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 depends: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: [] - size: 12361647 - timestamp: 1773822915649 -- pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl - name: identify - 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.13-pyhcf101f3_0.conda - sha256: 9ab620e6f64bb67737bd7bc1ad6f480770124e304c6710617aba7fe60b089f48 - md5: fb7130c190f9b4ec91219840a05ba3ac + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/colorama?source=hash-mapping + size: 27011 + timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 + md5: 2da13f2b299d8e1995bafbbe9689a2f7 depends: - - python >=3.10 + - python >=3.9 - python license: BSD-3-Clause license_family: BSD purls: - - 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 + - pkg:pypi/comm?source=hash-mapping + size: 14690 + timestamp: 1753453984907 +- conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.15.0-pyhcf101f3_0.conda + sha256: 2ceb1f7edb5e5943f7af69e4d6b3339b2e2580e7c9eef76efed4d18d05ae49db + md5: 12baf87bfa62ac066dbe2526f5d89de1 depends: - python >=3.10 - - zipp >=3.20 + - colorama >=0.4.6 + - dunamai >=1.7.0 + - funcy >=1.17 + - jinja2 >=3.1.5 + - jinja2-ansible-filters >=1.3.1 + - packaging >=23.0 + - pathspec >=0.9.0 + - plumbum >=1.6.9 + - prompt-toolkit <3.0.52 + - pydantic >=2.4.2 + - pygments >=2.7.1 + - pyyaml >=5.3.1 + - questionary >=1.8.1 + - eval-type-backport >=0.1.3,<0.3.0 + - platformdirs >=4.3.6 + - typing_extensions >=4.0.0,<5.0.0 - python - license: Apache-2.0 - license_family: APACHE + license: MIT + license_family: MIT purls: - - pkg:pypi/importlib-metadata?source=compressed-mapping - size: 34387 - timestamp: 1773931568510 -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-7.1.0-pyhd8ed1ab_0.conda - sha256: a563a51aa522998172838e867e6dedcf630bc45796e8612f5a1f6d73e9c8125a - md5: 0ba6225c279baf7ea9473a62ea0ec9ae + - pkg:pypi/copier?source=hash-mapping + size: 58609 + timestamp: 1777562504238 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.13-py313hd8ed1ab_100.conda + noarch: generic + sha256: 836b92c209d4b6b9fb28bd51bd788b22a0c5492ae95eec2724e65a15ed4ab2e1 + md5: 3a8a8b87e72f95b54689fb588e154ec9 depends: - - python >=3.10 - - zipp >=3.1.0 - constrains: - - importlib-resources >=7.1.0,<7.1.1.0a0 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/importlib-resources?source=compressed-mapping - size: 34809 - timestamp: 1776068839274 -- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - name: iniconfig - version: 2.3.0 - sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl - name: interrogate - version: 1.7.0 - sha256: b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12 - requires_dist: - - attrs - - click>=7.1 - - colorama - - py - - tabulate - - tomli ; python_full_version < '3.11' - - cairosvg ; extra == 'dev' - - sphinx ; extra == 'dev' - - sphinx-autobuild ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - pytest-mock ; extra == 'dev' - - coverage[toml] ; extra == 'dev' - - wheel ; extra == 'dev' - - pre-commit ; extra == 'dev' - - sphinx ; extra == 'docs' - - sphinx-autobuild ; extra == 'docs' - - cairosvg ; extra == 'png' - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-mock ; extra == 'tests' - - coverage[toml] ; extra == 'tests' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_0.conda - sha256: d7421c54944dec04d2671e23196a15583d48f309d06fbcf995b5dfa6d586a5e9 - md5: 4dee387356d58bdc4b686ae3424fbd9e + - python >=3.13,<3.14.0a0 + - python_abi * *_cp313 + license: Python-2.0 + purls: [] + size: 48530 + timestamp: 1775613723457 +- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 + md5: 9ce473d1d1be1cc3810856a48b3fab32 depends: - - python >=3.10 + - python >=3.9 license: BSD-2-Clause license_family: BSD purls: - - pkg:pypi/invoke?source=hash-mapping - size: 133811 - timestamp: 1775579645825 -- pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl - name: ipydatawidgets - version: 4.3.5 - sha256: d590cdb7c364f2f6ab346f20b9d2dd661d27a834ef7845bc9d7113118f05ec87 - requires_dist: - - ipywidgets>=7.0.0 - - numpy - - traittypes>=0.2.0 - - sphinx ; extra == 'docs' - - recommonmark ; extra == 'docs' - - sphinx-rtd-theme ; extra == 'docs' - - pytest>=4 ; extra == 'test' - - pytest-cov ; extra == 'test' - - nbval>=0.9.2 ; extra == 'test' - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda - sha256: 5c1f3e874adaf603449f2b135d48f168c5d510088c78c229bda0431268b43b27 - md5: 4b53d436f3fbc02ce3eeaf8ae9bebe01 + - pkg:pypi/decorator?source=hash-mapping + size: 14129 + timestamp: 1740385067843 +- conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be + md5: 961b3a227b437d82ad7054484cfa71b2 depends: - - appnope - - __osx - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.8.0 - - jupyter_core >=5.1,!=6.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.10 - - pyzmq >=25 - - tornado >=6.4.1 - - traitlets >=5.4.0 - - python - constrains: - - appnope >=0.1.2 - license: BSD-3-Clause - license_family: BSD + - python >=3.6 + license: PSF-2.0 + license_family: PSF purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 132260 - timestamp: 1770566135697 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda - sha256: 9cdadaeef5abadca4113f92f5589db19f8b7df5e1b81cb0225f7024a3aedefa3 - md5: b3a7d5842f857414d9ae831a799444dd + - pkg:pypi/defusedxml?source=hash-mapping + size: 24062 + timestamp: 1615232388757 +- conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.26.1-pyhd8ed1ab_0.conda + sha256: 930f21584babebdabc8310f894557d032211f6cb427f7028c7c92cab6fe6cc1f + md5: dbb824a3a87ac1e2ce02f8227be67e74 depends: - - __win - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.8.0 - - jupyter_core >=5.1,!=6.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 + - importlib-metadata >=1.6.0 + - packaging >=20.9 - python >=3.10 - - pyzmq >=25 - - tornado >=6.4.1 - - traitlets >=5.4.0 - - python - constrains: - - appnope >=0.1.2 - license: BSD-3-Clause - license_family: BSD + license: MIT + license_family: MIT purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 132382 - timestamp: 1770566174387 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda - sha256: b77ed58eb235e5ad80e742b03caeed4bbc2a2ef064cb9a2deee3b75dfae91b2a - md5: 8b267f517b81c13594ed68d646fd5dcb + - pkg:pypi/dunamai?source=hash-mapping + size: 30866 + timestamp: 1775597880526 +- conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda + sha256: 05ffdcb83903c159bfbb78a07fbcce6fd6dda41df9c55ed75e0eb1db5528048f + md5: 879479fda1dddb002fdc4885cea33740 depends: - - __linux - - comm >=0.1.1 - - debugpy >=1.6.5 - - ipython >=7.23.1 - - jupyter_client >=8.8.0 - - jupyter_core >=5.1,!=6.0.* - - matplotlib-inline >=0.1 - - nest-asyncio >=1.4 - - packaging >=22 - - psutil >=5.7 - - python >=3.10 - - pyzmq >=25 - - tornado >=6.4.1 - - traitlets >=5.4.0 - - python - constrains: - - appnope >=0.1.2 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipykernel?source=hash-mapping - size: 133644 - timestamp: 1770566133040 -- pypi: https://files.pythonhosted.org/packages/12/b3/88c0ef22878c86035f058df0ac6c171319ffd0aa52a406455ed3a3847566/ipympl-0.10.0-py3-none-any.whl - name: ipympl - version: 0.10.0 - sha256: a09c4f0ff86490cc62aed45e53b912fb706e3ec3506c4a51ce4a670d6667f5ce - requires_dist: - - ipython<10 - - ipywidgets>=7.6.0,<9 - - matplotlib>=3.5.0,<4 - - numpy - - pillow - - traitlets<6 - - intersphinx-registry ; extra == 'docs' - - myst-nb ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-thebe ; extra == 'docs' - - sphinx-togglebutton ; extra == 'docs' - - sphinx>=1.5 ; extra == 'docs' - - 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.13.0-pyh53cf698_0.conda - sha256: a0af49948a1842dfd15a0b0b2fd56c94ddbd07e07a6c8b4bc70d43015eafaff0 - md5: 73e9657cd19605740d21efb14d8d0cb9 - depends: - - __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.11 - - stack_data >=0.6.0 - - traitlets >=5.13.0 - - typing_extensions >=4.6 - - pexpect >4.6 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython?source=compressed-mapping - size: 651632 - timestamp: 1777038396606 -- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda - sha256: f252ec33597115ff21cbb31051f6f9be34ca36cbbbf3d266b597660d8d8edde9 - md5: 5631ab99e902463d9dd4221e5b4eab6d - depends: - - __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.11 - - stack_data >=0.6.0 - - traitlets >=5.13.0 - - typing_extensions >=4.6 - - colorama >=0.4.4 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - 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 - depends: - - pygments - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/ipython-pygments-lexers?source=hash-mapping - size: 13993 - timestamp: 1737123723464 -- pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - name: ipywidgets - version: 8.1.8 - sha256: ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e - requires_dist: - - comm>=0.1.3 - - ipython>=6.1.0 - - traitlets>=4.3.1 - - widgetsnbextension~=4.0.14 - - jupyterlab-widgets~=3.0.15 - - jsonschema ; extra == 'test' - - ipykernel ; extra == 'test' - - pytest>=3.6.0 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytz ; extra == 'test' - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda - sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed - md5: 0b0154421989637d424ccf0f104be51a - depends: - - arrow >=0.15.0 + - eval_type_backport >=0.2.2,<0.2.3.0a0 - python >=3.9 license: MIT license_family: MIT - purls: - - pkg:pypi/isoduration?source=hash-mapping - size: 19832 - timestamp: 1733493720346 -- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - sha256: 92c4d217e2dc68983f724aa983cca5464dcb929c566627b26a2511159667dba8 - md5: a4f4c5dc9b80bc50e0d3dc4e6e8f1bd9 + purls: [] + size: 6662 + timestamp: 1734857849281 +- conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda + sha256: 2d721421a60676216e10837a240c75e2190e093920a4016a469fa9a62c95ab5f + md5: 8681d7f876da5e66a1c7fce424509383 depends: - - parso >=0.8.3,<0.9.0 - python >=3.9 - license: Apache-2.0 AND MIT + constrains: + - eval-type-backport >=0.2.2,<0.2.3.0a0 + license: MIT + license_family: MIT purls: - - pkg:pypi/jedi?source=hash-mapping - size: 843646 - timestamp: 1733300981994 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b - md5: 04558c96691bed63104678757beb4f8d + - pkg:pypi/eval-type-backport?source=hash-mapping + size: 11520 + timestamp: 1734857840035 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab depends: - - markupsafe >=2.0 - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 purls: - - pkg:pypi/jinja2?source=compressed-mapping - size: 120685 - timestamp: 1764517220861 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda - sha256: a36789229ca9ce5315265b9d425abce9acb4691f5864aea69e935020545a9acb - md5: 974c5b3e353f031cfcf2365c9d375926 + - pkg:pypi/exceptiongroup?source=hash-mapping + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad + md5: ff9efb7f7469aed3c4a8106ffa29593c depends: - - jinja2 - - python >=3.9 - - pyyaml - license: BSD-2-Clause - license_family: BSD + - python >=3.10 + license: MIT + license_family: MIT purls: - - pkg:pypi/jinja2-ansible-filters?source=hash-mapping - size: 20315 - timestamp: 1734906203051 -- conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda - sha256: 9daa95bd164c8fa23b3ab196e906ef806141d749eddce2a08baa064f722d25fa - md5: 1269891272187518a0a75c286f7d0bbf + - pkg:pypi/executing?source=hash-mapping + size: 30753 + timestamp: 1756729456476 +- conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 + md5: d3549fd50d450b6d9e7dddff25dd2110 depends: - - python >=3.10 - license: Apache-2.0 - license_family: APACHE + - cached-property >=1.3.0 + - python >=3.9,<4 + license: MPL-2.0 + license_family: MOZILLA purls: - - pkg:pypi/json5?source=compressed-mapping - size: 34731 - timestamp: 1774655440045 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda - sha256: a3d10301b6ff399ba1f3d39e443664804a3d28315a4fb81e745b6817845f70ae - md5: 89bf346df77603055d3c8fe5811691e6 + - pkg:pypi/fqdn?source=hash-mapping + size: 16705 + timestamp: 1733327494780 +- conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda + sha256: 4a3e3e86b7b49aaa2a0faa57da2bcf39f7ee858499e8335132f83bfeed79191e + md5: 84f8955e99a8944fdee49da39edb0add depends: - - python >=3.10 - - python + - python >=3.9 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jsonpointer?source=hash-mapping - size: 14190 - timestamp: 1774311356147 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda - sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 - md5: ada41c863af263cc4c5fcbaff7c3e4dc + - pkg:pypi/funcy?source=hash-mapping + size: 30249 + timestamp: 1734381235500 +- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb + md5: b8993c19b0c32a2f7b66cbb58ca27069 depends: - - attrs >=22.2.0 - - jsonschema-specifications >=2023.3.6 - python >=3.10 - - referencing >=0.28.4 - - rpds-py >=0.25.0 + - typing_extensions - python license: MIT license_family: MIT purls: - - pkg:pypi/jsonschema?source=hash-mapping - size: 82356 - timestamp: 1767839954256 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda - sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 - md5: 439cd0f567d697b20a8f45cb70a1005a + - pkg:pypi/h11?source=compressed-mapping + size: 39069 + timestamp: 1767729720872 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 depends: - python >=3.10 - - referencing >=0.31.0 + - hyperframe >=6.1,<7 + - hpack >=4.1,<5 - python license: MIT license_family: MIT purls: - - pkg:pypi/jsonschema-specifications?source=hash-mapping - size: 19236 - timestamp: 1757335715225 -- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda - sha256: 6886fc61e4e4edd38fd38729976b134e8bd2143f7fce56cc80d7ac7bac99bce1 - md5: 8368d58342d0825f0843dc6acdd0c483 + - pkg:pypi/h2?source=hash-mapping + size: 95967 + timestamp: 1756364871835 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba + md5: 0a802cb9888dd14eeefc611f05c40b6e depends: - - jsonschema >=4.26.0,<4.26.1.0a0 - - fqdn - - idna - - isoduration - - jsonpointer >1.13 - - rfc3339-validator - - rfc3986-validator >0.1.0 - - rfc3987-syntax >=1.1.0 - - uri-template - - webcolors >=24.6.0 + - python >=3.9 license: MIT license_family: MIT - purls: [] - size: 4740 - timestamp: 1767839954258 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda - sha256: 3766e2ae59641c172cec8a821528bfa6bf9543ffaaeb8b358bfd5259dcf18e4e - md5: 0c3b465ceee138b9c39279cc02e5c4a0 + purls: + - pkg:pypi/hpack?source=hash-mapping + size: 30731 + timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b + md5: 4f14640d58e2cc0aa0819d9d8ba125bb depends: - - importlib-metadata >=4.8.3 - - jupyter_server >=1.1.2 - - python >=3.10 + - python >=3.9 + - h11 >=0.16 + - h2 >=3,<5 + - sniffio 1.* + - anyio >=4.0,<5.0 + - certifi - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyter-lsp?source=compressed-mapping - size: 61633 - timestamp: 1775136333147 -- pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl - name: jupyter-notebook-parser - version: 0.1.4 - sha256: 27b3b67cf898684e646d569f017cb27046774ad23866cb0bdf51d5f76a46476b - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda - sha256: e402bd119720862a33229624ec23645916a7d47f30e1711a4af9e005162b84f3 - md5: 8a3d6d0523f66cf004e563a50d9392b3 + - pkg:pypi/httpcore?source=hash-mapping + size: 49483 + timestamp: 1745602916758 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 + md5: d6989ead454181f4f9bc987d3dc4e285 depends: - - jupyter_core >=5.1 - - python >=3.10 - - python-dateutil >=2.8.2 - - pyzmq >=25.0 - - tornado >=6.4.1 - - traitlets >=5.3 - - python + - anyio + - certifi + - httpcore 1.* + - idna + - python >=3.9 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyter-client?source=hash-mapping - size: 112785 - timestamp: 1767954655912 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda - sha256: ed709a6c25b731e01563521ef338b93986cd14b5bc17f35e9382000864872ccc - md5: a8db462b01221e9f5135be466faeb3e0 + - pkg:pypi/httpx?source=hash-mapping + size: 63082 + timestamp: 1733663449209 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda + sha256: 9ab620e6f64bb67737bd7bc1ad6f480770124e304c6710617aba7fe60b089f48 + md5: fb7130c190f9b4ec91219840a05ba3ac depends: - - __win - - pywin32 - - platformdirs >=2.5 - python >=3.10 - - traitlets >=5.3 - python - constrains: - - pywin32 >=300 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyter-core?source=hash-mapping - size: 64679 - timestamp: 1760643889625 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda - sha256: 1d34b80e5bfcd5323f104dbf99a2aafc0e5d823019d626d0dce5d3d356a2a52a - md5: b38fe4e78ee75def7e599843ef4c1ab0 + - 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 depends: - - __unix - - python - - platformdirs >=2.5 - python >=3.10 - - traitlets >=5.3 + - zipp >=3.20 - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-metadata?source=compressed-mapping + size: 34387 + timestamp: 1773931568510 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-7.1.0-pyhd8ed1ab_0.conda + sha256: a563a51aa522998172838e867e6dedcf630bc45796e8612f5a1f6d73e9c8125a + md5: 0ba6225c279baf7ea9473a62ea0ec9ae + depends: + - python >=3.10 + - zipp >=3.1.0 constrains: - - pywin32 >=300 - license: BSD-3-Clause - license_family: BSD + - importlib-resources >=7.1.0,<7.1.1.0a0 + license: Apache-2.0 + license_family: APACHE purls: - - pkg:pypi/jupyter-core?source=hash-mapping - size: 65503 - timestamp: 1760643864586 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda - sha256: c7edb5682c6316a95ad781dccb1b6589cd2ec0bf94f23c21152974eb0363b5d7 - md5: bf42ee94c750c0b2e7e998b79ac299ea + - pkg:pypi/importlib-resources?source=compressed-mapping + size: 34809 + timestamp: 1776068839274 +- conda: https://conda.anaconda.org/conda-forge/noarch/invoke-3.0.3-pyhd8ed1ab_0.conda + sha256: d7421c54944dec04d2671e23196a15583d48f309d06fbcf995b5dfa6d586a5e9 + md5: 4dee387356d58bdc4b686ae3424fbd9e depends: - - jsonschema-with-format-nongpl >=4.18.0 - - packaging - python >=3.10 - - python-json-logger >=2.0.4 - - pyyaml >=5.3 - - referencing - - rfc3339-validator - - rfc3986-validator >=0.1.1 - - traitlets >=5.3 - - python - license: BSD-3-Clause + license: BSD-2-Clause license_family: BSD purls: - - pkg:pypi/jupyter-events?source=compressed-mapping - size: 24002 - timestamp: 1776861872237 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda - sha256: 04fb8ea7749f67abaf76df6257bf86688e1389ceed55eb4fb0176fd2e882dbd6 - md5: 5ee7945accf0f215ddd6055d25d7cd83 + - pkg:pypi/invoke?source=hash-mapping + size: 133811 + timestamp: 1775579645825 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh5552912_1.conda + sha256: 5c1f3e874adaf603449f2b135d48f168c5d510088c78c229bda0431268b43b27 + md5: 4b53d436f3fbc02ce3eeaf8ae9bebe01 depends: - - anyio >=3.1.0 - - argon2-cffi >=21.1 - - jinja2 >=3.0.3 - - jupyter_client >=7.4.4 - - jupyter_core >=4.12,!=5.0.* - - jupyter_events >=0.11.0 - - jupyter_server_terminals >=0.4.4 - - nbconvert-core >=6.4.4 - - nbformat >=5.3.0 - - overrides >=5.0 - - packaging >=22.0 - - prometheus_client >=0.9 + - appnope + - __osx + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.8.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio >=1.4 + - packaging >=22 + - psutil >=5.7 - python >=3.10 - - pyzmq >=24 - - send2trash >=1.8.2 - - terminado >=0.8.3 - - tornado >=6.2.0 - - traitlets >=5.6.0 - - websocket-client >=1.7 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 - python + constrains: + - appnope >=0.1.2 license: BSD-3-Clause + license_family: BSD purls: - - pkg:pypi/jupyter-server?source=compressed-mapping - size: 360522 - timestamp: 1778060967727 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda - sha256: 5eda79ed9f53f590031d29346abd183051263227dd9ee667b5ca1133ce297654 - md5: 7b8bace4943e0dc345fc45938826f2b8 + - pkg:pypi/ipykernel?source=hash-mapping + size: 132260 + timestamp: 1770566135697 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyh6dadd2b_1.conda + sha256: 9cdadaeef5abadca4113f92f5589db19f8b7df5e1b81cb0225f7024a3aedefa3 + md5: b3a7d5842f857414d9ae831a799444dd depends: + - __win + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.8.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio >=1.4 + - packaging >=22 + - psutil >=5.7 - python >=3.10 - - terminado >=0.8.3 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 - python + constrains: + - appnope >=0.1.2 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyter-server-terminals?source=hash-mapping - size: 22052 - timestamp: 1768574057200 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.5.7-pyhd8ed1ab_0.conda - sha256: b85befad5ba1f50c0cc042a2ffb26441d13ffc2f18572dc20d3541476da0c7b9 - md5: 2ffe77234070324e763a6eddabb5f467 + - pkg:pypi/ipykernel?source=hash-mapping + size: 132382 + timestamp: 1770566174387 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.2.0-pyha191276_1.conda + sha256: b77ed58eb235e5ad80e742b03caeed4bbc2a2ef064cb9a2deee3b75dfae91b2a + md5: 8b267f517b81c13594ed68d646fd5dcb depends: - - async-lru >=1.0.0 - - httpx >=0.25.0,<1 - - ipykernel >=6.5.0,!=6.30.0 - - jinja2 >=3.0.3 - - jupyter-lsp >=2.0.0 - - jupyter_core - - jupyter_server >=2.4.0,<3 - - jupyterlab_server >=2.28.0,<3 - - notebook-shim >=0.2 - - packaging + - __linux + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.8.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio >=1.4 + - packaging >=22 + - psutil >=5.7 - python >=3.10 - - setuptools >=41.1.0 - - tomli >=1.2.2 - - tornado >=6.2.0 - - traitlets + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 license: BSD-3-Clause license_family: BSD purls: - - 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 - sha256: 45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8 - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda - sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 - md5: fd312693df06da3578383232528c468d + - pkg:pypi/ipykernel?source=hash-mapping + size: 133644 + timestamp: 1770566133040 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyh53cf698_0.conda + sha256: a0af49948a1842dfd15a0b0b2fd56c94ddbd07e07a6c8b4bc70d43015eafaff0 + md5: 73e9657cd19605740d21efb14d8d0cb9 depends: - - pygments >=2.4.1,<3 - - python >=3.9 - constrains: - - jupyterlab >=4.0.8,<5.0.0 + - __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.11 + - stack_data >=0.6.0 + - traitlets >=5.13.0 + - typing_extensions >=4.6 + - pexpect >4.6 + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyterlab-pygments?source=hash-mapping - size: 18711 - timestamp: 1733328194037 -- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda - sha256: 381d2d6a259a3be5f38a69463e0f6c5dcf1844ae113058007b51c3bef13a7cee - md5: a63877cb23de826b1620d3adfccc4014 + - pkg:pypi/ipython?source=compressed-mapping + size: 651632 + timestamp: 1777038396606 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.13.0-pyhe2676ad_0.conda + sha256: f252ec33597115ff21cbb31051f6f9be34ca36cbbbf3d266b597660d8d8edde9 + md5: 5631ab99e902463d9dd4221e5b4eab6d depends: - - babel >=2.10 - - jinja2 >=3.0.3 - - json5 >=0.9.0 - - jsonschema >=4.18 - - jupyter_server >=1.21,<3 - - packaging >=21.3 - - python >=3.10 - - requests >=2.31 + - __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.11 + - stack_data >=0.6.0 + - traitlets >=5.13.0 + - typing_extensions >=4.6 + - colorama >=0.4.4 - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/jupyterlab-server?source=hash-mapping - size: 51621 - timestamp: 1761145478692 -- pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl - name: jupyterquiz - version: 2.9.6.4 - sha256: f8c4418f6c827454523fc882a30d744b585cb58ac1ae277769c3059d04fc272b -- pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - name: jupytext - version: 1.19.1 - sha256: d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9 - requires_dist: - - markdown-it-py>=1.0 - - mdit-py-plugins - - nbformat - - packaging - - pyyaml - - tomli ; python_full_version < '3.11' - - autopep8 ; extra == 'dev' - - black ; extra == 'dev' - - flake8 ; extra == 'dev' - - gitpython ; extra == 'dev' - - ipykernel ; extra == 'dev' - - isort ; extra == 'dev' - - jupyter-fs[fs]>=1.0 ; extra == 'dev' - - jupyter-server!=2.11 ; extra == 'dev' - - marimo>=0.17.6,<=0.19.4 ; extra == 'dev' - - nbconvert ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-asyncio ; extra == 'dev' - - pytest-cov>=2.6.1 ; extra == 'dev' - - pytest-randomly ; extra == 'dev' - - pytest-xdist ; extra == 'dev' - - sphinx ; extra == 'dev' - - sphinx-gallery>=0.8 ; extra == 'dev' - - myst-parser ; extra == 'docs' - - sphinx ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-rtd-theme ; extra == 'docs' - - pytest ; extra == 'test' - - pytest-asyncio ; extra == 'test' - - pytest-randomly ; extra == 'test' - - pytest-xdist ; extra == 'test' - - black ; extra == 'test-cov' - - ipykernel ; extra == 'test-cov' - - jupyter-server!=2.11 ; extra == 'test-cov' - - nbconvert ; extra == 'test-cov' - - pytest ; extra == 'test-cov' - - pytest-asyncio ; extra == 'test-cov' - - pytest-cov>=2.6.1 ; extra == 'test-cov' - - pytest-randomly ; extra == 'test-cov' - - pytest-xdist ; extra == 'test-cov' - - autopep8 ; extra == 'test-external' - - black ; extra == 'test-external' - - flake8 ; extra == 'test-external' - - gitpython ; extra == 'test-external' - - ipykernel ; extra == 'test-external' - - isort ; extra == 'test-external' - - jupyter-fs[fs]>=1.0 ; extra == 'test-external' - - jupyter-server!=2.11 ; extra == 'test-external' - - marimo>=0.17.6,<=0.19.4 ; extra == 'test-external' - - nbconvert ; extra == 'test-external' - - pre-commit ; extra == 'test-external' - - pytest ; extra == 'test-external' - - pytest-asyncio ; extra == 'test-external' - - pytest-randomly ; extra == 'test-external' - - pytest-xdist ; extra == 'test-external' - - sphinx ; extra == 'test-external' - - sphinx-gallery>=0.8 ; extra == 'test-external' - - black ; extra == 'test-functional' - - pytest ; extra == 'test-functional' - - pytest-asyncio ; extra == 'test-functional' - - pytest-randomly ; extra == 'test-functional' - - pytest-xdist ; extra == 'test-functional' - - black ; extra == 'test-integration' - - ipykernel ; extra == 'test-integration' - - jupyter-server!=2.11 ; extra == 'test-integration' - - nbconvert ; extra == 'test-integration' - - pytest ; extra == 'test-integration' - - pytest-asyncio ; extra == 'test-integration' - - pytest-randomly ; extra == 'test-integration' - - pytest-xdist ; extra == 'test-integration' - - bash-kernel ; extra == 'test-ui' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 - md5: b38117a3c920364aff79f870c984b4a3 + - 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 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-or-later - purls: [] - size: 134088 - timestamp: 1754905959823 -- pypi: https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl - name: kiwisolver - version: 1.5.0 - sha256: 0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: kiwisolver - version: 1.5.0 - sha256: 332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: kiwisolver - version: 1.5.0 - sha256: 2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl - name: kiwisolver - version: 1.5.0 - sha256: dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl - name: kiwisolver - version: 1.5.0 - sha256: beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl - name: kiwisolver - version: 1.5.0 - sha256: 0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - sha256: 3e307628ca3527448dd1cb14ad7bb9d04d1d28c7d4c5f97ba196ae984571dd25 - md5: fb53fb07ce46a575c5d004bbc96032c2 + - pygments + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython-pygments-lexers?source=hash-mapping + size: 13993 + timestamp: 1737123723464 +- conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed + md5: 0b0154421989637d424ccf0f104be51a depends: - - __glibc >=2.17,<3.0.a0 - - keyutils >=1.6.3,<2.0a0 - - libedit >=3.1.20250104,<3.2.0a0 - - libedit >=3.1.20250104,<4.0a0 - - libgcc >=14 - - libstdcxx >=14 - - openssl >=3.5.5,<4.0a0 + - arrow >=0.15.0 + - python >=3.9 license: MIT license_family: MIT - purls: [] - size: 1386730 - timestamp: 1769769569681 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda - sha256: c0a0bf028fe7f3defcdcaa464e536cf1b202d07451e18ad83fdd169d15bef6ed - md5: e446e1822f4da8e5080a9de93474184d + purls: + - pkg:pypi/isoduration?source=hash-mapping + size: 19832 + timestamp: 1733493720346 +- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda + sha256: 92c4d217e2dc68983f724aa983cca5464dcb929c566627b26a2511159667dba8 + md5: a4f4c5dc9b80bc50e0d3dc4e6e8f1bd9 depends: - - __osx >=11.0 - - libcxx >=19 - - libedit >=3.1.20250104,<3.2.0a0 - - libedit >=3.1.20250104,<4.0a0 - - openssl >=3.5.5,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1160828 - timestamp: 1769770119811 -- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - sha256: eb60f1ad8b597bcf95dee11bc11fe71a8325bc1204cf51d2bb1f2120ffd77761 - md5: 4432f52dc0c8eb6a7a6abc00a037d93c - depends: - - openssl >=3.5.5,<4.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: [] - size: 751055 - timestamp: 1769769688841 -- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda - sha256: 49570840fb15f5df5d4b4464db8ee43a6d643031a2bc70ef52120a52e3809699 - md5: 9b965c999135d43a3d0f7bd7d024e26a + - parso >=0.8.3,<0.9.0 + - python >=3.9 + license: Apache-2.0 AND MIT + purls: + - pkg:pypi/jedi?source=hash-mapping + size: 843646 + timestamp: 1733300981994 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d depends: + - markupsafe >=2.0 - python >=3.10 - license: MIT - license_family: MIT + - python + license: BSD-3-Clause + license_family: BSD purls: - - 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 - name: lazy-loader - version: '0.5' - sha256: ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005 - requires_dist: - - packaging - - pytest>=8.0 ; extra == 'test' - - pytest-cov>=5.0 ; extra == 'test' - - coverage[toml]>=7.2 ; extra == 'test' - - pre-commit==4.3.0 ; extra == 'lint' - - changelist==0.5 ; extra == 'dev' - - spin==0.15 ; extra == 'dev' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c - md5: 18335a698559cdbcd86150a48bf54ba6 - depends: - - __glibc >=2.17,<3.0.a0 - - zstd >=1.5.7,<1.6.0a0 - constrains: - - binutils_impl_linux-64 2.45.1 - license: GPL-3.0-only - license_family: GPL - purls: [] - size: 728002 - timestamp: 1774197446916 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - sha256: a7a4481a4d217a3eadea0ec489826a69070fcc3153f00443aa491ed21527d239 - md5: 6f7b4302263347698fd24565fbf11310 + - pkg:pypi/jinja2?source=compressed-mapping + size: 120685 + timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda + sha256: a36789229ca9ce5315265b9d425abce9acb4691f5864aea69e935020545a9acb + md5: 974c5b3e353f031cfcf2365c9d375926 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - constrains: - - libabseil-static =20260107.1=cxx17* - - abseil-cpp =20260107.1 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 1384817 - timestamp: 1770863194876 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - sha256: 756611fbb8d2957a5b4635d9772bd8432cb6ddac05580a6284cca6fdc9b07fca - md5: bb65152e0d7c7178c0f1ee25692c9fd1 + - jinja2 + - python >=3.9 + - pyyaml + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2-ansible-filters?source=hash-mapping + size: 20315 + timestamp: 1734906203051 +- conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.14.0-pyhd8ed1ab_0.conda + sha256: 9daa95bd164c8fa23b3ab196e906ef806141d749eddce2a08baa064f722d25fa + md5: 1269891272187518a0a75c286f7d0bbf depends: - - __osx >=11.0 - - libcxx >=19 - constrains: - - abseil-cpp =20260107.1 - - libabseil-static =20260107.1=cxx17* + - python >=3.10 license: Apache-2.0 - license_family: Apache - purls: [] - size: 1229639 - timestamp: 1770863511331 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e - md5: 72c8fd1af66bd67bf580645b426513ed - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 79965 - timestamp: 1764017188531 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda - sha256: a7cb9e660531cf6fbd4148cff608c85738d0b76f0975c5fc3e7d5e92840b7229 - md5: 006e7ddd8a110771134fcc4e1e3a6ffa - depends: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: [] - size: 79443 - timestamp: 1764017945924 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b - md5: 366b40a69f0ad6072561c1d09301c886 + license_family: APACHE + purls: + - pkg:pypi/json5?source=compressed-mapping + size: 34731 + timestamp: 1774655440045 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + sha256: a3d10301b6ff399ba1f3d39e443664804a3d28315a4fb81e745b6817845f70ae + md5: 89bf346df77603055d3c8fe5811691e6 depends: - - __glibc >=2.17,<3.0.a0 - - libbrotlicommon 1.2.0 hb03c661_1 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 34632 - timestamp: 1764017199083 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda - sha256: 2eae444039826db0454b19b52a3390f63bfe24f6b3e63089778dd5a5bf48b6bf - md5: 079e88933963f3f149054eec2c487bc2 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpointer?source=hash-mapping + size: 14190 + timestamp: 1774311356147 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda + sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 + md5: ada41c863af263cc4c5fcbaff7c3e4dc depends: - - __osx >=11.0 - - libbrotlicommon 1.2.0 hc919400_1 + - attrs >=22.2.0 + - jsonschema-specifications >=2023.3.6 + - python >=3.10 + - referencing >=0.28.4 + - rpds-py >=0.25.0 + - python license: MIT license_family: MIT - purls: [] - size: 29452 - timestamp: 1764017979099 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d - md5: 4ffbb341c8b616aa2494b6afb26a0c5f + purls: + - pkg:pypi/jsonschema?source=hash-mapping + size: 82356 + timestamp: 1767839954256 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 + md5: 439cd0f567d697b20a8f45cb70a1005a depends: - - __glibc >=2.17,<3.0.a0 - - libbrotlicommon 1.2.0 hb03c661_1 - - libgcc >=14 + - python >=3.10 + - referencing >=0.31.0 + - python license: MIT license_family: MIT - purls: [] - size: 298378 - timestamp: 1764017210931 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda - sha256: 01436c32bb41f9cb4bcf07dda647ce4e5deb8307abfc3abdc8da5317db8189d1 - md5: b2b7c8288ca1a2d71ff97a8e6a1e8883 + purls: + - pkg:pypi/jsonschema-specifications?source=hash-mapping + size: 19236 + timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_0.conda + sha256: 6886fc61e4e4edd38fd38729976b134e8bd2143f7fce56cc80d7ac7bac99bce1 + md5: 8368d58342d0825f0843dc6acdd0c483 depends: - - __osx >=11.0 - - libbrotlicommon 1.2.0 hc919400_1 + - jsonschema >=4.26.0,<4.26.1.0a0 + - fqdn + - idna + - isoduration + - jsonpointer >1.13 + - rfc3339-validator + - rfc3986-validator >0.1.0 + - rfc3987-syntax >=1.1.0 + - uri-template + - webcolors >=24.6.0 license: MIT license_family: MIT purls: [] - size: 290754 - timestamp: 1764018009077 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_0.conda - sha256: 76c3d3c702593b6ef57dc2f99c082193a99835526634b844f4a78ae3aecdb6c8 - md5: d0a2e921c5a89df73118cff1afed5213 - depends: - - __osx >=11.0 - license: Apache-2.0 WITH LLVM-exception - license_family: Apache - purls: [] - size: 570968 - timestamp: 1778110367423 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 - md5: c277e0a4d549b03ac1e9d6cbbe3d017b + size: 4740 + timestamp: 1767839954258 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda + sha256: 3766e2ae59641c172cec8a821528bfa6bf9543ffaaeb8b358bfd5259dcf18e4e + md5: 0c3b465ceee138b9c39279cc02e5c4a0 depends: - - ncurses - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause + - importlib-metadata >=4.8.3 + - jupyter_server >=1.1.2 + - python >=3.10 + - python + license: BSD-3-Clause license_family: BSD - purls: [] - size: 134676 - timestamp: 1738479519902 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - sha256: 66aa216a403de0bb0c1340a88d1a06adaff66bae2cfd196731aa24db9859d631 - md5: 44083d2d2c2025afca315c7a172eab2b + purls: + - pkg:pypi/jupyter-lsp?source=compressed-mapping + size: 61633 + timestamp: 1775136333147 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.8.0-pyhcf101f3_0.conda + sha256: e402bd119720862a33229624ec23645916a7d47f30e1711a4af9e005162b84f3 + md5: 8a3d6d0523f66cf004e563a50d9392b3 depends: - - ncurses - - __osx >=11.0 - - ncurses >=6.5,<7.0a0 - license: BSD-2-Clause + - jupyter_core >=5.1 + - python >=3.10 + - python-dateutil >=2.8.2 + - pyzmq >=25.0 + - tornado >=6.4.1 + - traitlets >=5.3 + - python + license: BSD-3-Clause license_family: BSD - purls: [] - size: 107691 - timestamp: 1738479560845 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 - md5: 172bf1cd1ff8629f2b1179945ed45055 + purls: + - pkg:pypi/jupyter-client?source=hash-mapping + size: 112785 + timestamp: 1767954655912 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda + sha256: ed709a6c25b731e01563521ef338b93986cd14b5bc17f35e9382000864872ccc + md5: a8db462b01221e9f5135be466faeb3e0 depends: - - libgcc-ng >=12 - license: BSD-2-Clause - license_family: BSD - purls: [] - size: 112766 - timestamp: 1702146165126 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda - sha256: 95cecb3902fbe0399c3a7e67a5bed1db813e5ab0e22f4023a5e0f722f2cc214f - md5: 36d33e440c31857372a72137f78bacf5 - license: BSD-2-Clause + - __win + - pywin32 + - platformdirs >=2.5 + - python >=3.10 + - traitlets >=5.3 + - python + constrains: + - pywin32 >=300 + license: BSD-3-Clause license_family: BSD - purls: [] - size: 107458 - timestamp: 1702146414478 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.0-hecca717_0.conda - sha256: ea33c40977ea7a2c3658c522230058395bc2ee0d89d99f0711390b6a1ee80d12 - md5: a3b390520c563d78cc58974de95a03e5 + purls: + - pkg:pypi/jupyter-core?source=hash-mapping + size: 64679 + timestamp: 1760643889625 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + sha256: 1d34b80e5bfcd5323f104dbf99a2aafc0e5d823019d626d0dce5d3d356a2a52a + md5: b38fe4e78ee75def7e599843ef4c1ab0 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - __unix + - python + - platformdirs >=2.5 + - python >=3.10 + - traitlets >=5.3 + - python constrains: - - expat 2.8.0.* - license: MIT - license_family: MIT - purls: [] - size: 77241 - timestamp: 1777846112704 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.0-hf6b4638_0.conda - sha256: f4b1cafc59afaede8fa0a2d9cf376840f1c553001acd72f6ead18bbc8ac8c49c - md5: 65466e82c09e888ca7560c11a97d5450 + - pywin32 >=300 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-core?source=hash-mapping + size: 65503 + timestamp: 1760643864586 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda + sha256: c7edb5682c6316a95ad781dccb1b6589cd2ec0bf94f23c21152974eb0363b5d7 + md5: bf42ee94c750c0b2e7e998b79ac299ea depends: - - __osx >=11.0 - constrains: - - expat 2.8.0.* - license: MIT - license_family: MIT - purls: [] - size: 68789 - timestamp: 1777846180142 -- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.0-hac47afa_0.conda - sha256: 2d81d647c1f01108803457cac999b947456f44dd0a3c2325395677feacaeca67 - md5: 264e350e035092b5135a2147c238aec4 + - jsonschema-with-format-nongpl >=4.18.0 + - packaging + - python >=3.10 + - python-json-logger >=2.0.4 + - pyyaml >=5.3 + - referencing + - rfc3339-validator + - rfc3986-validator >=0.1.1 + - traitlets >=5.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-events?source=compressed-mapping + size: 24002 + timestamp: 1776861872237 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.18.2-pyhcf101f3_0.conda + sha256: 04fb8ea7749f67abaf76df6257bf86688e1389ceed55eb4fb0176fd2e882dbd6 + md5: 5ee7945accf0f215ddd6055d25d7cd83 depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 + - anyio >=3.1.0 + - argon2-cffi >=21.1 + - jinja2 >=3.0.3 + - jupyter_client >=7.4.4 + - jupyter_core >=4.12,!=5.0.* + - jupyter_events >=0.11.0 + - jupyter_server_terminals >=0.4.4 + - nbconvert-core >=6.4.4 + - nbformat >=5.3.0 + - overrides >=5.0 + - packaging >=22.0 + - prometheus_client >=0.9 + - python >=3.10 + - pyzmq >=24 + - send2trash >=1.8.2 + - terminado >=0.8.3 + - tornado >=6.2.0 + - traitlets >=5.6.0 + - websocket-client >=1.7 + - python + license: BSD-3-Clause + purls: + - pkg:pypi/jupyter-server?source=compressed-mapping + size: 360522 + timestamp: 1778060967727 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + sha256: 5eda79ed9f53f590031d29346abd183051263227dd9ee667b5ca1133ce297654 + md5: 7b8bace4943e0dc345fc45938826f2b8 + depends: + - python >=3.10 + - terminado >=0.8.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-server-terminals?source=hash-mapping + size: 22052 + timestamp: 1768574057200 +- 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 + - ipykernel >=6.5.0,!=6.30.0 + - jinja2 >=3.0.3 + - jupyter-lsp >=2.0.0 + - jupyter_core + - jupyter_server >=2.4.0,<3 + - jupyterlab_server >=2.28.0,<3 + - notebook-shim >=0.2 + - packaging + - python >=3.10 + - setuptools >=41.1.0 + - tomli >=1.2.2 + - tornado >=6.2.0 + - traitlets + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab?source=compressed-mapping + size: 8861204 + timestamp: 1777483115382 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 + md5: fd312693df06da3578383232528c468d + depends: + - pygments >=2.4.1,<3 + - python >=3.9 constrains: - - expat 2.8.0.* - license: MIT - license_family: MIT - purls: [] - size: 71094 - timestamp: 1777846223617 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 - md5: a360c33a5abe61c07959e449fa1453eb + - jupyterlab >=4.0.8,<5.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-pygments?source=hash-mapping + size: 18711 + timestamp: 1733328194037 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + sha256: 381d2d6a259a3be5f38a69463e0f6c5dcf1844ae113058007b51c3bef13a7cee + md5: a63877cb23de826b1620d3adfccc4014 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - purls: [] - size: 58592 - timestamp: 1769456073053 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - sha256: 6686a26466a527585e6a75cc2a242bf4a3d97d6d6c86424a441677917f28bec7 - md5: 43c04d9cb46ef176bb2a4c77e324d599 + - babel >=2.10 + - jinja2 >=3.0.3 + - json5 >=0.9.0 + - jsonschema >=4.18 + - jupyter_server >=1.21,<3 + - packaging >=21.3 + - python >=3.10 + - requests >=2.31 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-server?source=hash-mapping + size: 51621 + timestamp: 1761145478692 +- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + sha256: 49570840fb15f5df5d4b4464db8ee43a6d643031a2bc70ef52120a52e3809699 + md5: 9b965c999135d43a3d0f7bd7d024e26a depends: - - __osx >=11.0 + - python >=3.10 license: MIT license_family: MIT - purls: [] - size: 40979 - timestamp: 1769456747661 -- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - sha256: 59d01f2dfa8b77491b5888a5ab88ff4e1574c9359f7e229da254cdfe27ddc190 - md5: 720b39f5ec0610457b725eb3f396219a + purls: + - pkg:pypi/lark?source=hash-mapping + size: 94312 + timestamp: 1761596921009 +- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.1-pyhd8ed1ab_0.conda + sha256: 9d690334de0cd1d22c51bc28420663f4277cfa60d34fa5cad1ce284a13f1d603 + md5: 00e120ce3e40bad7bfc78861ce3c4a25 depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: MIT - license_family: MIT - purls: [] - size: 45831 - timestamp: 1769456418774 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5 - md5: 0aa00f03f9e39fb9876085dee11a85d4 + - python >=3.10 + - traitlets + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/matplotlib-inline?source=hash-mapping + size: 15175 + timestamp: 1761214578417 +- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda + sha256: b52dc6c78fbbe7a3008535cb8bfd87d70d8053e9250bbe16e387470a9df07070 + md5: b97e84d1553b4a1c765b87fff83453ad depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex >=4.5 - constrains: - - libgcc-ng ==15.2.0=*_18 - - libgomp 15.2.0 he0feb66_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 1041788 - timestamp: 1771378212382 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893 - md5: d5e96b1ed75ca01906b3d2469b4ce493 - depends: - - libgcc 15.2.0 he0feb66_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 27526 - timestamp: 1771378224552 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda - sha256: 21337ab58e5e0649d869ab168d4e609b033509de22521de1bfed0c031bfc5110 - md5: 239c5e9546c38a1e884d69effcf4c882 - depends: - - __glibc >=2.17,<3.0.a0 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 603262 - timestamp: 1771378117851 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d - md5: b88d90cad08e6bc8ad540cb310a761fb + - python >=3.10 + - typing_extensions + - python + license: BSD-3-Clause + purls: + - pkg:pypi/mistune?source=compressed-mapping + size: 74567 + timestamp: 1777824616382 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda + sha256: 1b66960ee06874ddceeebe375d5f17fb5f393d025a09e15b830ad0c4fffb585b + md5: 00f5b8dafa842e0c27c1cd7296aa4875 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - xz 5.8.3.* - license: 0BSD - purls: [] - size: 113478 - timestamp: 1775825492909 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda - sha256: 34878d87275c298f1a732c6806349125cebbf340d24c6c23727268184bba051e - md5: b1fd823b5ae54fbec272cea0811bd8a9 + - jupyter_client >=6.1.12 + - jupyter_core >=4.12,!=5.0.* + - nbformat >=5.1 + - python >=3.8 + - traitlets >=5.4 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbclient?source=compressed-mapping + size: 28473 + timestamp: 1766485646962 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda + sha256: ab2ac79c5892c5434d50b3542d96645bdaa06d025b6e03734be29200de248ac2 + md5: 2bce0d047658a91b99441390b9b27045 depends: - - __osx >=11.0 + - beautifulsoup4 + - bleach-with-css !=5.0.0 + - defusedxml + - importlib-metadata >=3.6 + - jinja2 >=3.0 + - jupyter_core >=4.7 + - jupyterlab_pygments + - markupsafe >=2.0 + - mistune >=2.0.3,<4 + - nbclient >=0.5.0 + - nbformat >=5.7 + - packaging + - pandocfilters >=1.4.1 + - pygments >=2.4.1 + - python >=3.10 + - traitlets >=5.1 + - python constrains: - - xz 5.8.3.* - license: 0BSD - purls: [] - size: 92472 - timestamp: 1775825802659 -- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda - sha256: d636d1a25234063642f9c531a7bb58d84c1c496411280a36ea000bd122f078f1 - md5: 8f83619ab1588b98dd99c90b0bfc5c6d + - pandoc >=2.9.2,<4.0.0 + - nbconvert ==7.17.1 *_0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbconvert?source=compressed-mapping + size: 202229 + timestamp: 1775615493260 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda + sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 + md5: bbe1963f1e47f594070ffe87cdf612ea depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - xz 5.8.3.* - license: 0BSD - purls: [] - size: 106486 - timestamp: 1775825663227 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 - md5: 2c21e66f50753a083cbe6b80f38268fa + - jsonschema >=2.6 + - jupyter_core >=4.12,!=5.0.* + - python >=3.9 + - python-fastjsonschema >=2.15 + - traitlets >=5.1 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbformat?source=hash-mapping + size: 100945 + timestamp: 1733402844974 +- conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda + sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 + md5: 598fd7d4d0de2455fb74f56063969a97 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - python >=3.9 license: BSD-2-Clause license_family: BSD - purls: [] - size: 92400 - timestamp: 1769482286018 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda - sha256: 1089c7f15d5b62c622625ec6700732ece83be8b705da8c6607f4dabb0c4bd6d2 - md5: 57c4be259f5e0b99a5983799a228ae55 + purls: + - pkg:pypi/nest-asyncio?source=hash-mapping + size: 11543 + timestamp: 1733325673691 +- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + sha256: 7b920e46b9f7a2d2aa6434222e5c8d739021dbc5cc75f32d124a8191d86f9056 + md5: e7f89ea5f7ea9401642758ff50a2d9c1 depends: - - __osx >=11.0 - license: BSD-2-Clause + - jupyter_server >=1.8,<3 + - python >=3.9 + license: BSD-3-Clause license_family: BSD - purls: [] - size: 73690 - timestamp: 1769482560514 -- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - sha256: 40dcd0b9522a6e0af72a9db0ced619176e7cfdb114855c7a64f278e73f8a7514 - md5: e4a9fc2bba3b022dad998c78856afe47 + purls: + - pkg:pypi/notebook-shim?source=hash-mapping + size: 16817 + timestamp: 1733408419340 +- conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c + md5: e51f1e4089cad105b6cac64bd8166587 depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-2-Clause + - python >=3.9 + - typing_utils + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/overrides?source=hash-mapping + size: 30139 + timestamp: 1734587755455 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-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=compressed-mapping + size: 91574 + timestamp: 1777103621679 +- conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f + md5: 457c2c8c08e54905d6954e79cb5b5db9 + depends: + - python !=3.0,!=3.1,!=3.2,!=3.3 + license: BSD-3-Clause license_family: BSD - purls: [] - size: 89411 - timestamp: 1769482314283 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - sha256: 663444d77a42f2265f54fb8b48c5450bfff4388d9c0f8253dd7855f0d993153f - md5: 2a45e7f8af083626f009645a6481f12d + purls: + - pkg:pypi/pandocfilters?source=hash-mapping + size: 11627 + timestamp: 1631603397334 +- conda: https://conda.anaconda.org/conda-forge/noarch/paramiko-4.0.0-pyhd8ed1ab_0.conda + sha256: ce76d5a1fc6c7ef636cbdbf14ce2d601a1bfa0dd8d286507c1fd02546fccb94b + md5: 1a884d2b1ea21abfb73911dcdb8342e4 depends: - - __glibc >=2.17,<3.0.a0 - - c-ares >=1.34.6,<2.0a0 - - libev >=4.33,<4.34.0a0 - - libev >=4.33,<5.0a0 - - libgcc >=14 - - libstdcxx >=14 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 - license: MIT - license_family: MIT - purls: [] - size: 663344 - timestamp: 1773854035739 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda - sha256: 2bc7bc3978066f2c274ebcbf711850cc9ab92e023e433b9631958a098d11e10a - md5: 6ea18834adbc3b33df9bd9fb45eaf95b + - bcrypt >=3.2 + - cryptography >=3.3 + - invoke >=2.0 + - pynacl >=1.5 + - python >=3.9 + license: LGPL-2.1-or-later + license_family: LGPL + purls: + - pkg:pypi/paramiko?source=hash-mapping + size: 159896 + timestamp: 1755102147074 +- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + sha256: 611882f7944b467281c46644ffde6c5145d1a7730388bcde26e7e86819b0998e + md5: 39894c952938276405a1bd30e4ce2caf depends: - - __osx >=11.0 - - c-ares >=1.34.6,<2.0a0 - - libcxx >=19 - - libev >=4.33,<4.34.0a0 - - libev >=4.33,<5.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 + - python >=3.10 + - python license: MIT license_family: MIT - purls: [] - size: 576526 - timestamp: 1773854624224 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 - md5: d864d34357c3b65a4b731f78c0801dc4 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: LGPL-2.1-only - license_family: GPL - purls: [] - size: 33731 - timestamp: 1750274110928 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda - sha256: 64e5c80cbce4680a2d25179949739a6def695d72c40ca28f010711764e372d97 - md5: 7af961ef4aa2c1136e11dd43ded245ab + purls: + - pkg:pypi/parso?source=compressed-mapping + size: 82472 + timestamp: 1777722955579 +- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.1.1-pyhd8ed1ab_0.conda + sha256: 6eaee417d33f298db79bc7185ab1208604c0e6cf51dade34cd513c6f9db9c6f3 + md5: 11adc78451c998c0fd162584abfa3559 depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - license: ISC - purls: [] - size: 277661 - timestamp: 1772479381288 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda - sha256: df603472ea1ebd8e7d4fb71e4360fe48d10b11c240df51c129de1da2ff9e8227 - md5: 7cc5247987e6d115134ebab15186bc13 + - python >=3.10 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/pathspec?source=compressed-mapping + size: 56559 + timestamp: 1777271601895 +- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a + md5: d0d408b1f18883a944376da5cf8101ea depends: - - __osx >=11.0 + - ptyprocess >=0.5 + - python >=3.9 license: ISC - purls: [] - size: 248039 - timestamp: 1772479570912 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda - sha256: d915f4fa8ebbf237c7a6e511ed458f2cfdc7c76843a924740318a15d0dd33d6d - md5: da2aa614d16a795b3007b6f4a1318a81 + purls: + - pkg:pypi/pexpect?source=hash-mapping + size: 53561 + timestamp: 1733302019362 +- conda: https://conda.anaconda.org/conda-forge/noarch/pixi-kernel-0.7.1-pyhbbac1ac_0.conda + sha256: 506c9330b8dc5ae98f4c32629fa59fa40e6bdd42a681c48d2f9554693dd01156 + md5: d57ef7cb7ad6b5d62cef8b9bdf1d400b depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - license: ISC - purls: [] - size: 276860 - timestamp: 1772479407566 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda - sha256: 54cdcd3214313b62c2a8ee277e6f42150d9b748264c1b70d958bf735e420ef8d - md5: 7dc38adcbf71e6b38748e919e16e0dce + - ipykernel >=6 + - jupyter_client >=7 + - jupyter_server >=2.4 + - msgspec >=0.18 + - python >=3.10 + - returns >=0.23 + - tomli >=2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pixi-kernel?source=hash-mapping + size: 39509 + timestamp: 1764156429044 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda + sha256: 8f29915c172f1f7f4f7c9391cd5dac3ebf5d13745c8b7c8006032615246345a5 + md5: 89c0b6d1793601a2a3a3f7d2d3d8b937 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libzlib >=1.3.2,<2.0a0 - license: blessing - purls: [] - size: 954962 - timestamp: 1777986471789 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda - sha256: 49daec7c83e70d4efc17b813547824bc2bcf2f7256d84061d24fbfe537da9f74 - md5: 6681822ea9d362953206352371b6a904 + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/platformdirs?source=compressed-mapping + size: 25862 + timestamp: 1775741140609 +- conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda + sha256: 972e0c1c9f0b1763d482e885732baef7f9a0cbe8686f5e2c6bdd88838619a59a + md5: 7fd2e38f4e608f5ebd80f871352c5495 depends: - - __osx >=11.0 - - libzlib >=1.3.2,<2.0a0 - license: blessing - purls: [] - size: 920047 - timestamp: 1777987051643 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda - sha256: e70562450332ca8954bc16f3455468cca5ef3695c7d7187ecc87f8fc3c70e9eb - md5: 7fea434a17c323256acc510a041b80d7 + - python >=3.10 + - pywin32-on-windows + - paramiko + - importlib_resources + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/plumbum?source=hash-mapping + size: 103911 + timestamp: 1761911487086 +- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda + sha256: 4d7ec90d4f9c1f3b4a50623fefe4ebba69f651b102b373f7c0e9dbbfa43d495c + md5: a11ab1f31af799dd93c3a39881528884 depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: blessing - purls: [] - size: 1304178 - timestamp: 1777986510497 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda - sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e - md5: 1b08cd684f34175e4514474793d44bcb + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/prometheus-client?source=compressed-mapping + size: 57113 + timestamp: 1775771465170 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: d17ae9db4dc594267181bd199bf9a551 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc 15.2.0 he0feb66_18 + - python >=3.9 + - wcwidth constrains: - - libstdcxx-ng ==15.2.0=*_18 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - purls: [] - size: 5852330 - timestamp: 1771378262446 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda - sha256: bc1b08c92626c91500fd9f26f2c797f3eb153b627d53e9c13cd167f1e12b2829 - md5: 38ffe67b78c9d4de527be8315e5ada2c + - prompt_toolkit 3.0.51 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/prompt-toolkit?source=hash-mapping + size: 271841 + timestamp: 1744724188108 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda + sha256: 936189f0373836c1c77cd2d6e71ba1e583e2d3920bf6d015e96ee2d729b5e543 + md5: 1e61ab85dd7c60e5e73d853ea035dc29 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - prompt-toolkit >=3.0.51,<3.0.52.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 40297 - timestamp: 1775052476770 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda - sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b - md5: 0f03292cc56bf91a077a134ea8747118 + size: 7182 + timestamp: 1744724189376 +- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 + md5: 7d9daffbb8d8e0af0f769dbbcd173a54 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - python >=3.9 + license: ISC + purls: + - pkg:pypi/ptyprocess?source=hash-mapping + size: 19457 + timestamp: 1733302371990 +- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 + md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 + depends: + - python >=3.9 license: MIT license_family: MIT - purls: [] - size: 895108 - timestamp: 1753948278280 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda - sha256: 042c7488ad97a5629ec0a991a8b2a3345599401ecc75ad6a5af73b60e6db9689 - md5: c0d87c3c8e075daf1daf6c31b53e8083 + purls: + - pkg:pypi/pure-eval?source=hash-mapping + size: 16668 + timestamp: 1733569518868 +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda + sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 + md5: 12c566707c80111f9799308d9e265aef depends: - - __osx >=11.0 + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + size: 110100 + timestamp: 1733195786147 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + sha256: 69700e31165df070e9716315e042196aa92525dae5deb5107785847ab9f4189f + md5: 729843edafc0899b3348bd3f19525b9d + depends: + - typing-inspection >=0.4.2 + - typing_extensions >=4.14.1 + - python >=3.10 + - annotated-types >=0.6.0 + - pydantic-core ==2.46.4 + - python license: MIT license_family: MIT - purls: [] - size: 421195 - timestamp: 1753948426421 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c - md5: 5aa797f8787fe7a17d1b0821485b5adc + purls: + - pkg:pypi/pydantic?source=compressed-mapping + size: 346511 + timestamp: 1778103405862 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda + sha256: cf70b2f5ad9ae472b71235e5c8a736c9316df3705746de419b59d442e8348e86 + md5: 16c18772b340887160c79a6acc022db0 depends: - - libgcc-ng >=12 - license: LGPL-2.1-or-later - purls: [] - size: 100393 - timestamp: 1702724383534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 - md5: d87ff7921124eccd67248aa483c23fec + - python >=3.10 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pygments?source=compressed-mapping + size: 893031 + timestamp: 1774796815820 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca + md5: e2fd202833c4a981ce8a65974fe4abd1 depends: - - __glibc >=2.17,<3.0.a0 - constrains: - - zlib 1.3.2 *_2 - license: Zlib - license_family: Other + - __win + - python >=3.9 + - win_inet_pton + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21784 + timestamp: 1733217448189 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + size: 21085 + timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 + md5: 5b8d21249ff20967101ffa321cab24e8 + depends: + - python >=3.9 + - six >=1.5 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/python-dateutil?source=hash-mapping + size: 233310 + timestamp: 1751104122689 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda + sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 + md5: 23029aae904a2ba587daba708208012f + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/fastjsonschema?source=hash-mapping + size: 244628 + timestamp: 1755304154927 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda + sha256: b2e51d83e5ebeb7e9fde1cde822a60e8564cc9dabd786ad853056afbf708a466 + md5: fd00e4b24ea88093c93f5c9bad27b52f + depends: + - cpython 3.13.13.* + - python_abi * *_cp313 + license: Python-2.0 purls: [] - size: 63629 - timestamp: 1774072609062 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - sha256: 361415a698514b19a852f5d1123c5da746d4642139904156ddfca7c922d23a05 - md5: bc5a5721b6439f2f62a84f2548136082 + size: 48536 + timestamp: 1775613791711 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda + sha256: 1c55116c22512cef7b01d55ae49697707f2c1fd829407183c19817e2d300fd8d + md5: 1cd2f3e885162ee1366312bd1b1677fd depends: - - __osx >=11.0 + - python >=3.10 + - typing_extensions + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/python-json-logger?source=compressed-mapping + size: 18969 + timestamp: 1777318679482 +- 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: 146639 + timestamp: 1777068997932 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda + build_number: 8 + sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 + md5: 8fcb6b0e2161850556231336dae58358 constrains: - - zlib 1.3.2 *_2 - license: Zlib - license_family: Other + - python 3.11.* *_cpython + license: BSD-3-Clause + license_family: BSD purls: [] - size: 47759 - timestamp: 1774072956767 -- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 - md5: dbabbd6234dea34040e631f87676292f - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 + size: 7003 + timestamp: 1752805919375 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + build_number: 8 + sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 + md5: 94305520c52a4aa3f6c2b1ff6008d9f8 constrains: - - zlib 1.3.2 *_2 - license: Zlib - license_family: Other + - python 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD purls: [] - size: 58347 - timestamp: 1774072851498 -- pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: llvmlite - version: 0.47.0 - sha256: 2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/34/0b/b9d1911cfefa61399821dfb37f486d83e0f42630a8d12f7194270c417002/llvmlite-0.47.0-cp311-cp311-macosx_11_0_arm64.whl - name: llvmlite - version: 0.47.0 - sha256: 74090f0dcfd6f24ebbef3f21f11e38111c4d7e6919b54c4416e1e357c3446b07 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: llvmlite - version: 0.47.0 - sha256: ca14f02e29134e837982497959a8e2193d6035235de1cb41a9cb2bd6da4eedbb - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl - name: llvmlite - version: 0.47.0 - sha256: c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl - name: llvmlite - version: 0.47.0 - sha256: a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl - name: llvmlite - version: 0.47.0 - sha256: c37d6eb7aaabfa83ab9c2ff5b5cdb95a5e6830403937b2c588b7490724e05327 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - name: lmfit - version: 1.3.4 - sha256: afce1593b42324d37ae2908249b0c55445e2f4c1a0474ff706a8e2f7b5d949fa - requires_dist: - - asteval>=1.0 - - numpy>=1.24 - - scipy>=1.10.0 - - uncertainties>=3.2.2 - - dill>=0.3.4 - - build ; extra == 'dev' - - check-wheel-contents ; extra == 'dev' - - flake8-pyproject ; extra == 'dev' - - pre-commit ; extra == 'dev' - - twine ; extra == 'dev' - - cairosvg ; extra == 'doc' - - corner ; extra == 'doc' - - emcee>=3.0.0 ; extra == 'doc' - - ipykernel ; extra == 'doc' - - jupyter-sphinx>=0.2.4 ; extra == 'doc' - - matplotlib ; extra == 'doc' - - numdifftools ; extra == 'doc' - - pandas ; extra == 'doc' - - pillow ; extra == 'doc' - - pycairo ; sys_platform == 'win32' and extra == 'doc' - - sphinx ; extra == 'doc' - - sphinx-gallery>=0.10 ; extra == 'doc' - - sphinxcontrib-svg2pdfconverter ; extra == 'doc' - - sympy ; extra == 'doc' - - coverage ; extra == 'test' - - flaky ; extra == 'test' - - pytest ; extra == 'test' - - pytest-cov ; extra == 'test' - - lmfit[dev,doc,test] ; extra == 'all' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - name: locket - version: 1.0.0 - sha256: b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3 - requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' -- pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl - name: lxml - version: 6.1.0 - sha256: 183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7 - requires_dist: - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl - name: lxml - version: 6.1.0 - sha256: a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45 - requires_dist: - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl - name: lxml - version: 6.1.0 - sha256: cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9 - requires_dist: - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl - name: lxml - version: 6.1.0 - sha256: 4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16 - requires_dist: - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - name: lxml - version: 6.1.0 - sha256: 363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c - requires_dist: - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - name: lxml - version: 6.1.0 - sha256: e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2 - requires_dist: - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - name: mando - version: 0.7.1 - sha256: 26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a - requires_dist: - - six - - argparse ; python_full_version < '2.7' - - funcsigs ; python_full_version < '3.3' - - rst2ansi ; extra == 'restructuredtext' -- pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - name: markdown - version: 3.10.2 - sha256: e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36 - requires_dist: - - coverage ; extra == 'testing' - - pyyaml ; extra == 'testing' - - mkdocs>=1.6 ; extra == 'docs' - - mkdocs-nature>=0.6 ; extra == 'docs' - - mdx-gh-links>=0.2 ; extra == 'docs' - - mkdocstrings[python]>=0.28.3 ; extra == 'docs' - - mkdocs-gen-files ; extra == 'docs' - - mkdocs-section-index ; extra == 'docs' - - mkdocs-literate-nav ; extra == 'docs' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl - name: markdown-it-py - version: 4.1.0 - sha256: d4939a62a2dd0cd9cb80a191a711ba1d39bac8ed5ef9e9966895b0171c01c46d - requires_dist: - - mdurl~=0.1 - - psutil ; extra == 'benchmarking' - - pytest ; extra == 'benchmarking' - - pytest-benchmark ; extra == 'benchmarking' - - commonmark~=0.9 ; extra == 'compare' - - markdown~=3.4 ; extra == 'compare' - - mistletoe~=1.0 ; extra == 'compare' - - mistune~=3.0 ; extra == 'compare' - - panflute~=2.3 ; extra == 'compare' - - markdown-it-pyrs ; extra == 'compare' - - linkify-it-py>=1,<3 ; extra == 'linkify' - - mdit-py-plugins>=0.5.0 ; extra == 'plugins' - - gprof2dot ; extra == 'profiling' - - mdit-py-plugins>=0.5.0 ; extra == 'rtd' - - myst-parser ; extra == 'rtd' - - pyyaml ; extra == 'rtd' - - sphinx ; extra == 'rtd' - - sphinx-copybutton ; extra == 'rtd' - - sphinx-design ; extra == 'rtd' - - sphinx-book-theme~=1.0 ; extra == 'rtd' - - jupyter-sphinx ; extra == 'rtd' - - ipykernel ; extra == 'rtd' - - coverage ; extra == 'testing' - - pytest ; extra == 'testing' - - pytest-cov ; extra == 'testing' - - pytest-regressions ; extra == 'testing' - - pytest-timeout ; extra == 'testing' - - requests ; extra == 'testing' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl - name: markdown-it-py - version: 4.2.0 - sha256: 9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a - requires_dist: - - mdurl~=0.1 - - psutil ; extra == 'benchmarking' - - pytest ; extra == 'benchmarking' - - pytest-benchmark ; extra == 'benchmarking' - - commonmark~=0.9 ; extra == 'compare' - - markdown~=3.4 ; extra == 'compare' - - mistletoe~=1.0 ; extra == 'compare' - - mistune~=3.0 ; extra == 'compare' - - panflute~=2.3 ; extra == 'compare' - - markdown-it-pyrs ; extra == 'compare' - - linkify-it-py>=1,<3 ; extra == 'linkify' - - mdit-py-plugins>=0.5.0 ; extra == 'plugins' - - gprof2dot ; extra == 'profiling' - - mdit-py-plugins>=0.5.0 ; extra == 'rtd' - - myst-parser ; extra == 'rtd' - - pyyaml ; extra == 'rtd' - - sphinx ; extra == 'rtd' - - sphinx-copybutton ; extra == 'rtd' - - sphinx-design ; extra == 'rtd' - - sphinx-book-theme~=1.0 ; extra == 'rtd' - - jupyter-sphinx ; extra == 'rtd' - - ipykernel ; extra == 'rtd' - - coverage ; extra == 'testing' - - pytest ; extra == 'testing' - - pytest-cov ; extra == 'testing' - - pytest-regressions ; extra == 'testing' - - pytest-timeout ; extra == 'testing' - - requests ; extra == 'testing' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py311h3778330_1.conda - sha256: 710e207b2e91308a34bcfe547c60ad86c1fa294827266ba18548c1fe1a9d8333 - md5: f9efdf9b0f3d0cc309d56af6edf2a6b0 + size: 7002 + timestamp: 1752805902938 +- conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 + sha256: 09803b75cccc16d8586d2f41ea890658d165f4afc359973fa1c7904a2c140eae + md5: 91733394059b880d9cc0d010c20abda0 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 + - python >=2.7 + - pywin32 license: BSD-3-Clause license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 26756 - timestamp: 1772445078834 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda - sha256: 72ed7c0216541d65a17b171bf2eec4a3b81e9158d8ed48e59e1ecd3ae302d263 - md5: aeb9b9da79fd0258b3db091d1fefcd71 + purls: [] + size: 5282 + timestamp: 1646866839398 +- conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 + sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb + md5: 2807a0becd1d986fe1ef9b7f8135f215 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - jinja2 >=3.0.0 + - __unix + - python >=2.7 license: BSD-3-Clause license_family: BSD + purls: [] + size: 4856 + timestamp: 1646866525560 +- conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda + sha256: 0604c6dff3af5f53e34fceb985395d08287137f220450108a795bcd1959caf14 + md5: 34fa231b5c5927684b03bb296bb94ddc + depends: + - prompt_toolkit >=2.0,<4.0 + - python >=3.10 + license: MIT + license_family: MIT purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 26100 - timestamp: 1772445154165 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py311hc290fe0_1.conda - sha256: d635f2b1d9e19e8e68c5d33150f7e4f62df08ef2ef0e85977f743e81939afc01 - md5: ff068874356bbc7f9bd2d793f809f44b + - pkg:pypi/questionary?source=hash-mapping + size: 31257 + timestamp: 1757356458097 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 + md5: 870293df500ca7e18bedefa5838a22ab depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD + - attrs >=22.2.0 + - python >=3.10 + - rpds-py >=0.7.0 + - typing_extensions >=4.4.0 + - python + license: MIT + license_family: MIT purls: - - pkg:pypi/markupsafe?source=compressed-mapping - size: 26511 - timestamp: 1772445369187 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_1.conda - sha256: f62892a42948c61aa0a13d9a36ff811651f0a1102331223594aecf3cc042bece - md5: 0195d558b0c0ab8f4af3089af83067c5 + - pkg:pypi/referencing?source=hash-mapping + size: 51788 + timestamp: 1760379115194 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + sha256: 7f2c24dd3bd3c104a1d2c9a10ead5ed6758b0976b74f972cfe9c19884ccc4241 + md5: 9659f587a8ceacc21864260acd02fc67 depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 + - python >=3.10 + - certifi >=2023.5.7 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.26,<3 + - python constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD + - chardet >=3.0.2,<8 + license: Apache-2.0 + license_family: APACHE purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 26009 - timestamp: 1772445537524 -- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py311h3f79411_1.conda - sha256: 3d37fb1900e31131f84549560e7a4bfea5f39aa3ecd73345fef1f33975cf0baa - md5: f55de41c947bdd2ff9bbeffedf8089f7 + - pkg:pypi/requests?source=compressed-mapping + 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.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause + - python >=3.11 + - typing_extensions >=4.0,<5.0 + - python + license: BSD-2-Clause license_family: BSD purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 29362 - timestamp: 1772445178723 -- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py313hd650c13_1.conda - sha256: 9dc626b6c00bc2dbd2494df689876ff675b93d92636ba5df8e37b99040a1f6bc - md5: 5cc690ddf943700e0ef50a265df31f03 + - pkg:pypi/returns?source=hash-mapping + size: 100559 + timestamp: 1776176903101 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 + md5: 36de09a8d3e5d5e6f4ee63af49e59706 depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD + - python >=3.9 + - six + license: MIT + license_family: MIT purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 28992 - timestamp: 1772445161959 -- pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl - name: matplotlib - version: 3.10.9 - sha256: dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb - requires_dist: - - contourpy>=1.0.1 - - cycler>=0.10 - - fonttools>=4.22.0 - - kiwisolver>=1.3.1 - - numpy>=1.23 - - packaging>=20.0 - - pillow>=8 - - pyparsing>=3 - - python-dateutil>=2.7 - - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - - setuptools-scm>=7,<10 ; extra == 'dev' - - setuptools>=64 ; extra == 'dev' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl - name: matplotlib - version: 3.10.9 - sha256: b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e - requires_dist: - - contourpy>=1.0.1 - - cycler>=0.10 - - fonttools>=4.22.0 - - kiwisolver>=1.3.1 - - numpy>=1.23 - - packaging>=20.0 - - pillow>=8 - - pyparsing>=3 - - python-dateutil>=2.7 - - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - - setuptools-scm>=7,<10 ; extra == 'dev' - - setuptools>=64 ; extra == 'dev' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: matplotlib - version: 3.10.9 - sha256: 8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb - requires_dist: - - contourpy>=1.0.1 - - cycler>=0.10 - - fonttools>=4.22.0 - - kiwisolver>=1.3.1 - - numpy>=1.23 - - packaging>=20.0 - - pillow>=8 - - pyparsing>=3 - - python-dateutil>=2.7 - - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - - setuptools-scm>=7,<10 ; extra == 'dev' - - setuptools>=64 ; extra == 'dev' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl - name: matplotlib - version: 3.10.9 - sha256: d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f - requires_dist: - - contourpy>=1.0.1 - - cycler>=0.10 - - fonttools>=4.22.0 - - kiwisolver>=1.3.1 - - numpy>=1.23 - - packaging>=20.0 - - pillow>=8 - - pyparsing>=3 - - python-dateutil>=2.7 - - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - - setuptools-scm>=7,<10 ; extra == 'dev' - - setuptools>=64 ; extra == 'dev' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: matplotlib - version: 3.10.9 - sha256: 8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f - requires_dist: - - contourpy>=1.0.1 - - cycler>=0.10 - - fonttools>=4.22.0 - - kiwisolver>=1.3.1 - - numpy>=1.23 - - packaging>=20.0 - - pillow>=8 - - pyparsing>=3 - - python-dateutil>=2.7 - - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - - setuptools-scm>=7,<10 ; extra == 'dev' - - setuptools>=64 ; extra == 'dev' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl - name: matplotlib - version: 3.10.9 - sha256: f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921 - requires_dist: - - contourpy>=1.0.1 - - cycler>=0.10 - - fonttools>=4.22.0 - - kiwisolver>=1.3.1 - - numpy>=1.23 - - packaging>=20.0 - - pillow>=8 - - pyparsing>=3 - - python-dateutil>=2.7 - - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' - - setuptools-scm>=7,<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 - sha256: 9d690334de0cd1d22c51bc28420663f4277cfa60d34fa5cad1ce284a13f1d603 - md5: 00e120ce3e40bad7bfc78861ce3c4a25 + - pkg:pypi/rfc3339-validator?source=hash-mapping + size: 10209 + timestamp: 1733600040800 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + sha256: 2a5b495a1de0f60f24d8a74578ebc23b24aa53279b1ad583755f223097c41c37 + md5: 912a71cc01012ee38e6b90ddd561e36f + depends: + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rfc3986-validator?source=hash-mapping + size: 7818 + timestamp: 1598024297745 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + sha256: 70001ac24ee62058557783d9c5a7bbcfd97bd4911ef5440e3f7a576f9e43bc92 + md5: 7234f99325263a5af6d4cd195035e8f2 + depends: + - python >=3.9 + - lark >=1.2.2 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rfc3987-syntax?source=hash-mapping + size: 22913 + timestamp: 1752876729969 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + sha256: 8fc024bf1a7b99fc833b131ceef4bef8c235ad61ecb95a71a6108be2ccda63e8 + md5: b70e2d44e6aa2beb69ba64206a16e4c6 depends: + - __osx + - pyobjc-framework-cocoa - python >=3.10 - - traitlets + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/matplotlib-inline?source=hash-mapping - size: 15175 - timestamp: 1761214578417 -- pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - name: mdit-py-plugins - version: 0.5.0 - sha256: 07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f - requires_dist: - - markdown-it-py>=2.0.0,<5.0.0 - - pre-commit ; extra == 'code-style' - - myst-parser ; extra == 'rtd' - - sphinx-book-theme ; extra == 'rtd' - - coverage ; extra == 'testing' - - pytest ; extra == 'testing' - - pytest-cov ; extra == 'testing' - - pytest-regressions ; extra == 'testing' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - name: mdurl - version: 0.1.2 - sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl - name: mergedeep - version: 1.3.4 - sha256: 70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 - requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - name: mike - version: 2.2.0 - sha256: e1f4981c1152eec7c2490a3401142292cc47d686194188416db2648fdfe1d040 - requires_dist: - - jinja2>=2.7 - - mkdocs~=1.0 - - pyparsing>=3.0 - - pyyaml>=5.1 - - pyyaml-env-tag - - verspec - - importlib-metadata ; python_full_version < '3.10' - - importlib-resources ; python_full_version < '3.10' - - coverage ; extra == 'dev' - - flake8-quotes ; extra == 'dev' - - flake8>=3.0 ; extra == 'dev' - - shtab ; extra == 'dev' - - coverage ; extra == 'test' - - flake8-quotes ; extra == 'test' - - flake8>=3.0 ; extra == 'test' - - shtab ; extra == 'test' -- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.2.1-pyhcf101f3_0.conda - sha256: b52dc6c78fbbe7a3008535cb8bfd87d70d8053e9250bbe16e387470a9df07070 - md5: b97e84d1553b4a1c765b87fff83453ad + - pkg:pypi/send2trash?source=hash-mapping + size: 22519 + timestamp: 1770937603551 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda + sha256: 305446a0b018f285351300463653d3d3457687270e20eda37417b12ee386ef76 + md5: 6ac53f3fff2c416d63511843a04646fa depends: + - __win + - pywin32 - python >=3.10 - - typing_extensions - python license: BSD-3-Clause + license_family: BSD purls: - - 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 - sha256: db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e - requires_dist: - - click>=7.0 - - colorama>=0.4 ; sys_platform == 'win32' - - ghp-import>=1.0 - - importlib-metadata>=4.4 ; python_full_version < '3.10' - - jinja2>=2.11.1 - - markdown>=3.3.6 - - markupsafe>=2.0.1 - - mergedeep>=1.3.4 - - mkdocs-get-deps>=0.2.0 - - packaging>=20.5 - - pathspec>=0.11.1 - - pyyaml-env-tag>=0.1 - - pyyaml>=5.1 - - watchdog>=2.0 - - babel>=2.9.0 ; extra == 'i18n' - - babel==2.9.0 ; extra == 'min-versions' - - click==7.0 ; extra == 'min-versions' - - colorama==0.4 ; sys_platform == 'win32' and extra == 'min-versions' - - ghp-import==1.0 ; extra == 'min-versions' - - importlib-metadata==4.4 ; python_full_version < '3.10' and extra == 'min-versions' - - jinja2==2.11.1 ; extra == 'min-versions' - - markdown==3.3.6 ; extra == 'min-versions' - - markupsafe==2.0.1 ; extra == 'min-versions' - - mergedeep==1.3.4 ; extra == 'min-versions' - - mkdocs-get-deps==0.2.0 ; extra == 'min-versions' - - packaging==20.5 ; extra == 'min-versions' - - pathspec==0.11.1 ; extra == 'min-versions' - - pyyaml-env-tag==0.1 ; extra == 'min-versions' - - pyyaml==5.1 ; extra == 'min-versions' - - watchdog==2.0 ; extra == 'min-versions' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - name: mkdocs-autorefs - version: 1.4.4 - sha256: 834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089 - requires_dist: - - markdown>=3.3 - - markupsafe>=2.0.1 - - mkdocs>=1.1 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - name: mkdocs-get-deps - version: 0.2.2 - sha256: e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650 - requires_dist: - - importlib-metadata>=4.3 ; python_full_version < '3.10' - - mergedeep>=1.3.4 - - platformdirs>=2.2.0 - - pyyaml>=5.1 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - name: mkdocs-jupyter - version: 0.26.3 - sha256: cd6644fb578131157194d750fd4d10fc2fd8f1e84e00036ee62df3b5b4b84c82 - requires_dist: - - ipykernel>6.0.0,<8 - - jupytext>1.13.8,<2 - - mkdocs-material>9.0.0 - - mkdocs>=1.4.0,<2 - - nbconvert>=7.2.9,<8 - - pygments>2.12.0 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl - name: mkdocs-markdownextradata-plugin - version: 0.2.6 - sha256: 34dd40870781784c75809596b2d8d879da783815b075336d541de1f150c94242 - requires_dist: - - mkdocs - - pyyaml - requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - name: mkdocs-material - version: 9.7.6 - sha256: 71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba - requires_dist: - - babel>=2.10 - - backrefs>=5.7.post1 - - colorama>=0.4 - - jinja2>=3.1 - - markdown>=3.2 - - mkdocs-material-extensions>=1.3 - - mkdocs>=1.6,<2 - - paginate>=0.5 - - pygments>=2.16 - - pymdown-extensions>=10.2 - - requests>=2.30 - - mkdocs-git-committers-plugin-2>=1.1 ; extra == 'git' - - mkdocs-git-revision-date-localized-plugin>=1.2.4 ; extra == 'git' - - cairosvg>=2.6 ; extra == 'imaging' - - pillow>=10.2 ; extra == 'imaging' - - mkdocs-minify-plugin>=0.7 ; extra == 'recommended' - - mkdocs-redirects>=1.2 ; extra == 'recommended' - - mkdocs-rss-plugin>=1.6 ; extra == 'recommended' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - name: mkdocs-material-extensions - version: 1.3.1 - sha256: adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl - name: mkdocs-plugin-inline-svg - version: 0.1.0 - sha256: a5aab2d98a19b24019f8e650f54fc647c2f31e7d0e36fc5cf2d2161acc0ea49a - requires_dist: - - mkdocs - requires_python: '>=3.5' -- pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl - name: mkdocstrings - version: 1.0.4 - sha256: 63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b - requires_dist: - - jinja2>=3.1 - - markdown>=3.6 - - markupsafe>=1.1 - - mkdocs>=1.6 - - mkdocs-autorefs>=1.4 - - pymdown-extensions>=6.3 - - mkdocstrings-crystal>=0.3.4 ; extra == 'crystal' - - mkdocstrings-python-legacy>=0.2.1 ; extra == 'python-legacy' - - mkdocstrings-python>=1.16.2 ; extra == 'python' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl - name: mkdocstrings-python - version: 2.0.3 - sha256: 0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12 - requires_dist: - - mkdocstrings>=0.30 - - mkdocs-autorefs>=1.4 - - griffelib>=2.0 - - typing-extensions>=4.0 ; python_full_version < '3.11' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl - name: mpld3 - version: 0.5.12 - sha256: bea31799a4041029a906f53f2662bbf1c49903e0c0bc712b412354158ec7cf54 - requires_dist: - - jinja2 - - matplotlib -- pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl - name: mpltoolbox - version: 26.2.0 - sha256: cd2668db4216fc4d7c2ba37974961aa61445f1517527b645b6082930e35ba7f0 - requires_dist: - - matplotlib - - ipympl ; extra == 'test' - - pytest>=8.0 ; extra == 'test' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl - name: mpmath - version: 1.3.0 - sha256: a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c - requires_dist: - - pytest>=4.6 ; extra == 'develop' - - pycodestyle ; extra == 'develop' - - pytest-cov ; extra == 'develop' - - codecov ; extra == 'develop' - - wheel ; extra == 'develop' - - sphinx ; extra == 'docs' - - gmpy2>=2.1.0a4 ; platform_python_implementation != 'PyPy' and extra == 'gmpy' - - pytest>=4.6 ; extra == 'tests' -- pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl - name: msgpack - version: 1.1.2 - sha256: d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: msgpack - version: 1.1.2 - sha256: fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl - name: msgpack - version: 1.1.2 - sha256: a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl - name: msgpack - version: 1.1.2 - sha256: 283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl - name: msgpack - version: 1.1.2 - sha256: 42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: msgpack - version: 1.1.2 - sha256: 454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py311h49ec1c0_0.conda - sha256: 59123ba4af69c1e7ca6b1b8ffcc40f2aff9a635814495bda3f4f555b3b929165 - md5: 0347d2804701019ac005528df9eb502c + - pkg:pypi/send2trash?source=hash-mapping + size: 22864 + timestamp: 1770937641143 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + sha256: 59656f6b2db07229351dfb3a859c35e57cc8e8bcbc86d4e501bff881a6f771f1 + md5: 28eb91468df04f655a57bcfbb35fc5c5 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 + - __linux + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/msgspec?source=hash-mapping - size: 217556 - timestamp: 1776337480302 -- conda: https://conda.anaconda.org/conda-forge/linux-64/msgspec-0.21.1-py313h07c4f96_0.conda - sha256: a44d23085117672e4e19629ea3e6c53f516984322513c4bfdd052b1c7c6fc8ad - md5: 15c679f7133347d68058b688f3519cea + - pkg:pypi/send2trash?source=hash-mapping + size: 24108 + timestamp: 1770937597662 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 + md5: 8e194e7b992f99a5015edbd4ebd38efd depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD + - python >=3.10 + license: MIT + license_family: MIT purls: - - pkg:pypi/msgspec?source=hash-mapping - size: 219767 - timestamp: 1776337423071 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py311hc949640_0.conda - sha256: 55ce08c77b6e87fcc5cca61a516e81fdcc1cd0969e0204fce0a32ca84118a148 - md5: 24707aa446fa7babd806397b8c5349a2 + - pkg:pypi/setuptools?source=hash-mapping + size: 639697 + timestamp: 1773074868565 +- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD + - python >=3.9 + - python + license: MIT + license_family: MIT purls: - - pkg:pypi/msgspec?source=hash-mapping - size: 207945 - timestamp: 1776338655947 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_0.conda - sha256: 63cdc0052bd638f1f3df6de438e69275b7b273b4b77dbd1ac34ce25d98cf973c - md5: 2dd8d3b25e88780abc058559bc7975c6 + - pkg:pypi/six?source=hash-mapping + size: 18455 + timestamp: 1753199211006 +- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad + md5: 03fe290994c5e4ec17293cfb6bdce520 depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD + - python >=3.10 + license: Apache-2.0 + license_family: Apache purls: - - pkg:pypi/msgspec?source=hash-mapping - size: 214497 - timestamp: 1776338358818 -- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py311h3485c13_0.conda - sha256: d096ad02a47a61aeeade2ad3639cc300adf950790cca097717040d46ca5bff52 - md5: 6e6fd9a9012358254bebee33eb3d7f71 + - pkg:pypi/sniffio?source=hash-mapping + size: 15698 + timestamp: 1762941572482 +- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda + sha256: 23b71ecf089967d2900126920e7f9ff18cdcef82dbff3e2f54ffa360243a17ac + md5: 18de09b20462742fe093ba39185d9bac depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-3-Clause - license_family: BSD + - python >=3.10 + license: MIT + license_family: MIT purls: - - pkg:pypi/msgspec?source=hash-mapping - size: 199272 - timestamp: 1776337711230 -- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_0.conda - sha256: 4e70a7b9bee6b9dadd1f038da6fcf1627cc53581d1cc01355b66d638ae55c0e3 - md5: 96575971ffc81497b83bf321f0b9e945 + - pkg:pypi/soupsieve?source=hash-mapping + size: 38187 + timestamp: 1769034509657 +- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 + md5: b1b505328da7a6b246787df4b5a49fbc depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: BSD-3-Clause - license_family: BSD + - asttokens + - executing + - pure_eval + - python >=3.9 + license: MIT + license_family: MIT purls: - - pkg:pypi/msgspec?source=hash-mapping - size: 201017 - timestamp: 1776337591817 -- pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: multidict - version: 6.7.1 - sha256: 439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144 - requires_dist: - - typing-extensions>=4.1.0 ; python_full_version < '3.11' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl - name: multidict - version: 6.7.1 - sha256: 935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445 - requires_dist: - - typing-extensions>=4.1.0 ; python_full_version < '3.11' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: multidict - version: 6.7.1 - sha256: 9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429 - requires_dist: - - typing-extensions>=4.1.0 ; python_full_version < '3.11' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl - name: multidict - version: 6.7.1 - sha256: 960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5 - requires_dist: - - typing-extensions>=4.1.0 ; python_full_version < '3.11' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl - name: multidict - version: 6.7.1 - sha256: bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b - requires_dist: - - typing-extensions>=4.1.0 ; python_full_version < '3.11' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl - name: multidict - version: 6.7.1 - sha256: f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855 - requires_dist: - - typing-extensions>=4.1.0 ; python_full_version < '3.11' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl - name: narwhals - version: 2.20.0 - sha256: 16e750ea5507d4ba6e8d03455b5f93a535e0405976561baea235bca5dc9f475d - requires_dist: - - cudf-cu12>=24.10.0 ; extra == 'cudf' - - dask[dataframe]>=2024.8 ; extra == 'dask' - - duckdb>=1.1 ; extra == 'duckdb' - - ibis-framework>=6.0.0 ; extra == 'ibis' - - packaging ; extra == 'ibis' - - pyarrow-hotfix ; extra == 'ibis' - - rich ; extra == 'ibis' - - modin ; extra == 'modin' - - pandas>=1.1.3 ; extra == 'pandas' - - polars>=0.20.4 ; extra == 'polars' - - pyarrow>=13.0.0 ; extra == 'pyarrow' - - pyspark>=3.5.0 ; extra == 'pyspark' - - pyspark[connect]>=3.5.0 ; extra == 'pyspark-connect' - - duckdb>=1.1 ; extra == 'sql' - - sqlparse ; extra == 'sql' - - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.10.4-pyhd8ed1ab_0.conda - sha256: 1b66960ee06874ddceeebe375d5f17fb5f393d025a09e15b830ad0c4fffb585b - md5: 00f5b8dafa842e0c27c1cd7296aa4875 + - pkg:pypi/stack-data?source=hash-mapping + size: 26988 + timestamp: 1733569565672 +- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda + sha256: b375e8df0d5710717c31e7c8e93c025c37fa3504aea325c7a55509f64e5d4340 + md5: e43ca10d61e55d0a8ec5d8c62474ec9e depends: - - jupyter_client >=6.1.12 - - jupyter_core >=4.12,!=5.0.* - - nbformat >=5.1 - - python >=3.8 - - traitlets >=5.4 - license: BSD-3-Clause + - __win + - pywinpty >=1.1.0 + - python >=3.10 + - tornado >=6.1.0 + - python + license: BSD-2-Clause license_family: BSD purls: - - pkg:pypi/nbclient?source=compressed-mapping - size: 28473 - timestamp: 1766485646962 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda - sha256: ab2ac79c5892c5434d50b3542d96645bdaa06d025b6e03734be29200de248ac2 - md5: 2bce0d047658a91b99441390b9b27045 + - pkg:pypi/terminado?source=hash-mapping + size: 23665 + timestamp: 1766513806974 +- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + sha256: 6b6727a13d1ca6a23de5e6686500d0669081a117736a87c8abf444d60c1e40eb + md5: 17b43cee5cc84969529d5d0b0309b2cb depends: - - beautifulsoup4 - - bleach-with-css !=5.0.0 - - defusedxml - - importlib-metadata >=3.6 - - jinja2 >=3.0 - - jupyter_core >=4.7 - - jupyterlab_pygments - - markupsafe >=2.0 - - mistune >=2.0.3,<4 - - nbclient >=0.5.0 - - nbformat >=5.7 - - packaging - - pandocfilters >=1.4.1 - - pygments >=2.4.1 + - __unix + - ptyprocess - python >=3.10 - - traitlets >=5.1 + - tornado >=6.1.0 - python - constrains: - - pandoc >=2.9.2,<4.0.0 - - nbconvert ==7.17.1 *_0 - license: BSD-3-Clause + license: BSD-2-Clause license_family: BSD purls: - - pkg:pypi/nbconvert?source=compressed-mapping - size: 202229 - timestamp: 1775615493260 -- conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda - sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 - md5: bbe1963f1e47f594070ffe87cdf612ea + - pkg:pypi/terminado?source=hash-mapping + size: 24749 + timestamp: 1766513766867 +- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 + md5: f1acf5fdefa8300de697982bcb1761c9 depends: - - jsonschema >=2.6 - - jupyter_core >=4.12,!=5.0.* - - python >=3.9 - - python-fastjsonschema >=2.15 - - traitlets >=5.1 + - python >=3.5 + - webencodings >=0.4 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/nbformat?source=hash-mapping - size: 100945 - timestamp: 1733402844974 -- pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - name: nbmake - version: 1.5.5 - sha256: c6fbe6e48b60cacac14af40b38bf338a3b88f47f085c54ac5b8639ff0babaf4b - requires_dist: - - ipykernel>=5.4.0 - - nbclient>=0.6.6 - - nbformat>=5.0.4 - - pygments>=2.7.3 - - pytest>=6.1.0 - requires_python: '>=3.8.0' -- pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl - name: nbqa - version: 1.9.1 - sha256: 95552d2f6c2c038136252a805aa78d85018aef922586270c3a074332737282e5 - requires_dist: - - autopep8>=1.5 - - ipython>=7.8.0 - - tokenize-rt>=3.2.0 - - tomli - - black ; extra == 'toolchain' - - blacken-docs ; extra == 'toolchain' - - flake8 ; extra == 'toolchain' - - isort ; extra == 'toolchain' - - jupytext ; extra == 'toolchain' - - mypy ; extra == 'toolchain' - - pylint ; extra == 'toolchain' - - pyupgrade ; extra == 'toolchain' - - ruff ; extra == 'toolchain' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl - name: nbstripout - version: 0.9.1 - sha256: ca027ee45742ee77e4f8e9080254f9a707f1161ba11367b82fdf4a29892c759e - requires_dist: - - nbformat - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - name: ncrystal - version: 4.4.2 - sha256: e02fa7d743addc3fbea23287737a88b8d01192450fdca51554d3f9032fe4617c - requires_dist: - - ncrystal-core==4.4.2 - - ncrystal-python==4.4.2 - - spglib>=2.1.0 ; extra == 'composer' - - ase>=3.23.0 ; extra == 'cif' - - gemmi>=0.6.1 ; extra == 'cif' - - spglib>=2.1.0 ; extra == 'cif' - - endf-parserpy>=0.14.3 ; extra == 'endf' - - matplotlib>=3.6.0 ; extra == 'plot' - - ase>=3.23.0 ; extra == 'all' - - endf-parserpy>=0.14.3 ; extra == 'all' - - gemmi>=0.6.1 ; extra == 'all' - - matplotlib>=3.6.0 ; extra == 'all' - - spglib>=2.1.0 ; extra == 'all' - - pyyaml>=6.0.0 ; extra == 'devel' - - ase>=3.23.0 ; extra == 'devel' - - cppcheck ; extra == 'devel' - - endf-parserpy>=0.14.3 ; extra == 'devel' - - gemmi>=0.6.1 ; extra == 'devel' - - matplotlib>=3.6.0 ; extra == 'devel' - - mpmath>=1.3.0 ; extra == 'devel' - - numpy>=1.22 ; extra == 'devel' - - pybind11>=2.11.0 ; extra == 'devel' - - ruff>=0.8.1 ; extra == 'devel' - - simple-build-system>=1.6.0 ; extra == 'devel' - - spglib>=2.1.0 ; extra == 'devel' - - tomli>=2.0.0 ; python_full_version < '3.11' and extra == 'devel' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl - name: ncrystal-core - version: 4.4.2 - sha256: 9b28a90b63849e6a3a807a0a59f7c2ee57e4c64f5643b2dcb6a798ac8ccf666a - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl - name: ncrystal-core - version: 4.4.2 - sha256: b7e6101a6850aa18cf441825214381614db444ffcba648de8266fe1c4d1024ce - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: ncrystal-core - version: 4.4.2 - sha256: d0d9c47cd017b7cefc52dde50546d7c151bfdd75d345e42e2b3e74ab5fe83c62 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl - name: ncrystal-python - version: 4.4.2 - sha256: f419318d088fade6bcff1e39e15baf6fe69fcf5306dd681fca1106d1f63a89ce - requires_dist: - - numpy>=1.22 - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 - md5: fc21868a1a5aacc937e7a18747acb8a5 + - pkg:pypi/tinycss2?source=hash-mapping + size: 28285 + timestamp: 1729802975370 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd + md5: b5325cf06a000c5b14970462ff5e4d58 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: X11 AND BSD-3-Clause - purls: [] - size: 918956 - timestamp: 1777422145199 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - sha256: 4ea6c620b87bd1d42bb2ccc2c87cd2483fa2d7f9e905b14c223f11ff3f4c455d - md5: 343d10ed5b44030a2f67193905aea159 + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + size: 21561 + timestamp: 1774492402955 +- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda + sha256: dfb681579be59c2e790c95f7f49b7529a9b0511d6385ad276e3c8988cbd54d2c + md5: 4bada6a6d908a27262af8ebddf4f7492 depends: - - __osx >=11.0 - license: X11 AND BSD-3-Clause + - python >=3.10 + - python + license: BSD-3-Clause + purls: + - pkg:pypi/traitlets?source=compressed-mapping + size: 115165 + timestamp: 1778074251714 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c + md5: edd329d7d3a4ab45dcf905899a7a6115 + depends: + - typing_extensions ==4.15.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF purls: [] - size: 805509 - timestamp: 1777423252320 -- conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_1.conda - sha256: bb7b21d7fd0445ddc0631f64e66d91a179de4ba920b8381f29b9d006a42788c0 - md5: 598fd7d4d0de2455fb74f56063969a97 + size: 91383 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda + sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a + md5: 53f5409c5cfd6c5a66417d68e3f0a864 + depends: + - python >=3.10 + - typing_extensions >=4.12.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/typing-inspection?source=compressed-mapping + size: 20935 + timestamp: 1777105465795 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + size: 51692 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c + md5: f6d7aa696c67756a650e91e15e88223c depends: - python >=3.9 - license: BSD-2-Clause - license_family: BSD + license: Apache-2.0 + license_family: APACHE purls: - - pkg:pypi/nest-asyncio?source=hash-mapping - size: 11543 - timestamp: 1733325673691 -- pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl - name: networkx - version: 3.6.1 - sha256: d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 - requires_dist: - - asv ; extra == 'benchmarking' - - virtualenv ; extra == 'benchmarking' - - numpy>=1.25 ; extra == 'default' - - scipy>=1.11.2 ; extra == 'default' - - matplotlib>=3.8 ; extra == 'default' - - pandas>=2.0 ; extra == 'default' - - pre-commit>=4.1 ; extra == 'developer' - - mypy>=1.15 ; extra == 'developer' - - sphinx>=8.0 ; extra == 'doc' - - pydata-sphinx-theme>=0.16 ; extra == 'doc' - - sphinx-gallery>=0.18 ; extra == 'doc' - - numpydoc>=1.8.0 ; extra == 'doc' - - pillow>=10 ; extra == 'doc' - - texext>=0.6.7 ; extra == 'doc' - - myst-nb>=1.1 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - osmnx>=2.0.0 ; extra == 'example' - - momepy>=0.7.2 ; extra == 'example' - - contextily>=1.6 ; extra == 'example' - - seaborn>=0.13 ; extra == 'example' - - cairocffi>=1.7 ; extra == 'example' - - igraph>=0.11 ; extra == 'example' - - scikit-learn>=1.5 ; extra == 'example' - - iplotx>=0.9.0 ; extra == 'example' - - lxml>=4.6 ; extra == 'extra' - - pygraphviz>=1.14 ; extra == 'extra' - - pydot>=3.0.1 ; extra == 'extra' - - sympy>=1.10 ; extra == 'extra' - - build>=0.10 ; extra == 'release' - - twine>=4.0 ; extra == 'release' - - wheel>=0.40 ; extra == 'release' - - changelist==0.5 ; extra == 'release' - - pytest>=7.2 ; extra == 'test' - - pytest-cov>=4.0 ; extra == 'test' - - pytest-xdist>=3.0 ; extra == 'test' - - pytest-mpl ; extra == 'test-extras' - - pytest-randomly ; extra == 'test-extras' - requires_python: '>=3.11,!=3.14.1' -- pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - name: nodeenv - version: 1.10.0 - sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 - requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.8.2-he4ff34a_0.conda - sha256: d1a673d1418d9e956b6e4e46c23e72a511c5c1d45dc5519c947457427036d5e2 - md5: baffb1570b3918c784d4490babc52fbf + - pkg:pypi/typing-utils?source=hash-mapping + size: 15183 + timestamp: 1733331395943 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 + license: LicenseRef-Public-Domain + purls: [] + size: 119135 + timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 + md5: e7cb0f5745e4c5035a460248334af7eb depends: - - libgcc >=14 - - libstdcxx >=14 - - __glibc >=2.28,<3.0.a0 - - libnghttp2 >=1.68.1,<2.0a0 - - libuv >=1.51.0,<2.0a0 - - c-ares >=1.34.6,<2.0a0 - - openssl >=3.5.5,<4.0a0 - - libsqlite >=3.52.0,<4.0a0 - - icu >=78.3,<79.0a0 - - libzlib >=1.3.2,<2.0a0 - - libabseil >=20260107.1,<20260108.0a0 - - libabseil * cxx17* - - zstd >=1.5.7,<1.6.0a0 - - libbrotlicommon >=1.2.0,<1.3.0a0 - - libbrotlienc >=1.2.0,<1.3.0a0 - - libbrotlidec >=1.2.0,<1.3.0a0 + - python >=3.9 license: MIT license_family: MIT - purls: [] - size: 18829340 - timestamp: 1774514313036 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda - sha256: 4782b172b3b8a557b60bf5f591821cf100e2092ba7a5494ce047dfa41626de26 - md5: ca8277c52fdface8bb8ebff7cd9a6f56 + purls: + - pkg:pypi/uri-template?source=hash-mapping + size: 23990 + timestamp: 1733323714454 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a + md5: 9272daa869e03efe68833e3dc7a02130 depends: - - libcxx >=19 - - __osx >=11.0 - - icu >=78.3,<79.0a0 - - libbrotlicommon >=1.2.0,<1.3.0a0 - - libbrotlienc >=1.2.0,<1.3.0a0 - - libbrotlidec >=1.2.0,<1.3.0a0 - - libnghttp2 >=1.68.1,<2.0a0 - - libuv >=1.51.0,<2.0a0 - - libsqlite >=3.52.0,<4.0a0 - - libzlib >=1.3.2,<2.0a0 - - openssl >=3.5.5,<4.0a0 - - zstd >=1.5.7,<1.6.0a0 - - c-ares >=1.34.6,<2.0a0 - - libabseil >=20260107.1,<20260108.0a0 - - libabseil * cxx17* + - backports.zstd >=1.0.0 + - brotli-python >=1.2.0 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.10 license: MIT license_family: MIT - purls: [] - size: 17101803 - timestamp: 1774517834028 -- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda - sha256: 5e38e51da1aa4bc352db9b4cec1c3e25811de0f4408edaa24e009a64de6dbfdf - md5: e626ee7934e4b7cb21ce6b721cff8677 + purls: + - pkg:pypi/urllib3?source=hash-mapping + size: 103172 + timestamp: 1767817860341 +- 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: [] - size: 31271315 - timestamp: 1774517904472 -- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda - sha256: 7b920e46b9f7a2d2aa6434222e5c8d739021dbc5cc75f32d124a8191d86f9056 - md5: e7f89ea5f7ea9401642758ff50a2d9c1 + purls: + - 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 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/webcolors?source=hash-mapping + size: 18987 + timestamp: 1761899393153 +- conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 + md5: 2841eb5bfc75ce15e9a0054b98dcd64d depends: - - jupyter_server >=1.8,<3 - python >=3.9 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/notebook-shim?source=hash-mapping - size: 16817 - timestamp: 1733408419340 -- pypi: https://files.pythonhosted.org/packages/44/0b/0615dbedb98f5b32a35a53290fbdc6e22306968109278d7e58df82d7a9f6/numba-0.65.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: numba - version: 0.65.1 - sha256: f80ed83774b5173abd6581cd8d2165d1d38e13d2e5c8155c0c0b421784745420 - requires_dist: - - llvmlite>=0.47.0.dev0,<0.48 - - numpy>=1.22 - - numpy>=1.22,<2.5 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl - name: numba - version: 0.65.1 - sha256: 1735c15c1134a5108b4d6a5c77fc0947924ea066a738dc09a52008c13df9cad3 - requires_dist: - - llvmlite>=0.47.0.dev0,<0.48 - - numpy>=1.22 - - numpy>=1.22,<2.5 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/96/b3/650500c2eab4534d98e9166f4298e0f3c69c742afdf24e6eabccd1f16ad8/numba-0.65.1-cp311-cp311-macosx_12_0_arm64.whl - name: numba - version: 0.65.1 - sha256: 7020d74b19cdb8cff16506542fdd510756e28c5e7f3bd0b7f574f0f42272fcd9 - requires_dist: - - llvmlite>=0.47.0.dev0,<0.48 - - numpy>=1.22 - - numpy>=1.22,<2.5 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - name: numba - version: 0.65.1 - sha256: c09f49117ef255e1f1c6dad0c7a1ed39868243862a73be5706793241a3755f1b - requires_dist: - - llvmlite>=0.47.0.dev0,<0.48 - - numpy>=1.22 - - numpy>=1.22,<2.5 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/bd/9a/af61ec03b3116c161fd7a06b9e8a265729a8718458333e8ffbb06d9a3978/numba-0.65.1-cp311-cp311-win_amd64.whl - name: numba - version: 0.65.1 - sha256: df40a5028a975b9ea66f6a2a3f7abbdbd541a863070e34ed367aff21141248e4 - requires_dist: - - llvmlite>=0.47.0.dev0,<0.48 - - numpy>=1.22 - - numpy>=1.22,<2.5 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl - name: numba - version: 0.65.1 - sha256: 85be74c0d036842699a30058f82fb88fc5ffdc59f7615cab5792ea92914c9b62 - requires_dist: - - llvmlite>=0.47.0.dev0,<0.48 - - numpy>=1.22 - - numpy>=1.22,<2.5 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl - name: numpy - version: 2.4.4 - sha256: 5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl - name: numpy - version: 2.4.4 - sha256: 86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015 - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl - name: numpy - version: 2.4.4 - sha256: 6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: numpy - version: 2.4.4 - sha256: df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502 - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: numpy - version: 2.4.4 - sha256: c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83 - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl - name: numpy - version: 2.4.4 - sha256: 4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af - requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda - sha256: c0ef482280e38c71a08ad6d71448194b719630345b0c9c60744a2010e8a8e0cb - md5: da1b85b6a87e141f5140bb9924cecab0 + - pkg:pypi/webencodings?source=hash-mapping + size: 15496 + timestamp: 1733236131358 +- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + sha256: 42a2b61e393e61cdf75ced1f5f324a64af25f347d16c60b14117393a98656397 + md5: 2f1ed718fcd829c184a6d4f0f2e07409 depends: - - __glibc >=2.17,<3.0.a0 - - ca-certificates - - libgcc >=14 + - python >=3.10 license: Apache-2.0 - license_family: Apache - purls: [] - size: 3167099 - timestamp: 1775587756857 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda - sha256: c91bf510c130a1ea1b6ff023e28bac0ccaef869446acd805e2016f69ebdc49ea - md5: 25dcccd4f80f1638428613e0d7c9b4e1 + license_family: APACHE + purls: + - pkg:pypi/websocket-client?source=hash-mapping + size: 61391 + timestamp: 1759928175142 +- conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f + md5: 46e441ba871f524e2b067929da3051c2 + depends: + - __win + - python >=3.9 + license: LicenseRef-Public-Domain + purls: + - pkg:pypi/win-inet-pton?source=hash-mapping + size: 9555 + timestamp: 1733130678956 +- conda: https://conda.anaconda.org/conda-forge/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=compressed-mapping + size: 24461 + timestamp: 1776131454755 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py311h9408147_2.conda + sha256: c15fc1aa6184185f1b31670a16b76d59f484d3efa25b467f28d59088cf0085c0 + md5: 501c2a606787bcd55b9ddee776208333 depends: - __osx >=11.0 - - ca-certificates - license: Apache-2.0 - license_family: Apache - purls: [] - size: 3106008 - timestamp: 1775587972483 -- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda - sha256: feb5815125c60f2be4a411e532db1ed1cd2d7261a6a43c54cb6ae90724e2e154 - md5: 05c7d624cff49dbd8db1ad5ba537a8a3 + - cffi >=1.0.1 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 34403 + timestamp: 1762509963182 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-25.1.0-py313h6535dbc_2.conda + sha256: 05ea6fa7109235cfb4fc24526bae1fe82d88bbb5e697ab3945c313f5f041af5b + md5: e23e087109b2096db4cf9a3985bab329 depends: - - ca-certificates - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: [] - size: 9410183 - timestamp: 1775589779763 -- pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl - name: orsopy - version: 1.2.3 - sha256: 11d3ee9a1185467ba4363b24d6b98b268956ee0fb528aeff7ff106b49f363180 - requires_dist: - - pyyaml>=5.4.1 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - name: oscrypto - version: 1.3.0 - sha256: 2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085 - requires_dist: - - asn1crypto>=1.5.1 -- conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda - sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c - md5: e51f1e4089cad105b6cac64bd8166587 + - __osx >=11.0 + - cffi >=1.0.1 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 33947 + timestamp: 1762510144907 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py311h36d4fbb_0.conda + sha256: 2e6399a3663e7e20134a208cd09befc44ce1e9efe94d1d36d8911db04f186270 + md5: 3ee7e8ae532366221658b80baee36dc0 depends: - - python >=3.9 - - typing_utils + - python + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 244743 + timestamp: 1777848723707 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.4.0-py313h7208f8c_0.conda + sha256: 257b234caffc760e48c8d7e642d6d0d8bac4341ca19c7df098421358eff636a1 + md5: 84e922fe79295051f6925d7f8d71c98c + depends: + - python + - __osx >=11.0 + - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 242188 + timestamp: 1777848729476 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py311h71babbd_1.conda + sha256: 79594899d1f2472a6d95c3d489268bb9103ba6bab043d9faca379d989074aeb3 + md5: 00ad8eed3ec75b6ec425526968c72e61 + depends: + - python + - python 3.11.* *_cpython + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/overrides?source=hash-mapping - size: 30139 - timestamp: 1734587755455 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 - md5: 4c06a92e74452cfa53623a81592e8934 + - pkg:pypi/bcrypt?source=hash-mapping + size: 268519 + timestamp: 1762497846273 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bcrypt-5.0.0-py313h2c089d5_1.conda + sha256: c8e3b95ad2665dc16ac37888fb91dd481fa759bad1fbf799451d12d6bfaec600 + md5: 4b14cba28eaf98170c2ddd7e900efa07 depends: - - python >=3.8 - python + - __osx >=11.0 + - python 3.13.* *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - __osx >=11.0 license: Apache-2.0 license_family: APACHE purls: - - 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 - sha256: b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591 - requires_dist: - - pytest ; extra == 'dev' - - tox ; extra == 'dev' - - black ; extra == 'lint' -- pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl - name: pandas - version: 3.0.2 - sha256: d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668 - requires_dist: - - numpy>=1.26.0 ; python_full_version < '3.14' - - numpy>=2.3.3 ; python_full_version >= '3.14' - - python-dateutil>=2.8.2 - - tzdata ; sys_platform == 'win32' - - tzdata ; sys_platform == 'emscripten' - - hypothesis>=6.116.0 ; extra == 'test' - - pytest>=8.3.4 ; extra == 'test' - - pytest-xdist>=3.6.1 ; extra == 'test' - - pyarrow>=13.0.0 ; extra == 'pyarrow' - - bottleneck>=1.4.2 ; extra == 'performance' - - numba>=0.60.0 ; extra == 'performance' - - numexpr>=2.10.2 ; extra == 'performance' - - scipy>=1.14.1 ; extra == 'computation' - - xarray>=2024.10.0 ; extra == 'computation' - - fsspec>=2024.10.0 ; extra == 'fss' - - s3fs>=2024.10.0 ; extra == 'aws' - - gcsfs>=2024.10.0 ; extra == 'gcp' + - pkg:pypi/bcrypt?source=hash-mapping + size: 267465 + timestamp: 1762497730829 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py311hdc60ec4_1.conda + sha256: 617545ec0e97d35ed2ff7852f2581a20c0dda80b366d0c42a43706687f971ba8 + md5: 150cbf381febcf0a5e470a8d066e1bc0 + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 359588 + timestamp: 1764018467340 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_1.conda + sha256: 2e21dccccd68bedd483300f9ab87a425645f6776e6e578e10e0dd98c946e1be9 + md5: b03732afa9f4f54634d94eb920dfb308 + depends: + - __osx >=11.0 + - libcxx >=19 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 359568 + timestamp: 1764018359470 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + sha256: 540fe54be35fac0c17feefbdc3e29725cce05d7367ffedfaaa1bdda234b019df + md5: 620b85a3f45526a8bc4d23fd78fc22f0 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 124834 + timestamp: 1771350416561 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + sha256: 2995f2aed4e53725e5efbc28199b46bf311c3cab2648fc4f10c2227d6d5fa196 + md5: bcb3cba70cf1eec964a03b4ba7775f01 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 180327 + timestamp: 1765215064054 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py311hd10dc20_1.conda + sha256: 1ffde698463d6e7ed571bdb85cb17bfc1d3a107c026d20047995512dcc2749ec + md5: 4d7f6780e36f18e7601811dddf3bbec5 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 294848 + timestamp: 1761203196617 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.0.0-py313h224173a_1.conda + sha256: 1fa69651f5e81c25d48ac42064db825ed1a3e53039629db69f86b952f5ce603c + md5: 050374657d1c7a4f2ea443c0d0cbd9a0 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pycparser + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 291376 + timestamp: 1761203583358 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py311hbb43c59_0.conda + sha256: 6e59412439c8b51ee5f05b3c50b05b43991fa544a895226168fab63be9ba28f9 + md5: cbc7fb4eb2049a4a6cd2c452afd2f7bd + depends: + - __osx >=11.0 + - cffi >=2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1855763 + timestamp: 1777966249758 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cryptography-48.0.0-py313hda5ae78_0.conda + sha256: 2adca86f420558eaaad8b8a7ecffadc4a23f980c02716d0fd5bee3f51879e76b + md5: 730fd8d2542ece52b71bc4fbb80ceb10 + depends: + - __osx >=11.0 + - cffi >=2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - __osx >=11.0 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1854514 + timestamp: 1777966313532 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py311h8948835_0.conda + sha256: 093b015e9abf27fb4d3b4f7e52417d35cd69a99fab8b95ec5c6c3983275c46ba + md5: 150c921424bc9f08c0378f8a6ae58d05 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 2668163 + timestamp: 1769745020016 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.20-py313h1188861_0.conda + sha256: 372464b345220758769f49e76d125933008abec7048df4077a851adcc79da1e8 + md5: b3a832c19cfa5dfcce7575750ef693ed + depends: + - python + - libcxx >=19 + - python 3.13.* *_cp313 + - __osx >=11.0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 2761021 + timestamp: 1769744996428 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda + sha256: 3a7907a17e9937d3a46dfd41cffaf815abad59a569440d1e25177c15fd0684e5 + md5: f1182c91c0de31a7abd40cedf6a5ebef + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 12361647 + timestamp: 1773822915649 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + sha256: c0a0bf028fe7f3defcdcaa464e536cf1b202d07451e18ad83fdd169d15bef6ed + md5: e446e1822f4da8e5080a9de93474184d + depends: + - __osx >=11.0 + - libcxx >=19 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 1160828 + timestamp: 1769770119811 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + sha256: 756611fbb8d2957a5b4635d9772bd8432cb6ddac05580a6284cca6fdc9b07fca + md5: bb65152e0d7c7178c0f1ee25692c9fd1 + depends: + - __osx >=11.0 + - libcxx >=19 + constrains: + - abseil-cpp =20260107.1 + - libabseil-static =20260107.1=cxx17* + license: Apache-2.0 + license_family: Apache + purls: [] + size: 1229639 + timestamp: 1770863511331 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + sha256: a7cb9e660531cf6fbd4148cff608c85738d0b76f0975c5fc3e7d5e92840b7229 + md5: 006e7ddd8a110771134fcc4e1e3a6ffa + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 79443 + timestamp: 1764017945924 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + sha256: 2eae444039826db0454b19b52a3390f63bfe24f6b3e63089778dd5a5bf48b6bf + md5: 079e88933963f3f149054eec2c487bc2 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: [] + size: 29452 + timestamp: 1764017979099 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + sha256: 01436c32bb41f9cb4bcf07dda647ce4e5deb8307abfc3abdc8da5317db8189d1 + md5: b2b7c8288ca1a2d71ff97a8e6a1e8883 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 hc919400_1 + license: MIT + license_family: MIT + purls: [] + size: 290754 + timestamp: 1764018009077 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.5-h55c6f16_0.conda + sha256: 76c3d3c702593b6ef57dc2f99c082193a99835526634b844f4a78ae3aecdb6c8 + md5: d0a2e921c5a89df73118cff1afed5213 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + size: 570968 + timestamp: 1778110367423 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + sha256: 66aa216a403de0bb0c1340a88d1a06adaff66bae2cfd196731aa24db9859d631 + md5: 44083d2d2c2025afca315c7a172eab2b + depends: + - ncurses + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 107691 + timestamp: 1738479560845 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + sha256: 95cecb3902fbe0399c3a7e67a5bed1db813e5ab0e22f4023a5e0f722f2cc214f + md5: 36d33e440c31857372a72137f78bacf5 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 107458 + timestamp: 1702146414478 +- 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.8.0.* + license: MIT + license_family: MIT + purls: [] + size: 68789 + timestamp: 1777846180142 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + sha256: 6686a26466a527585e6a75cc2a242bf4a3d97d6d6c86424a441677917f28bec7 + md5: 43c04d9cb46ef176bb2a4c77e324d599 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 40979 + timestamp: 1769456747661 +- 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.3.* + license: 0BSD + purls: [] + size: 92472 + timestamp: 1775825802659 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + sha256: 1089c7f15d5b62c622625ec6700732ece83be8b705da8c6607f4dabb0c4bd6d2 + md5: 57c4be259f5e0b99a5983799a228ae55 + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 73690 + timestamp: 1769482560514 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + sha256: 2bc7bc3978066f2c274ebcbf711850cc9ab92e023e433b9631958a098d11e10a + md5: 6ea18834adbc3b33df9bd9fb45eaf95b + depends: + - __osx >=11.0 + - c-ares >=1.34.6,<2.0a0 + - libcxx >=19 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 576526 + timestamp: 1773854624224 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.21-h1a92334_3.conda + sha256: df603472ea1ebd8e7d4fb71e4360fe48d10b11c240df51c129de1da2ff9e8227 + md5: 7cc5247987e6d115134ebab15186bc13 + depends: + - __osx >=11.0 + license: ISC + purls: [] + size: 248039 + timestamp: 1772479570912 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.1-h1b79a29_0.conda + sha256: 49daec7c83e70d4efc17b813547824bc2bcf2f7256d84061d24fbfe537da9f74 + md5: 6681822ea9d362953206352371b6a904 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + size: 920047 + timestamp: 1777987051643 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.51.0-h6caf38d_1.conda + sha256: 042c7488ad97a5629ec0a991a8b2a3345599401ecc75ad6a5af73b60e6db9689 + md5: c0d87c3c8e075daf1daf6c31b53e8083 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 421195 + timestamp: 1753948426421 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + sha256: 361415a698514b19a852f5d1123c5da746d4642139904156ddfca7c922d23a05 + md5: bc5a5721b6439f2f62a84f2548136082 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 47759 + timestamp: 1774072956767 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py311hc290fe0_1.conda + sha256: d635f2b1d9e19e8e68c5d33150f7e4f62df08ef2ef0e85977f743e81939afc01 + md5: ff068874356bbc7f9bd2d793f809f44b + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=compressed-mapping + size: 26511 + timestamp: 1772445369187 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_1.conda + sha256: f62892a42948c61aa0a13d9a36ff811651f0a1102331223594aecf3cc042bece + md5: 0195d558b0c0ab8f4af3089af83067c5 + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 26009 + timestamp: 1772445537524 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py311hc949640_0.conda + sha256: 55ce08c77b6e87fcc5cca61a516e81fdcc1cd0969e0204fce0a32ca84118a148 + md5: 24707aa446fa7babd806397b8c5349a2 + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 207945 + timestamp: 1776338655947 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgspec-0.21.1-py313h0997733_0.conda + sha256: 63cdc0052bd638f1f3df6de438e69275b7b273b4b77dbd1ac34ce25d98cf973c + md5: 2dd8d3b25e88780abc058559bc7975c6 + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 214497 + timestamp: 1776338358818 +- 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: 805509 + timestamp: 1777423252320 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/nodejs-25.8.2-h7039424_0.conda + sha256: 4782b172b3b8a557b60bf5f591821cf100e2092ba7a5494ce047dfa41626de26 + md5: ca8277c52fdface8bb8ebff7cd9a6f56 + depends: + - libcxx >=19 + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libnghttp2 >=1.68.1,<2.0a0 + - libuv >=1.51.0,<2.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - zstd >=1.5.7,<1.6.0a0 + - c-ares >=1.34.6,<2.0a0 + - libabseil >=20260107.1,<20260108.0a0 + - libabseil * cxx17* + license: MIT + license_family: MIT + purls: [] + size: 17101803 + timestamp: 1774517834028 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda + sha256: c91bf510c130a1ea1b6ff023e28bac0ccaef869446acd805e2016f69ebdc49ea + md5: 25dcccd4f80f1638428613e0d7c9b4e1 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + size: 3106008 + timestamp: 1775587972483 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py311he363849_0.conda + sha256: 2b774e8f4ccaac8783d1908f281e4bb20b7036d6708dc913ee7811b070f5b4b5 + md5: 7cff50265141513c960f00ba586780ea + depends: + - python + - python 3.11.* *_cpython + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 245203 + timestamp: 1769678306347 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + sha256: 1d2a6039fb71d61134b1d6816202529f2f6286c83b59bc1491fd288f5c08046e + md5: ba2d89e51a855963c767648f44c03871 + depends: + - python + - __osx >=11.0 + - python 3.13.* *_cp313 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 242596 + timestamp: 1769678288893 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py311hf7c400d_0.conda + sha256: 194e8872dcda301703c8f1427e480fee6421e3901803ffc863b68eb886372eaf + md5: 80d6864b30e0697b652a9bd580b2d351 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - __osx >=11.0 + - python 3.11.* *_cpython + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1730463 + timestamp: 1778084304998 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + sha256: 5bcc20f2c21d8a20d8f2821caf31d8c0ef2fa3bcdaf7a9bdce62e37be819f1d7 + md5: 32bb0d4dbb1be2064c06248a1190aa96 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - python 3.13.* *_cp313 + - __osx >=11.0 + - python_abi 3.13.* *_cp313 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1720475 + timestamp: 1778084300413 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py311h38a6bc0_1.conda + sha256: 503b3a2543577f2fb70ddc44113a4da7ecdfdfb2bf51ecd1203adcb93e7257df + md5: 64d4dd58b1eb87d7e6ba084c7dc9259b + depends: + - __osx >=11.0 + - cffi >=1.4.1 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - six + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1188895 + timestamp: 1772171487639 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda + sha256: ef6ab85953ebf81b3b0c6d0b6377eb1a318b07823e76d2c8e850365e60d9ca5c + md5: fca94b9ddea6d5c63ccb6b5ad6d8e232 + depends: + - __osx >=11.0 + - cffi >=1.4.1 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - six + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1192924 + timestamp: 1772171603602 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py311hce6e4fa_0.conda + sha256: f6dced0ea4f220abc1278f6217e22ca1c631f6154dd086b0a7a2866589d6b78c + md5: 812e22c5c7859e719ec225ece0c6f2c1 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-core?source=hash-mapping + size: 481434 + timestamp: 1763151508974 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda + sha256: 307ca29ebf2317bd2561639b1ee0290fd8c03c3450fa302b9f9437d8df6a5280 + md5: 31a0a72f3466682d0ea2ebcbd7d319b8 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-core?source=hash-mapping + size: 481508 + timestamp: 1763152124940 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py311h9049b8e_0.conda + sha256: 67e209a5e9ebaf08aa36e2a4b9139ff91a7ecee7c17408d0a3a1ea1dc3a67681 + md5: b215a767b5ef6f16347cacebc409e66b + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pyobjc-core 12.1.* + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping + size: 383445 + timestamp: 1763160541362 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda + sha256: 194e188d8119befc952d04157079733e2041a7a502d50340ddde632658799fdc + md5: a6d28c8fc266a3d3c3dae183e25c4d31 + depends: + - __osx >=11.0 + - libffi >=3.5.2,<3.6.0a0 + - pyobjc-core 12.1.* + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping + size: 376136 + timestamp: 1763160678792 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda + sha256: 9a846065863925b2562126a5c6fecd7a972e84aaa4de9e686ad3715ca506acfa + md5: 49c7d96c58b969585cf09fb01d74e08e + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.5,<4.0a0 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 14753109 + timestamp: 1772730203101 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda + build_number: 100 + sha256: d0fffc5fde21d1ae350da545dfb9e115a8c53bed8a9c5761f9efd4a5581853c1 + md5: 9991a930e81d3873eba7a299ba783ec4 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + size: 12966447 + timestamp: 1775615694085 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda + sha256: 984e73d7957460689e10533059de8adb38a308853d298900a37acc58edd84cec + md5: e4b908da7cd496b3fa6798c0f60a2a19 + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 192948 + timestamp: 1770223655988 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_1.conda + sha256: 950725516f67c9691d81bb8dde8419581c5332c5da3da10c9ba8cbb1698b825d + md5: 5d0c8b92128c93027632ca8f8dc1190f + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 188763 + timestamp: 1770224094408 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h745ac33_2.conda + sha256: c5d65e1192241d764a68eb0e95ef3ec4800a7e8584260aa2bdcfad0a2d769609 + md5: 9648524ed194922a084f0c2c2c2ada6a + depends: + - python + - __osx >=11.0 + - python 3.11.* *_cpython + - libcxx >=19 + - zeromq >=4.3.5,<4.4.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 359869 + timestamp: 1771717160649 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda + noarch: python + sha256: 2f31f799a46ed75518fae0be75ecc8a1b84360dbfd55096bc2fe8bd9c797e772 + md5: 2f6b79700452ef1e91f45a99ab8ffe5a + depends: + - python + - libcxx >=19 + - __osx >=11.0 + - _python_abi3_support 1.* + - cpython >=3.12 + - zeromq >=4.3.5,<4.4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 191641 + timestamp: 1771717073430 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + sha256: a77010528efb4b548ac2a4484eaf7e1c3907f2aec86123ed9c5212ae44502477 + md5: f8381319127120ce51e081dce4865cf4 + depends: + - __osx >=11.0 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + size: 313930 + timestamp: 1765813902568 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py311h71babbd_0.conda + sha256: 15873755f078583cea046f7ca7fc0d5348d1f29a16a30b73bdb53dd62f2ba379 + md5: 4408829b022e8e0d19365c0c00be00c4 + depends: + - python + - python 3.11.* *_cpython + - __osx >=11.0 + - python_abi 3.11.* *_cp311 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 357600 + timestamp: 1764543142990 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda + sha256: db63344f91e8bfe77703c6764aa9eeafb44d165e286053214722814eabda0264 + md5: 190c2d0d4e98ec97df48cdb74caf44d8 + depends: + - python + - __osx >=11.0 + - python 3.13.* *_cp313 + - python_abi 3.13.* *_cp313 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 358961 + timestamp: 1764543165314 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda + noarch: python + sha256: 91992a557456cbe9980e8ffb0f0d1200f65f741adeefc7ecf568d1935690a7bc + md5: 7151a0851207b88c0031f388a7825e9b + depends: + - python + - __osx >=11.0 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 8485007 + timestamp: 1778119519471 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 + md5: a9d86bc62f39b94c4661716624eb21b0 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: TCL + license_family: BSD + purls: [] + size: 3127137 + timestamp: 1769460817696 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py311hc949640_0.conda + sha256: 572923958ec987f3f68e228dfec243c89ab94b50398731215d36e2e040db8703 + md5: 6f12d9ff025ab1875dff67ad779ca29d + depends: + - __osx >=11.0 + - python >=3.11,<3.12.0a0 + - python >=3.11,<3.12.0a0 *_cpython + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 875022 + timestamp: 1774358570256 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda + sha256: c5b0ee042d8a0b88a3823226dc95b794c042c498aee330aa9b4d78bfad01d099 + md5: 303333dd882dfeb303cc8bfac178464b + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 883472 + timestamp: 1774358832451 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + sha256: b03433b13d89f5567e828ea9f1a7d5c5d697bf374c28a4168d71e9464f5dafac + md5: 78a0fe9e9c50d2c381e8ee47e3ea437d + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + size: 83386 + timestamp: 1753484079473 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda + sha256: 2705360c72d4db8de34291493379ffd13b09fd594d0af20c9eefa8a3f060d868 + md5: e85dcd3bde2b10081cdcaeae15797506 + depends: + - __osx >=11.0 + - libcxx >=19 + - krb5 >=1.22.2,<1.23.0a0 + - libsodium >=1.0.21,<1.0.22.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + size: 245246 + timestamp: 1772476886668 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + sha256: 9485ba49e8f47d2b597dd399e88f4802e100851b27c21d7525625b0b4025a5d9 + md5: ab136e4c34e97f34fb621d2592a393d8 + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 433413 + timestamp: 1764777166076 +- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py311h3485c13_2.conda + sha256: 787e9a5f0a5624fee2af4f58c823bd7933b8ca560e794dd9a821e990b31d2d49 + md5: 5fde0926a521a37d454a5e3162cb6e51 + depends: + - cffi >=1.0.1 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 38502 + timestamp: 1762509668838 +- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-25.1.0-py313h5ea7bf4_2.conda + sha256: 3f8a1affdfeb2be5289d709e365fc6e386d734773895215cf8cbc5100fa6af9a + md5: eabb4b677b54874d7d6ab775fdaa3d27 + depends: + - cffi >=1.0.1 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + size: 38779 + timestamp: 1762509796090 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py311h71c1bcc_0.conda + sha256: f551f6d7f1a62be18e4165772d300d21e0bd925956bcd9c2a605cabf9a2835d0 + md5: a0b8a2723108680aa6bb85fe3e413e76 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=hash-mapping + size: 243538 + timestamp: 1777848744191 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.4.0-py313h2a31948_0.conda + sha256: b9c1465c03cb6ff79fb4f2feb21955b9b89783f868c900cad45dbcb8d81ab429 + md5: ef16917e1231df5c6f2ba245c8fe35e4 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: + - pkg:pypi/backports-zstd?source=compressed-mapping + size: 240632 + timestamp: 1777848740331 +- conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py311hf51aa87_1.conda + sha256: b4cc5ee88f5d19381923519b1f6a1bbc73752f5b1528e4a1df0a3a802e497d13 + md5: fd25556009eae0faab9fc6286150d477 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/bcrypt?source=hash-mapping + size: 170642 + timestamp: 1762497737769 +- conda: https://conda.anaconda.org/conda-forge/win-64/bcrypt-5.0.0-py313hfbe8231_1.conda + sha256: 21baf1316a8160fd3b83e7128803735b107f47c58ba5d5305a372fd6c679d42b + md5: 2a664b5cc93fbc9d7ca595b206a299f0 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/bcrypt?source=hash-mapping + size: 170702 + timestamp: 1762497739995 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py311hc5da9e4_1.conda + sha256: 1803c838946d79ef6485ae8c7dafc93e28722c5999b059a34118ef758387a4c9 + md5: b0c459f98ac5ea504a9d9df6242f7ee1 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hfd05255_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 335333 + timestamp: 1764018370925 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_1.conda + sha256: 3558006cd6e836de8dff53cbe5f0b9959f96ea6a6776b4e14f1c524916dd956c + md5: 916a39a0261621b8c33e9db2366dd427 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hfd05255_1 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + size: 335605 + timestamp: 1764018132514 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + sha256: 76dfb71df5e8d1c4eded2dbb5ba15bb8fb2e2b0fe42d94145d5eed4c75c35902 + md5: 4cb8e6b48f67de0b018719cdf1136306 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + size: 56115 + timestamp: 1771350256444 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py311h3485c13_1.conda + sha256: c9caca6098e3d92b1a269159b759d757518f2c477fbbb5949cb9fee28807c1f1 + md5: f02335db0282d5077df5bc84684f7ff9 + depends: + - pycparser + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 297941 + timestamp: 1761203850323 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.0.0-py313h5ea7bf4_1.conda + sha256: f867a11f42bb64a09b232e3decf10f8a8fe5194d7e3a216c6bac9f40483bd1c6 + md5: 55b44664f66a2caf584d72196aa98af9 + depends: + - pycparser + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + size: 292681 + timestamp: 1761203203673 +- conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py311h2098ed6_0.conda + sha256: a6fca5c54f019724d08fc3d9e54c60ca8c0c8aa8b54571fec3e713bbb64a55c3 + md5: 27def055167d830e4ce6c6821eaee591 + depends: + - cffi >=2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1645132 + timestamp: 1777966325193 +- conda: https://conda.anaconda.org/conda-forge/win-64/cryptography-48.0.0-py313hf5c5e30_0.conda + sha256: 1fbf66094059379d72f06d3cbb9df5277352a95d25a68d55d1dd60fa6acca5c6 + md5: c216777102e0b49ed307e181df3903d2 + depends: + - cffi >=2.0 + - openssl >=3.5.6,<4.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT + license_family: BSD + purls: + - pkg:pypi/cryptography?source=hash-mapping + size: 1646298 + timestamp: 1777966340031 +- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py311h5dfdfe8_0.conda + sha256: 661e5c582b1f853a46a78d4bb6e55f2bfdac66e68d015e111f1580a11c28abbf + md5: 683be2cd10e80a367790b3083ce529b7 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 3940002 + timestamp: 1769745017274 +- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.20-py313h927ade5_0.conda + sha256: f6fe9cbd9e3d3c09f173eeb43676a58bc6169e97da0d190e0201e40828a3a62b + md5: 75eb3091b05924429a3a8d2a9bbdfac2 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + size: 4003589 + timestamp: 1769745018248 +- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + sha256: eb60f1ad8b597bcf95dee11bc11fe71a8325bc1204cf51d2bb1f2120ffd77761 + md5: 4432f52dc0c8eb6a7a6abc00a037d93c + depends: + - openssl >=3.5.5,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 751055 + timestamp: 1769769688841 +- 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.8.0.* + license: MIT + license_family: MIT + purls: [] + size: 71094 + timestamp: 1777846223617 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + sha256: 59d01f2dfa8b77491b5888a5ab88ff4e1574c9359f7e229da254cdfe27ddc190 + md5: 720b39f5ec0610457b725eb3f396219a + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + size: 45831 + timestamp: 1769456418774 +- 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.3.* + license: 0BSD + purls: [] + size: 106486 + timestamp: 1775825663227 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + sha256: 40dcd0b9522a6e0af72a9db0ced619176e7cfdb114855c7a64f278e73f8a7514 + md5: e4a9fc2bba3b022dad998c78856afe47 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 89411 + timestamp: 1769482314283 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.21-h6a83c73_3.conda + sha256: d915f4fa8ebbf237c7a6e511ed458f2cfdc7c76843a924740318a15d0dd33d6d + md5: da2aa614d16a795b3007b6f4a1318a81 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: ISC + purls: [] + size: 276860 + timestamp: 1772479407566 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.1-hf5d6505_0.conda + sha256: e70562450332ca8954bc16f3455468cca5ef3695c7d7187ecc87f8fc3c70e9eb + md5: 7fea434a17c323256acc510a041b80d7 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + size: 1304178 + timestamp: 1777986510497 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 + md5: dbabbd6234dea34040e631f87676292f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - zlib 1.3.2 *_2 + license: Zlib + license_family: Other + purls: [] + size: 58347 + timestamp: 1774072851498 +- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py311h3f79411_1.conda + sha256: 3d37fb1900e31131f84549560e7a4bfea5f39aa3ecd73345fef1f33975cf0baa + md5: f55de41c947bdd2ff9bbeffedf8089f7 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 29362 + timestamp: 1772445178723 +- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py313hd650c13_1.conda + sha256: 9dc626b6c00bc2dbd2494df689876ff675b93d92636ba5df8e37b99040a1f6bc + md5: 5cc690ddf943700e0ef50a265df31f03 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + size: 28992 + timestamp: 1772445161959 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py311h3485c13_0.conda + sha256: d096ad02a47a61aeeade2ad3639cc300adf950790cca097717040d46ca5bff52 + md5: 6e6fd9a9012358254bebee33eb3d7f71 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 199272 + timestamp: 1776337711230 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgspec-0.21.1-py313h5ea7bf4_0.conda + sha256: 4e70a7b9bee6b9dadd1f038da6fcf1627cc53581d1cc01355b66d638ae55c0e3 + md5: 96575971ffc81497b83bf321f0b9e945 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/msgspec?source=hash-mapping + size: 201017 + timestamp: 1776337591817 +- conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-25.8.2-h80d1838_0.conda + sha256: 5e38e51da1aa4bc352db9b4cec1c3e25811de0f4408edaa24e009a64de6dbfdf + md5: e626ee7934e4b7cb21ce6b721cff8677 + license: MIT + license_family: MIT + purls: [] + size: 31271315 + timestamp: 1774517904472 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda + sha256: feb5815125c60f2be4a411e532db1ed1cd2d7261a6a43c54cb6ae90724e2e154 + md5: 05c7d624cff49dbd8db1ad5ba537a8a3 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + size: 9410183 + timestamp: 1775589779763 +- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py311hf893f09_0.conda + sha256: 32da17824abadd1f5b46faedfa4964c7b1817b11887c2e8bb4e48628da51b93a + md5: fd968cdacc7967efd0ff5ef1805b812c + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 249478 + timestamp: 1769678166841 +- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + sha256: 3ec3373748f83069bef93b540de416e637ee30231b222d5df8f712e93f2f9195 + md5: 761b299a6289c77459defea3563f8fc0 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/psutil?source=hash-mapping + size: 246062 + timestamp: 1769678176886 +- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py311hf51aa87_0.conda + sha256: 4cf860418a6a1a678bcc23bc29a83f4f0916682fd6ab1ff209b4e5ee3dc5e15f + md5: 85efdb748cbec9210e718c4a8860749c + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1898998 + timestamp: 1778084255268 +- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + sha256: 6466b7e441adb71e29308de2a87c9787d5d0100601ede2d02ed4f85c251699d6 + md5: 9981bc46be6341306a5473b290b2f366 + depends: + - python + - typing-extensions >=4.6.0,!=4.7.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pydantic-core?source=hash-mapping + size: 1888029 + timestamp: 1778084253466 +- conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py311hcd7b28f_1.conda + sha256: cd35ad681895b45f578bac8fd8dafc87116c2db42489dda9bef76ff3518bbc73 + md5: e2197089006b3cda7e03c7a51fa7050e + depends: + - cffi >=1.4.1 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - six + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1147862 + timestamp: 1772171327674 +- conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda + sha256: 0960e727d0bd70e07bca0c049c4c4afa0b41bcea050135db49f54c1c0c02ed7f + md5: 99b7617a643bed6a403fc908c1e167f0 + depends: + - cffi >=1.4.1 + - libsodium >=1.0.21,<1.0.22.0a0 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - six + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/pynacl?source=hash-mapping + size: 1186552 + timestamp: 1772171311072 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda + sha256: a1f1031088ce69bc99c82b95980c1f54e16cbd5c21f042e9c1ea25745a8fc813 + md5: d09dbf470b41bca48cbe6a78ba1e009b + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.4,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.5,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - python_abi 3.11.* *_cp311 + license: Python-2.0 + purls: [] + size: 18416208 + timestamp: 1772728847666 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda + build_number: 100 + sha256: b8108d7f83f71fb15fbb4a263406c2065a8990b3d7eba2cbd7a3075b9a6392ba + md5: 7065f7067762c4c2bda1912f18d16239 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.7.5,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - liblzma >=5.8.2,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.52.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.6,<4.0a0 + - python_abi 3.13.* *_cp313 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Python-2.0 + purls: [] + size: 16618694 + timestamp: 1775613654892 + python_site_packages_path: Lib/site-packages +- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda + sha256: e3ef7e0cc53111ab81b8a9dd3eabc1374d7420d4c9fce3c8631e73310203ad55 + md5: c1cfe9f5d8e278cc4d2d4c7b0126634d + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/pywin32?source=hash-mapping + size: 6729388 + timestamp: 1756487145061 +- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda + sha256: 87eaeb79b5961e0f216aa840bc35d5f0b9b123acffaecc4fda4de48891901f20 + md5: 1ce4f826332dca56c76a5b0cc89fb19e + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/pywin32?source=hash-mapping + size: 6695114 + timestamp: 1756487139550 +- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_1.conda + sha256: b1f6b3a907e36f7af486faf3892f47fab42993c13c934cc19855bbae227f2b18 + md5: e5dd9afed138ff193d4593f1b15a388b + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - winpty + license: MIT + license_family: MIT + purls: + - pkg:pypi/pywinpty?source=hash-mapping + size: 215911 + timestamp: 1759557817579 +- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda + sha256: d34a7cd0a4a7dc79662cb6005e01d630245d9a942e359eb4d94b2fb464ed2552 + md5: 8f01ed27e2baa455e753301218e054fd + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - winpty + license: MIT + license_family: MIT + purls: + - pkg:pypi/pywinpty?source=hash-mapping + size: 216075 + timestamp: 1759556799508 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda + sha256: 301c3ba100d25cd5ae37895988ee3ab986210d4d972aa58efed948fbe857773d + md5: a0153c033dc55203e11d1cac8f6a9cf2 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 187108 + timestamp: 1770223467913 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_1.conda + sha256: dfaed50de8ee72a51096163b87631921688851001e38c78a841eba1ae8b35889 + md5: c1bdb8dd255c79fb9c428ad25cc6ee54 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + size: 180992 + timestamp: 1770223457761 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hff25285_2.conda + sha256: d37e831618e8df0506d32e16820adab550b74b36d039da753f7543bb967b700d + md5: 0ad824e928af5d6200a97649d810ab2b + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + - zeromq >=4.3.5,<4.3.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 363314 + timestamp: 1771716977369 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda + noarch: python + sha256: d84bcc19a945ca03d1fd794be3e9896ab6afc9f691d58d9c2da514abe584d4df + md5: eb1ec67a70b4d479f7dd76e6c8fe7575 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - zeromq >=4.3.5,<4.3.6.0a0 + - _python_abi3_support 1.* + - cpython >=3.12 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=hash-mapping + size: 183235 + timestamp: 1771716967192 +- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py311hf51aa87_0.conda + sha256: 6edeab1412def450e72f0e96a5d8bb31a2a0b4e56624699c916d3bafd4d9b475 + md5: 43ab63451a9df29f2c499da524665de9 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.11.* *_cp311 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 241288 + timestamp: 1764543026991 +- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda + sha256: 27bd383787c0df7a0a926b11014fd692d60d557398dcf1d50c55aa2378507114 + md5: 58ae648b12cfa6df3923b5fd219931cb + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + size: 243419 + timestamp: 1764543047271 +- conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda + noarch: python + sha256: 9b47c39449fd0102315bb62bd632707a754d32cf6c6f08d2a9c74b0a476f2282 + md5: 2db0d3419f3e6fc05516da561f81cda5 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/ruff?source=compressed-mapping + size: 9719930 + timestamp: 1778119435755 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 + md5: 0481bfd9814bf525bd4b3ee4b51494c4 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: TCL + license_family: BSD + purls: [] + size: 3526350 + timestamp: 1769460339384 +- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py311h3485c13_0.conda + sha256: 71f58fe09f7729ad82c8eb942e775ddeffcef454483911d19d1041ac5d81eec3 + md5: b004afcc680af88cb877978e71d42667 + depends: + - python >=3.11,<3.12.0a0 + - python_abi 3.11.* *_cp311 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 878544 + timestamp: 1774358187588 +- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda + sha256: b97fab804ab457cf4157103289317e3619e801a77410e756bb35c6223418cc6e + md5: 7d53f0d25ad5fd7d6962ce4eb385fb07 + depends: + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + size: 883689 + timestamp: 1774358224157 +- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + size: 694692 + timestamp: 1756385147981 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda + sha256: 9dc40c2610a6e6727d635c62cced5ef30b7b30123f5ef67d6139e23d21744b3a + md5: 1e610f2416b6acdd231c5f573d754a0f + depends: + - vc14_runtime >=14.44.35208 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 19356 + timestamp: 1767320221521 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + sha256: 02732f953292cce179de9b633e74928037fa3741eb5ef91c3f8bae4f761d32a5 + md5: 37eb311485d2d8b2c419449582046a42 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.44.35208 h818238b_34 + constrains: + - vs2015_runtime 14.44.35208.* *_34 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 683233 + timestamp: 1767320219644 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + sha256: 878d5d10318b119bd98ed3ed874bd467acbe21996e1d81597a1dbf8030ea0ce6 + md5: 242d9f25d2ae60c76b38a5e42858e51d + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.44.35208.* *_34 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + size: 115235 + timestamp: 1767320173250 +- conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 + sha256: 9df10c5b607dd30e05ba08cbd940009305c75db242476f4e845ea06008b0a283 + md5: 1cee351bf20b830d991dbe0bc8cd7dfe + license: MIT + license_family: MIT + purls: [] + size: 1176306 +- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + sha256: 80ee68c1e7683a35295232ea79bcc87279d31ffeda04a1665efdb43cbd50a309 + md5: 433699cba6602098ae8957a323da2664 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + size: 63944 + timestamp: 1753484092156 +- conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda + sha256: b8568dfde46edf3455458912ea6ffb760e4456db8230a0cf34ecbc557d3c275f + md5: 1ab0237036bfb14e923d6107473b0021 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libsodium >=1.0.21,<1.0.22.0a0 + - krb5 >=1.22.2,<1.23.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + size: 265665 + timestamp: 1772476832995 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + sha256: 368d8628424966fd8f9c8018326a9c779e06913dd39e646cf331226acc90e5b2 + md5: 053b84beec00b71ea8ff7a4f84b55207 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 388453 + timestamp: 1764777142545 +- pypi: . + name: easyreflectometry + requires_dist: + - bumps + - easyscience @ git+https://github.com/easyscience/corelib@develop + - orsopy + - pooch + - refl1d>=1.0.0 + - refnx + - scipp + - svglib<1.6 ; sys_platform == 'darwin' or sys_platform == 'linux' + - xhtml2pdf + - arviz ; extra == 'dev' + - build ; extra == 'dev' + - copier ; extra == 'dev' + - docstripy ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - mike ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-autorefs ; extra == 'dev' + - mkdocs-jupyter ; extra == 'dev' + - mkdocs-markdownextradata-plugin ; extra == 'dev' + - mkdocs-material ; extra == 'dev' + - mkdocs-plugin-inline-svg ; extra == 'dev' + - mkdocstrings-python ; extra == 'dev' + - nbmake ; extra == 'dev' + - nbqa ; extra == 'dev' + - nbstripout ; extra == 'dev' + - plopp ; extra == 'dev' + - plotly ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' + - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.11' +- pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + name: easyscience + version: 2.3.1+dev8 + requires_dist: + - asteval + - bumps + - dfo-ls + - lmfit + - numpy + - scipp + - build ; extra == 'dev' + - copier ; extra == 'dev' + - docstripy ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - ipykernel ; extra == 'dev' + - ipympl ; extra == 'dev' + - ipython ; extra == 'dev' + - ipywidgets ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterlab ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - matplotlib ; 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' + - pooch ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' + - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl + name: backrefs + version: '7.0' + sha256: f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a + requires_dist: + - regex ; extra == 'extras' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl + name: oscrypto + version: 1.3.0 + sha256: 2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085 + requires_dist: + - asn1crypto>=1.5.1 +- pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl + name: lxml + version: 6.1.0 + sha256: 183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl + name: pyyaml-env-tag + version: '1.1' + sha256: 17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 + requires_dist: + - pyyaml + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: python-bidi + version: 0.6.9 + sha256: 29e40e02bf2bddec225730eb3ba9f4ca948dced9877358458b74d7a447d936ac + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- 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 + sha256: a3eb1702e92aa2f2b5d3ba00261b61f062cce51f1cfb6900bf3ab4d1934d2d35 + requires_dist: + - bidict>=0.21.0 + - python-engineio>=4.11.0 + - requests>=2.21.0 ; extra == 'client' + - websocket-client>=0.54.0 ; extra == 'client' + - aiohttp>=3.4 ; extra == 'asyncio-client' + - tox ; extra == 'dev' + - sphinx ; extra == 'docs' + - furo ; extra == 'docs' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl + name: lxml + version: 6.1.0 + sha256: a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipy + version: 1.17.1 + sha256: 43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4 + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl + name: kiwisolver + version: 1.5.0 + sha256: 0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl + name: frozenlist + version: 1.8.0 + sha256: ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl + name: pandas + version: 3.0.2 + sha256: d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl + name: frozenlist + version: 1.8.0 + sha256: f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl + name: coverage + version: 7.13.5 + sha256: 941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl + name: pypdf + version: 6.10.2 + sha256: aa53be9826655b51c96741e5d7983ca224d898ac0a77896e64636810517624aa + requires_dist: + - typing-extensions>=4.0 ; python_full_version < '3.11' + - cryptography ; extra == 'crypto' + - pycryptodome ; extra == 'cryptodome' + - flit ; extra == 'dev' + - pip-tools ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-socket ; extra == 'dev' + - pytest-timeout ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - wheel ; extra == 'dev' + - myst-parser ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - cryptography ; extra == 'full' + - pillow>=8.0.0 ; extra == 'full' + - pillow>=8.0.0 ; extra == 'image' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0d/1f/d398de1612f7a611e22d743280339c9af4903675635e41be3370091c704b/arviz_stats-1.1.0-py3-none-any.whl + name: arviz-stats + version: 1.1.0 + sha256: ed47334ccff8670a0b90a50e1a37e7257268084eb3436e6b7b15e623f1001947 + requires_dist: + - numpy>=2 + - scipy>=1.13 + - sphinx-book-theme ; extra == 'doc' + - myst-parser[linkify] ; extra == 'doc' + - myst-nb ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - numpydoc ; extra == 'doc' + - sphinx<9 ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - jupyter-sphinx ; extra == 'doc' + - h5netcdf[h5py] ; extra == 'doc' + - sphinx-autosummary-accessors ; extra == 'doc' + - numba ; extra == 'numba' + - xarray-einstats[einops,numba] ; extra == 'numba' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest ; extra == 'test-xarray' + - pytest-cov ; extra == 'test-xarray' + - h5netcdf[h5py] ; extra == 'test-xarray' + - arviz-base>=1.1,<1.2 ; extra == 'xarray' + - xarray-einstats ; extra == 'xarray' + - xarray>=2024.11.0 ; extra == 'xarray' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl + name: contourpy + version: 1.3.3 + sha256: 23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl + name: build + 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.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.10' +- pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + name: aiohappyeyeballs + version: 2.6.1 + sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + name: pyparsing + version: 3.3.2 + sha256: 850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d + requires_dist: + - railroad-diagrams ; extra == 'diagrams' + - jinja2 ; extra == 'diagrams' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl + name: blinker + version: 1.9.0 + sha256: ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl + name: griffelib + version: 2.0.2 + sha256: 925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1 + requires_dist: + - pip>=24.0 ; extra == 'pypi' + - platformdirs>=4.2 ; extra == 'pypi' + - wheel>=0.42 ; extra == 'pypi' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: frozenlist + version: 1.8.0 + sha256: 2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/12/aa/fb2a0649fdeef5ab7072d221e8f4df164098792c813af6c87e2581cfa860/mpltoolbox-26.2.0-py3-none-any.whl + name: mpltoolbox + version: 26.2.0 + sha256: cd2668db4216fc4d7c2ba37974961aa61445f1517527b645b6082930e35ba7f0 + requires_dist: + - matplotlib + - ipympl ; extra == 'test' + - pytest>=8.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 3.0.2 + sha256: 5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl + name: interrogate + version: 1.7.0 + sha256: b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12 + requires_dist: + - attrs + - click>=7.1 + - colorama + - py + - tabulate + - tomli ; python_full_version < '3.11' + - cairosvg ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-autobuild ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-mock ; extra == 'dev' + - coverage[toml] ; extra == 'dev' + - wheel ; extra == 'dev' + - pre-commit ; extra == 'dev' + - sphinx ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - cairosvg ; extra == 'png' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-mock ; extra == 'tests' + - coverage[toml] ; extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl + name: mkdocs-jupyter + version: 0.26.3 + sha256: cd6644fb578131157194d750fd4d10fc2fd8f1e84e00036ee62df3b5b4b84c82 + requires_dist: + - ipykernel>6.0.0,<8 + - jupytext>1.13.8,<2 + - mkdocs-material>9.0.0 + - mkdocs>=1.4.0,<2 + - nbconvert>=7.2.9,<8 + - pygments>2.12.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl + name: chardet + version: 7.4.3 + sha256: 7005c88da26fd95d8abb8acbe6281d833e9a9181b03cf49b4546c4555389bd97 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/15/fe/b426215b6843ee70285521e2dd52f08a32096fe1d68e5b92fbdaffdf8ca3/diffpy_pdffit2-1.6.0-cp313-cp313-win_amd64.whl + name: diffpy-pdffit2 + version: 1.6.0 + sha256: f1bba407eac8ff8ef1264ad5a1d3dff840e2d0a5f71e9fb0c0b9b06d5a8eb139 + requires_dist: + - diffpy-structure + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl + name: spglib + version: 2.6.0 + sha256: ff1632524f6ac0031423474e48d6b69f4932ecb7eb4446a501f59619e2b5cbc9 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl + name: jupytext + version: 1.19.1 + sha256: d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9 + requires_dist: + - markdown-it-py>=1.0 + - mdit-py-plugins + - nbformat + - packaging + - pyyaml + - tomli ; python_full_version < '3.11' + - autopep8 ; extra == 'dev' + - black ; extra == 'dev' + - flake8 ; extra == 'dev' + - gitpython ; extra == 'dev' + - ipykernel ; extra == 'dev' + - isort ; extra == 'dev' + - jupyter-fs[fs]>=1.0 ; extra == 'dev' + - jupyter-server!=2.11 ; extra == 'dev' + - marimo>=0.17.6,<=0.19.4 ; extra == 'dev' + - nbconvert ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-asyncio ; extra == 'dev' + - pytest-cov>=2.6.1 ; extra == 'dev' + - pytest-randomly ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-gallery>=0.8 ; extra == 'dev' + - myst-parser ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - pytest ; extra == 'test' + - pytest-asyncio ; extra == 'test' + - pytest-randomly ; extra == 'test' + - pytest-xdist ; extra == 'test' + - black ; extra == 'test-cov' + - ipykernel ; extra == 'test-cov' + - jupyter-server!=2.11 ; extra == 'test-cov' + - nbconvert ; extra == 'test-cov' + - pytest ; extra == 'test-cov' + - pytest-asyncio ; extra == 'test-cov' + - pytest-cov>=2.6.1 ; extra == 'test-cov' + - pytest-randomly ; extra == 'test-cov' + - pytest-xdist ; extra == 'test-cov' + - autopep8 ; extra == 'test-external' + - black ; extra == 'test-external' + - flake8 ; extra == 'test-external' + - gitpython ; extra == 'test-external' + - ipykernel ; extra == 'test-external' + - isort ; extra == 'test-external' + - jupyter-fs[fs]>=1.0 ; extra == 'test-external' + - jupyter-server!=2.11 ; extra == 'test-external' + - marimo>=0.17.6,<=0.19.4 ; extra == 'test-external' + - nbconvert ; extra == 'test-external' + - pre-commit ; extra == 'test-external' + - pytest ; extra == 'test-external' + - pytest-asyncio ; extra == 'test-external' + - pytest-randomly ; extra == 'test-external' + - pytest-xdist ; extra == 'test-external' + - sphinx ; extra == 'test-external' + - sphinx-gallery>=0.8 ; extra == 'test-external' + - black ; extra == 'test-functional' + - pytest ; extra == 'test-functional' + - pytest-asyncio ; extra == 'test-functional' + - pytest-randomly ; extra == 'test-functional' + - pytest-xdist ; extra == 'test-functional' + - black ; extra == 'test-integration' + - ipykernel ; extra == 'test-integration' + - jupyter-server!=2.11 ; extra == 'test-integration' + - nbconvert ; extra == 'test-integration' + - pytest ; extra == 'test-integration' + - pytest-asyncio ; extra == 'test-integration' + - pytest-randomly ; extra == 'test-integration' + - pytest-xdist ; extra == 'test-integration' + - bash-kernel ; extra == 'test-ui' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl + name: sqlalchemy + version: 2.0.49 + sha256: 12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148 + requires_dist: + - importlib-metadata ; python_full_version < '3.8' + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' + - typing-extensions>=4.6.0 + - greenlet>=1 ; extra == 'asyncio' + - mypy>=0.910 ; extra == 'mypy' + - pyodbc ; extra == 'mssql' + - pymssql ; extra == 'mssql-pymssql' + - pyodbc ; extra == 'mssql-pyodbc' + - mysqlclient>=1.4.0 ; extra == 'mysql' + - mysql-connector-python ; extra == 'mysql-connector' + - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector' + - cx-oracle>=8 ; extra == 'oracle' + - oracledb>=1.0.1 ; extra == 'oracle-oracledb' + - psycopg2>=2.7 ; extra == 'postgresql' + - pg8000>=1.29.1 ; extra == 'postgresql-pg8000' + - greenlet>=1 ; extra == 'postgresql-asyncpg' + - asyncpg ; extra == 'postgresql-asyncpg' + - psycopg2-binary ; extra == 'postgresql-psycopg2binary' + - psycopg2cffi ; extra == 'postgresql-psycopg2cffi' + - psycopg>=3.0.7 ; extra == 'postgresql-psycopg' + - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary' + - pymysql ; extra == 'pymysql' + - greenlet>=1 ; extra == 'aiomysql' + - aiomysql>=0.2.0 ; extra == 'aiomysql' + - greenlet>=1 ; extra == 'aioodbc' + - aioodbc ; extra == 'aioodbc' + - greenlet>=1 ; extra == 'asyncmy' + - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy' + - greenlet>=1 ; extra == 'aiosqlite' + - aiosqlite ; extra == 'aiosqlite' + - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' + - sqlcipher3-binary ; extra == 'sqlcipher' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl + name: xarray-einstats + version: 0.9.1 + sha256: 777339524e85d066f2ef9ed1e3a3fb63aead4c1065fd1406f30dfa4de58ce063 + requires_dist: + - numpy>=1.25 + - scipy>=1.11 + - xarray>=2023.6.0 + - furo ; extra == 'doc' + - myst-parser[linkify] ; extra == 'doc' + - myst-nb ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - numpydoc ; extra == 'doc' + - sphinx>=5 ; extra == 'doc' + - jupyter-sphinx ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - watermark ; extra == 'doc' + - matplotlib ; extra == 'doc' + - sphinx-togglebutton ; extra == 'doc' + - einops ; extra == 'einops' + - numba>=0.55 ; extra == 'numba' + - hypothesis ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - packaging ; extra == 'test' + - scipy>=1.15 ; extra == 'test' + - preliz>=0.19 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl + name: contourpy + version: 1.3.3 + sha256: 1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl + name: xarray-einstats + version: 0.10.0 + sha256: fa3169b46cee29092db820d8bbc203148bada4fc970ee75e62cbf3dd7c5a8945 + requires_dist: + - numpy>=2.0 + - scipy>=1.13 + - xarray>=2024.2.0 + - furo ; extra == 'doc' + - myst-parser[linkify] ; extra == 'doc' + - myst-nb ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - numpydoc ; extra == 'doc' + - sphinx>=5 ; extra == 'doc' + - jupyter-sphinx ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - watermark ; extra == 'doc' + - matplotlib ; extra == 'doc' + - sphinx-togglebutton ; extra == 'doc' + - einops ; extra == 'einops' + - numba>=0.55 ; extra == 'numba' + - hypothesis ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - packaging ; extra == 'test' + - scipy>=1.15 ; extra == 'test' + - preliz>=0.19 ; extra == 'test' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/1b/b3/5d0e77ea774bd3224321c248880ea0c0379000ac5c2bb6d77609549de247/chardet-7.4.3-cp313-cp313-win_amd64.whl + name: chardet + version: 7.4.3 + sha256: e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl + name: reportlab + version: 4.5.0 + sha256: b8cc8996947d84e805368b47b2376070966f091d029351a0d8a1f238984c2c7f + requires_dist: + - pillow>=9.0.0 + - charset-normalizer + - rl-accel>=0.9.0,<1.1 ; extra == 'accel' + - rl-renderpm>=4.0.3,<4.1 ; extra == 'renderpm' + - rlpycairo>=0.2.0,<1 ; extra == 'pycairo' + - freetype-py>=2.3.0,<2.4 ; extra == 'pycairo' + - rlbidi ; extra == 'bidi' + - uharfbuzz ; extra == 'shaping' + requires_python: '>=3.9,<4' +- pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl + name: versioningit + version: 3.3.0 + sha256: 23b1db3c4756cded9bd6b0ddec6643c261e3d0c471707da3e0b230b81ce53e4b + requires_dist: + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - packaging>=17.1 + - tomli>=1.2,<3.0 ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + name: dill + version: 0.4.1 + sha256: 1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d + requires_dist: + - objgraph>=1.7.2 ; extra == 'graph' + - gprof2dot>=2022.7.29 ; extra == 'profile' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl + name: annotated-doc + version: 0.0.4 + sha256: 571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/20/16/e777eadfa0c0305878c36fae1d5e6db474fbb15dae202b9ec378809dfb4d/nbstripout-0.9.1-py3-none-any.whl + name: nbstripout + version: 0.9.1 + sha256: ca027ee45742ee77e4f8e9080254f9a707f1161ba11367b82fdf4a29892c759e + requires_dist: + - nbformat + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 3.0.2 + sha256: 61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' - odfpy>=1.4.1 ; extra == 'excel' - openpyxl>=3.1.5 ; extra == 'excel' - python-calamine>=0.3.0 ; extra == 'excel' @@ -9014,536 +9246,556 @@ packages: - xlsxwriter>=3.2.0 ; extra == 'all' - zstandard>=0.23.0 ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - name: pandas - version: 3.0.2 - sha256: 5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e +- pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl + name: gitpython + version: 3.1.50 + sha256: d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9 + requires_dist: + - gitdb>=4.0.1,<5 + - typing-extensions>=3.10.0.2 ; python_full_version < '3.10' + - coverage[toml] ; extra == 'test' + - ddt>=1.1.1,!=1.4.3 ; extra == 'test' + - mock ; python_full_version < '3.8' and extra == 'test' + - mypy==1.18.2 ; python_full_version >= '3.9' and extra == 'test' + - pre-commit ; extra == 'test' + - pytest>=7.3.1 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-instafail ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-sugar ; extra == 'test' + - typing-extensions ; python_full_version < '3.11' and extra == 'test' + - sphinx>=7.4.7,<8 ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - sphinx-autodoc-typehints ; extra == 'doc' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl + name: cssselect2 + version: 0.9.0 + sha256: 6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563 + requires_dist: + - tinycss2 + - webencodings + - sphinx ; extra == 'doc' + - furo ; extra == 'doc' + - pytest ; extra == 'test' + - ruff ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl + name: mkdocs + version: 1.6.1 + sha256: db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e + requires_dist: + - click>=7.0 + - colorama>=0.4 ; sys_platform == 'win32' + - ghp-import>=1.0 + - importlib-metadata>=4.4 ; python_full_version < '3.10' + - jinja2>=2.11.1 + - markdown>=3.3.6 + - markupsafe>=2.0.1 + - mergedeep>=1.3.4 + - mkdocs-get-deps>=0.2.0 + - packaging>=20.5 + - pathspec>=0.11.1 + - pyyaml-env-tag>=0.1 + - pyyaml>=5.1 + - watchdog>=2.0 + - babel>=2.9.0 ; extra == 'i18n' + - babel==2.9.0 ; extra == 'min-versions' + - click==7.0 ; extra == 'min-versions' + - colorama==0.4 ; sys_platform == 'win32' and extra == 'min-versions' + - ghp-import==1.0 ; extra == 'min-versions' + - importlib-metadata==4.4 ; python_full_version < '3.10' and extra == 'min-versions' + - jinja2==2.11.1 ; extra == 'min-versions' + - markdown==3.3.6 ; extra == 'min-versions' + - markupsafe==2.0.1 ; extra == 'min-versions' + - mergedeep==1.3.4 ; extra == 'min-versions' + - mkdocs-get-deps==0.2.0 ; extra == 'min-versions' + - packaging==20.5 ; extra == 'min-versions' + - pathspec==0.11.1 ; extra == 'min-versions' + - pyyaml-env-tag==0.1 ; extra == 'min-versions' + - pyyaml==5.1 ; extra == 'min-versions' + - watchdog==2.0 ; extra == 'min-versions' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl + name: py3dmol + version: 2.5.4 + sha256: 32806726b5310524a2b5bfee320737f7feef635cafc945c991062806daa9e43a + requires_dist: + - ipython ; extra == 'ipython' +- pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl + name: nbqa + version: 1.9.1 + sha256: 95552d2f6c2c038136252a805aa78d85018aef922586270c3a074332737282e5 + requires_dist: + - autopep8>=1.5 + - ipython>=7.8.0 + - tokenize-rt>=3.2.0 + - tomli + - black ; extra == 'toolchain' + - blacken-docs ; extra == 'toolchain' + - flake8 ; extra == 'toolchain' + - isort ; extra == 'toolchain' + - jupytext ; extra == 'toolchain' + - mypy ; extra == 'toolchain' + - pylint ; extra == 'toolchain' + - pyupgrade ; extra == 'toolchain' + - ruff ; extra == 'toolchain' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl + name: mkdocs-autorefs + version: 1.4.4 + sha256: 834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089 + requires_dist: + - markdown>=3.3 + - markupsafe>=2.0.1 + - mkdocs>=1.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl + name: pooch + version: 1.9.0 + sha256: f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b + requires_dist: + - platformdirs>=2.5.0 + - packaging>=20.0 + - requests>=2.19.0 + - tqdm>=4.41.0,<5.0.0 ; extra == 'progress' + - paramiko>=2.7.0 ; extra == 'sftp' + - xxhash>=1.4.3 ; extra == 'xxhash' + - pytest-httpserver ; extra == 'test' + - pytest-localftpserver ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: kiwisolver + version: 1.5.0 + sha256: 332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl + name: mkdocs-material + version: 9.7.6 + sha256: 71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba + requires_dist: + - babel>=2.10 + - backrefs>=5.7.post1 + - colorama>=0.4 + - jinja2>=3.1 + - markdown>=3.2 + - mkdocs-material-extensions>=1.3 + - mkdocs>=1.6,<2 + - paginate>=0.5 + - pygments>=2.16 + - pymdown-extensions>=10.2 + - requests>=2.30 + - mkdocs-git-committers-plugin-2>=1.1 ; extra == 'git' + - mkdocs-git-revision-date-localized-plugin>=1.2.4 ; extra == 'git' + - cairosvg>=2.6 ; extra == 'imaging' + - pillow>=10.2 ; extra == 'imaging' + - mkdocs-minify-plugin>=0.7 ; extra == 'recommended' + - mkdocs-redirects>=1.2 ; extra == 'recommended' + - mkdocs-rss-plugin>=1.6 ; extra == 'recommended' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl + name: mergedeep + version: 1.3.4 + sha256: 70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl + name: spglib + version: 2.6.0 + sha256: 12db7a0d6ad84c55e61eda67590a438edeb48e57ffd5df868cd931b57fb8c630 + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipp + version: 26.3.1 + sha256: 993706e7c31f0317be2db5f528f9142ba67b2e52d7af174fcad195f702e1d6c7 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/2f/c8/005d1de3af80f54411703d1263a0b9d31276411ec9f273d9432c59b17299/arviz_plots-1.1.0-py3-none-any.whl + name: arviz-plots + version: 1.1.0 + sha256: 5c7ab5b0c7c29cda6ddb5e04c699c70285fe68a76d2b1b42302c69a85742adde + requires_dist: + - arviz-base>=1.1,<1.2 + - arviz-stats[xarray]>=1.1,<1.2 + - bokeh>=3.4 ; extra == 'bokeh' + - sphinx-book-theme ; extra == 'doc' + - myst-parser[linkify] ; extra == 'doc' + - myst-nb ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - numpydoc ; extra == 'doc' + - sphinx>=6 ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - jupyter-sphinx ; extra == 'doc' + - h5netcdf[h5py] ; extra == 'doc' + - plotly<6 ; extra == 'doc' + - matplotlib>=3.9 ; extra == 'matplotlib' + - plotly>=5.19 ; extra == 'plotly' + - webcolors ; extra == 'plotly' + - hypothesis ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - h5netcdf[h5py] ; extra == 'test' + - kaleido ; extra == 'test' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + name: python-discovery + version: 1.3.0 + sha256: 441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f requires_dist: - - numpy>=1.26.0 ; python_full_version < '3.14' - - numpy>=2.3.3 ; python_full_version >= '3.14' - - python-dateutil>=2.8.2 - - tzdata ; sys_platform == 'win32' - - tzdata ; sys_platform == 'emscripten' - - hypothesis>=6.116.0 ; extra == 'test' - - pytest>=8.3.4 ; extra == 'test' - - pytest-xdist>=3.6.1 ; extra == 'test' - - pyarrow>=13.0.0 ; extra == 'pyarrow' - - bottleneck>=1.4.2 ; extra == 'performance' - - numba>=0.60.0 ; extra == 'performance' - - numexpr>=2.10.2 ; extra == 'performance' - - scipy>=1.14.1 ; extra == 'computation' - - xarray>=2024.10.0 ; extra == 'computation' - - fsspec>=2024.10.0 ; extra == 'fss' - - s3fs>=2024.10.0 ; extra == 'aws' - - gcsfs>=2024.10.0 ; extra == 'gcp' - - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.5 ; extra == 'excel' - - python-calamine>=0.3.0 ; extra == 'excel' - - pyxlsb>=1.0.10 ; extra == 'excel' - - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.2.0 ; extra == 'excel' - - pyarrow>=13.0.0 ; extra == 'parquet' - - pyarrow>=13.0.0 ; extra == 'feather' - - pyiceberg>=0.8.1 ; extra == 'iceberg' - - tables>=3.10.1 ; extra == 'hdf5' - - pyreadstat>=1.2.8 ; extra == 'spss' - - sqlalchemy>=2.0.36 ; extra == 'postgresql' - - psycopg2>=2.9.10 ; extra == 'postgresql' - - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.36 ; extra == 'mysql' - - pymysql>=1.1.1 ; extra == 'mysql' - - sqlalchemy>=2.0.36 ; extra == 'sql-other' - - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' - - beautifulsoup4>=4.12.3 ; extra == 'html' - - html5lib>=1.1 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'xml' - - matplotlib>=3.9.3 ; extra == 'plot' - - jinja2>=3.1.5 ; extra == 'output-formatting' - - tabulate>=0.9.0 ; extra == 'output-formatting' - - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.4.2 ; extra == 'clipboard' - - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' - - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - - beautifulsoup4>=4.12.3 ; extra == 'all' - - bottleneck>=1.4.2 ; extra == 'all' - - fastparquet>=2024.11.0 ; extra == 'all' - - fsspec>=2024.10.0 ; extra == 'all' - - gcsfs>=2024.10.0 ; extra == 'all' - - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.116.0 ; extra == 'all' - - jinja2>=3.1.5 ; extra == 'all' - - lxml>=5.3.0 ; extra == 'all' - - matplotlib>=3.9.3 ; extra == 'all' - - numba>=0.60.0 ; extra == 'all' - - numexpr>=2.10.2 ; extra == 'all' - - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.5 ; extra == 'all' - - psycopg2>=2.9.10 ; extra == 'all' - - pyarrow>=13.0.0 ; extra == 'all' - - pyiceberg>=0.8.1 ; extra == 'all' - - pymysql>=1.1.1 ; extra == 'all' - - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.8 ; extra == 'all' - - pytest>=8.3.4 ; extra == 'all' - - pytest-xdist>=3.6.1 ; extra == 'all' - - python-calamine>=0.3.0 ; extra == 'all' - - pytz>=2024.2 ; extra == 'all' - - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.4.2 ; extra == 'all' - - scipy>=1.14.1 ; extra == 'all' - - s3fs>=2024.10.0 ; extra == 'all' - - sqlalchemy>=2.0.36 ; extra == 'all' - - tables>=3.10.1 ; extra == 'all' - - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2024.10.0 ; extra == 'all' - - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.2.0 ; extra == 'all' - - zstandard>=0.23.0 ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - name: pandas - version: 3.0.2 - sha256: 61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76 + - filelock>=3.15.4 + - platformdirs>=4.3.6,<5 + - furo>=2025.12.19 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.6.3 ; extra == 'docs' + - sphinx>=9.1 ; extra == 'docs' + - sphinxcontrib-mermaid>=2 ; extra == 'docs' + - covdefaults>=2.3 ; extra == 'testing' + - coverage>=7.5.4 ; extra == 'testing' + - pytest-mock>=3.14 ; extra == 'testing' + - pytest>=8.3.5 ; extra == 'testing' + - setuptools>=75.1 ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: llvmlite + version: 0.47.0 + sha256: 2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl + name: mkdocstrings-python + version: 2.0.3 + sha256: 0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12 requires_dist: - - numpy>=1.26.0 ; python_full_version < '3.14' - - numpy>=2.3.3 ; python_full_version >= '3.14' - - python-dateutil>=2.8.2 - - tzdata ; sys_platform == 'win32' - - tzdata ; sys_platform == 'emscripten' - - hypothesis>=6.116.0 ; extra == 'test' - - pytest>=8.3.4 ; extra == 'test' - - pytest-xdist>=3.6.1 ; extra == 'test' - - pyarrow>=13.0.0 ; extra == 'pyarrow' - - bottleneck>=1.4.2 ; extra == 'performance' - - numba>=0.60.0 ; extra == 'performance' - - numexpr>=2.10.2 ; extra == 'performance' - - scipy>=1.14.1 ; extra == 'computation' - - xarray>=2024.10.0 ; extra == 'computation' - - fsspec>=2024.10.0 ; extra == 'fss' - - s3fs>=2024.10.0 ; extra == 'aws' - - gcsfs>=2024.10.0 ; extra == 'gcp' - - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.5 ; extra == 'excel' - - python-calamine>=0.3.0 ; extra == 'excel' - - pyxlsb>=1.0.10 ; extra == 'excel' - - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.2.0 ; extra == 'excel' - - pyarrow>=13.0.0 ; extra == 'parquet' - - pyarrow>=13.0.0 ; extra == 'feather' - - pyiceberg>=0.8.1 ; extra == 'iceberg' - - tables>=3.10.1 ; extra == 'hdf5' - - pyreadstat>=1.2.8 ; extra == 'spss' - - sqlalchemy>=2.0.36 ; extra == 'postgresql' - - psycopg2>=2.9.10 ; extra == 'postgresql' - - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.36 ; extra == 'mysql' - - pymysql>=1.1.1 ; extra == 'mysql' - - sqlalchemy>=2.0.36 ; extra == 'sql-other' - - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' - - beautifulsoup4>=4.12.3 ; extra == 'html' - - html5lib>=1.1 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'xml' - - matplotlib>=3.9.3 ; extra == 'plot' - - jinja2>=3.1.5 ; extra == 'output-formatting' - - tabulate>=0.9.0 ; extra == 'output-formatting' - - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.4.2 ; extra == 'clipboard' - - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' - - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - - beautifulsoup4>=4.12.3 ; extra == 'all' - - bottleneck>=1.4.2 ; extra == 'all' - - fastparquet>=2024.11.0 ; extra == 'all' - - fsspec>=2024.10.0 ; extra == 'all' - - gcsfs>=2024.10.0 ; extra == 'all' - - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.116.0 ; extra == 'all' - - jinja2>=3.1.5 ; extra == 'all' - - lxml>=5.3.0 ; extra == 'all' - - matplotlib>=3.9.3 ; extra == 'all' - - numba>=0.60.0 ; extra == 'all' - - numexpr>=2.10.2 ; extra == 'all' - - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.5 ; extra == 'all' - - psycopg2>=2.9.10 ; extra == 'all' - - pyarrow>=13.0.0 ; extra == 'all' - - pyiceberg>=0.8.1 ; extra == 'all' - - pymysql>=1.1.1 ; extra == 'all' - - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.8 ; extra == 'all' - - pytest>=8.3.4 ; extra == 'all' - - pytest-xdist>=3.6.1 ; extra == 'all' - - python-calamine>=0.3.0 ; extra == 'all' - - pytz>=2024.2 ; extra == 'all' - - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.4.2 ; extra == 'all' - - scipy>=1.14.1 ; extra == 'all' - - s3fs>=2024.10.0 ; extra == 'all' - - sqlalchemy>=2.0.36 ; extra == 'all' - - tables>=3.10.1 ; extra == 'all' - - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2024.10.0 ; extra == 'all' - - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.2.0 ; extra == 'all' - - zstandard>=0.23.0 ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl - name: pandas - version: 3.0.2 - sha256: ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df + - mkdocstrings>=0.30 + - mkdocs-autorefs>=1.4 + - griffelib>=2.0 + - typing-extensions>=4.0 ; python_full_version < '3.11' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl + name: distlib + version: 0.4.0 + sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 +- pypi: https://files.pythonhosted.org/packages/33/b2/986d1220f6ee931e338d272bc1f3ec02cfe5f9b5fad84e95afdad57f1ebc/format_docstring-0.2.7-py3-none-any.whl + name: format-docstring + version: 0.2.7 + sha256: c9d50eafebe0f260e3270ca662ff3a0ed4050f64d95e352f8c5f88d9aede42d6 + requires_dist: + - click>=8.0 + - jupyter-notebook-parser>=0.1.4 + - tomli>=1.1.0 ; python_full_version < '3.11' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/33/eb/f9f1ded8e4db9638f9530c3782eb01f5ab04945f4cb9e597a51c203fa4c5/diffpy_pdffit2-1.6.0.tar.gz + name: diffpy-pdffit2 + version: 1.6.0 + sha256: 11a65466f8790f5ac7ae45f2f3fc0d5d116d156d274bcfc079df653123d080e2 + requires_dist: + - diffpy-structure + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl + name: tokenize-rt + version: 6.2.0 + sha256: a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/34/0b/b9d1911cfefa61399821dfb37f486d83e0f42630a8d12f7194270c417002/llvmlite-0.47.0-cp311-cp311-macosx_11_0_arm64.whl + name: llvmlite + version: 0.47.0 + sha256: 74090f0dcfd6f24ebbef3f21f11e38111c4d7e6919b54c4416e1e357c3446b07 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl + name: coverage + version: 7.13.5 + sha256: 145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587 requires_dist: - - numpy>=1.26.0 ; python_full_version < '3.14' - - numpy>=2.3.3 ; python_full_version >= '3.14' - - python-dateutil>=2.8.2 - - tzdata ; sys_platform == 'win32' - - tzdata ; sys_platform == 'emscripten' - - hypothesis>=6.116.0 ; extra == 'test' - - pytest>=8.3.4 ; extra == 'test' - - pytest-xdist>=3.6.1 ; extra == 'test' - - pyarrow>=13.0.0 ; extra == 'pyarrow' - - bottleneck>=1.4.2 ; extra == 'performance' - - numba>=0.60.0 ; extra == 'performance' - - numexpr>=2.10.2 ; extra == 'performance' - - scipy>=1.14.1 ; extra == 'computation' - - xarray>=2024.10.0 ; extra == 'computation' - - fsspec>=2024.10.0 ; extra == 'fss' - - s3fs>=2024.10.0 ; extra == 'aws' - - gcsfs>=2024.10.0 ; extra == 'gcp' - - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.5 ; extra == 'excel' - - python-calamine>=0.3.0 ; extra == 'excel' - - pyxlsb>=1.0.10 ; extra == 'excel' - - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.2.0 ; extra == 'excel' - - pyarrow>=13.0.0 ; extra == 'parquet' - - pyarrow>=13.0.0 ; extra == 'feather' - - pyiceberg>=0.8.1 ; extra == 'iceberg' - - tables>=3.10.1 ; extra == 'hdf5' - - pyreadstat>=1.2.8 ; extra == 'spss' - - sqlalchemy>=2.0.36 ; extra == 'postgresql' - - psycopg2>=2.9.10 ; extra == 'postgresql' - - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.36 ; extra == 'mysql' - - pymysql>=1.1.1 ; extra == 'mysql' - - sqlalchemy>=2.0.36 ; extra == 'sql-other' - - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' - - beautifulsoup4>=4.12.3 ; extra == 'html' - - html5lib>=1.1 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'xml' - - matplotlib>=3.9.3 ; extra == 'plot' - - jinja2>=3.1.5 ; extra == 'output-formatting' - - tabulate>=0.9.0 ; extra == 'output-formatting' - - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.4.2 ; extra == 'clipboard' - - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' - - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - - beautifulsoup4>=4.12.3 ; extra == 'all' - - bottleneck>=1.4.2 ; extra == 'all' - - fastparquet>=2024.11.0 ; extra == 'all' - - fsspec>=2024.10.0 ; extra == 'all' - - gcsfs>=2024.10.0 ; extra == 'all' - - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.116.0 ; extra == 'all' - - jinja2>=3.1.5 ; extra == 'all' - - lxml>=5.3.0 ; extra == 'all' - - matplotlib>=3.9.3 ; extra == 'all' - - numba>=0.60.0 ; extra == 'all' - - numexpr>=2.10.2 ; extra == 'all' - - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.5 ; extra == 'all' - - psycopg2>=2.9.10 ; extra == 'all' - - pyarrow>=13.0.0 ; extra == 'all' - - pyiceberg>=0.8.1 ; extra == 'all' - - pymysql>=1.1.1 ; extra == 'all' - - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.8 ; extra == 'all' - - pytest>=8.3.4 ; extra == 'all' - - pytest-xdist>=3.6.1 ; extra == 'all' - - python-calamine>=0.3.0 ; extra == 'all' - - pytz>=2024.2 ; extra == 'all' - - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.4.2 ; extra == 'all' - - scipy>=1.14.1 ; extra == 'all' - - s3fs>=2024.10.0 ; extra == 'all' - - sqlalchemy>=2.0.36 ; extra == 'all' - - tables>=3.10.1 ; extra == 'all' - - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2024.10.0 ; extra == 'all' - - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.2.0 ; extra == 'all' - - zstandard>=0.23.0 ; extra == 'all' + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/35/8c/a917ef8741729ef8b3228f815240f4859717e600f9498359a6c411ed6992/crysfml-0.6.2-cp313-cp313-macosx_14_0_arm64.whl + name: crysfml + version: 0.6.2 + sha256: 0010858646240f6c64e2711c33ccfffd94ee6602859babd1186785e28d6c5c11 + requires_dist: + - numpy + requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl + name: scipy + version: 1.17.1 + sha256: 37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448 + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl - name: pandas - version: 3.0.2 - sha256: 07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991 +- pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl + name: scipp + version: 26.3.1 + sha256: 03c6dbf8936a2ed62587c5abe8ab5266a5098834a0709321ce799bd1328eb3e6 requires_dist: - - numpy>=1.26.0 ; python_full_version < '3.14' - - numpy>=2.3.3 ; python_full_version >= '3.14' - - python-dateutil>=2.8.2 - - tzdata ; sys_platform == 'win32' - - tzdata ; sys_platform == 'emscripten' - - hypothesis>=6.116.0 ; extra == 'test' - - pytest>=8.3.4 ; extra == 'test' - - pytest-xdist>=3.6.1 ; extra == 'test' - - pyarrow>=13.0.0 ; extra == 'pyarrow' - - bottleneck>=1.4.2 ; extra == 'performance' - - numba>=0.60.0 ; extra == 'performance' - - numexpr>=2.10.2 ; extra == 'performance' - - scipy>=1.14.1 ; extra == 'computation' - - xarray>=2024.10.0 ; extra == 'computation' - - fsspec>=2024.10.0 ; extra == 'fss' - - s3fs>=2024.10.0 ; extra == 'aws' - - gcsfs>=2024.10.0 ; extra == 'gcp' - - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.5 ; extra == 'excel' - - python-calamine>=0.3.0 ; extra == 'excel' - - pyxlsb>=1.0.10 ; extra == 'excel' - - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.2.0 ; extra == 'excel' - - pyarrow>=13.0.0 ; extra == 'parquet' - - pyarrow>=13.0.0 ; extra == 'feather' - - pyiceberg>=0.8.1 ; extra == 'iceberg' - - tables>=3.10.1 ; extra == 'hdf5' - - pyreadstat>=1.2.8 ; extra == 'spss' - - sqlalchemy>=2.0.36 ; extra == 'postgresql' - - psycopg2>=2.9.10 ; extra == 'postgresql' - - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.36 ; extra == 'mysql' - - pymysql>=1.1.1 ; extra == 'mysql' - - sqlalchemy>=2.0.36 ; extra == 'sql-other' - - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' - - beautifulsoup4>=4.12.3 ; extra == 'html' - - html5lib>=1.1 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'xml' - - matplotlib>=3.9.3 ; extra == 'plot' - - jinja2>=3.1.5 ; extra == 'output-formatting' - - tabulate>=0.9.0 ; extra == 'output-formatting' - - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.4.2 ; extra == 'clipboard' - - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' - - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - - beautifulsoup4>=4.12.3 ; extra == 'all' - - bottleneck>=1.4.2 ; extra == 'all' - - fastparquet>=2024.11.0 ; extra == 'all' - - fsspec>=2024.10.0 ; extra == 'all' - - gcsfs>=2024.10.0 ; extra == 'all' - - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.116.0 ; extra == 'all' - - jinja2>=3.1.5 ; extra == 'all' - - lxml>=5.3.0 ; extra == 'all' - - matplotlib>=3.9.3 ; extra == 'all' - - numba>=0.60.0 ; extra == 'all' - - numexpr>=2.10.2 ; extra == 'all' - - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.5 ; extra == 'all' - - psycopg2>=2.9.10 ; extra == 'all' - - pyarrow>=13.0.0 ; extra == 'all' - - pyiceberg>=0.8.1 ; extra == 'all' - - pymysql>=1.1.1 ; extra == 'all' - - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.8 ; extra == 'all' - - pytest>=8.3.4 ; extra == 'all' - - pytest-xdist>=3.6.1 ; extra == 'all' - - python-calamine>=0.3.0 ; extra == 'all' - - pytz>=2024.2 ; extra == 'all' - - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.4.2 ; extra == 'all' - - scipy>=1.14.1 ; extra == 'all' - - s3fs>=2024.10.0 ; extra == 'all' - - sqlalchemy>=2.0.36 ; extra == 'all' - - tables>=3.10.1 ; extra == 'all' - - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2024.10.0 ; extra == 'all' - - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.2.0 ; extra == 'all' - - zstandard>=0.23.0 ; extra == 'all' + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl - name: pandas - version: 3.0.2 - sha256: dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c +- pypi: https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl + name: fonttools + version: 4.62.1 + sha256: 7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056 requires_dist: - - numpy>=1.26.0 ; python_full_version < '3.14' - - numpy>=2.3.3 ; python_full_version >= '3.14' - - python-dateutil>=2.8.2 - - tzdata ; sys_platform == 'win32' - - tzdata ; sys_platform == 'emscripten' - - hypothesis>=6.116.0 ; extra == 'test' - - pytest>=8.3.4 ; extra == 'test' - - pytest-xdist>=3.6.1 ; extra == 'test' - - pyarrow>=13.0.0 ; extra == 'pyarrow' - - bottleneck>=1.4.2 ; extra == 'performance' - - numba>=0.60.0 ; extra == 'performance' - - numexpr>=2.10.2 ; extra == 'performance' - - scipy>=1.14.1 ; extra == 'computation' - - xarray>=2024.10.0 ; extra == 'computation' - - fsspec>=2024.10.0 ; extra == 'fss' - - s3fs>=2024.10.0 ; extra == 'aws' - - gcsfs>=2024.10.0 ; extra == 'gcp' - - odfpy>=1.4.1 ; extra == 'excel' - - openpyxl>=3.1.5 ; extra == 'excel' - - python-calamine>=0.3.0 ; extra == 'excel' - - pyxlsb>=1.0.10 ; extra == 'excel' - - xlrd>=2.0.1 ; extra == 'excel' - - xlsxwriter>=3.2.0 ; extra == 'excel' - - pyarrow>=13.0.0 ; extra == 'parquet' - - pyarrow>=13.0.0 ; extra == 'feather' - - pyiceberg>=0.8.1 ; extra == 'iceberg' - - tables>=3.10.1 ; extra == 'hdf5' - - pyreadstat>=1.2.8 ; extra == 'spss' - - sqlalchemy>=2.0.36 ; extra == 'postgresql' - - psycopg2>=2.9.10 ; extra == 'postgresql' - - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' - - sqlalchemy>=2.0.36 ; extra == 'mysql' - - pymysql>=1.1.1 ; extra == 'mysql' - - sqlalchemy>=2.0.36 ; extra == 'sql-other' - - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' - - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' - - beautifulsoup4>=4.12.3 ; extra == 'html' - - html5lib>=1.1 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'html' - - lxml>=5.3.0 ; extra == 'xml' - - matplotlib>=3.9.3 ; extra == 'plot' - - jinja2>=3.1.5 ; extra == 'output-formatting' - - tabulate>=0.9.0 ; extra == 'output-formatting' - - pyqt5>=5.15.9 ; extra == 'clipboard' - - qtpy>=2.4.2 ; extra == 'clipboard' - - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' - - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - - beautifulsoup4>=4.12.3 ; extra == 'all' - - bottleneck>=1.4.2 ; extra == 'all' - - fastparquet>=2024.11.0 ; extra == 'all' - - fsspec>=2024.10.0 ; extra == 'all' - - gcsfs>=2024.10.0 ; extra == 'all' - - html5lib>=1.1 ; extra == 'all' - - hypothesis>=6.116.0 ; extra == 'all' - - jinja2>=3.1.5 ; extra == 'all' - - lxml>=5.3.0 ; extra == 'all' - - matplotlib>=3.9.3 ; extra == 'all' - - numba>=0.60.0 ; extra == 'all' - - numexpr>=2.10.2 ; extra == 'all' - - odfpy>=1.4.1 ; extra == 'all' - - openpyxl>=3.1.5 ; extra == 'all' - - psycopg2>=2.9.10 ; extra == 'all' - - pyarrow>=13.0.0 ; extra == 'all' - - pyiceberg>=0.8.1 ; extra == 'all' - - pymysql>=1.1.1 ; extra == 'all' - - pyqt5>=5.15.9 ; extra == 'all' - - pyreadstat>=1.2.8 ; extra == 'all' - - pytest>=8.3.4 ; extra == 'all' - - pytest-xdist>=3.6.1 ; extra == 'all' - - python-calamine>=0.3.0 ; extra == 'all' - - pytz>=2024.2 ; extra == 'all' - - pyxlsb>=1.0.10 ; extra == 'all' - - qtpy>=2.4.2 ; extra == 'all' - - scipy>=1.14.1 ; extra == 'all' - - s3fs>=2024.10.0 ; extra == 'all' - - sqlalchemy>=2.0.36 ; extra == 'all' - - tables>=3.10.1 ; extra == 'all' - - tabulate>=0.9.0 ; extra == 'all' - - xarray>=2024.10.0 ; extra == 'all' - - xlrd>=2.0.1 ; extra == 'all' - - xlsxwriter>=3.2.0 ; extra == 'all' - - zstandard>=0.23.0 ; extra == 'all' - requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 - sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f - md5: 457c2c8c08e54905d6954e79cb5b5db9 - depends: - - python !=3.0,!=3.1,!=3.2,!=3.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pandocfilters?source=hash-mapping - size: 11627 - timestamp: 1631603397334 -- conda: https://conda.anaconda.org/conda-forge/noarch/paramiko-4.0.0-pyhd8ed1ab_0.conda - sha256: ce76d5a1fc6c7ef636cbdbf14ce2d601a1bfa0dd8d286507c1fd02546fccb94b - md5: 1a884d2b1ea21abfb73911dcdb8342e4 - depends: - - bcrypt >=3.2 - - cryptography >=3.3 - - invoke >=2.0 - - pynacl >=1.5 - - python >=3.9 - license: LGPL-2.1-or-later - license_family: LGPL - purls: - - pkg:pypi/paramiko?source=hash-mapping - size: 159896 - timestamp: 1755102147074 -- 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=compressed-mapping - size: 82472 - timestamp: 1777722955579 -- pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - name: partd - version: 1.4.2 - sha256: 978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl + name: lmfit + version: 1.3.4 + sha256: afce1593b42324d37ae2908249b0c55445e2f4c1a0474ff706a8e2f7b5d949fa + requires_dist: + - asteval>=1.0 + - numpy>=1.24 + - scipy>=1.10.0 + - uncertainties>=3.2.2 + - dill>=0.3.4 + - build ; extra == 'dev' + - check-wheel-contents ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - pre-commit ; extra == 'dev' + - twine ; extra == 'dev' + - cairosvg ; extra == 'doc' + - corner ; extra == 'doc' + - emcee>=3.0.0 ; extra == 'doc' + - ipykernel ; extra == 'doc' + - jupyter-sphinx>=0.2.4 ; extra == 'doc' + - matplotlib ; extra == 'doc' + - numdifftools ; extra == 'doc' + - pandas ; extra == 'doc' + - pillow ; extra == 'doc' + - pycairo ; sys_platform == 'win32' and extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-gallery>=0.10 ; extra == 'doc' + - sphinxcontrib-svg2pdfconverter ; extra == 'doc' + - sympy ; extra == 'doc' + - coverage ; extra == 'test' + - flaky ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - lmfit[dev,doc,test] ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl + name: xraydb + version: 4.5.8 + sha256: 2215baafa6a03d00d0254a94525aafc6493c8c285e4ac4477fbd6271b25e6a51 + requires_dist: + - numpy>=1.19 + - scipy>=1.6 + - sqlalchemy>=2.0.1 + - platformdirs + - build ; extra == 'dev' + - twine ; extra == 'dev' + - sphinx ; extra == 'doc' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - coverage ; extra == 'test' + - xraydb[dev,doc,test] ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl + name: pip + version: 26.1.1 + sha256: 99cb1c2899893b075ff56e4ed0af55669a955b49ad7fb8d8603ecdaf4ed653fb + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl + name: fonttools + version: 4.62.1 + sha256: c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79 + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz + name: python-bidi + version: 0.6.9 + sha256: 001c1769893fd859216b0dc39dd4679c7260bf292c3637b727a928402a254322 requires_dist: - - locket - - toolz - - numpy>=1.20.0 ; extra == 'complete' - - pandas>=1.3 ; extra == 'complete' - - pyzmq ; extra == 'complete' - - blosc ; extra == 'complete' + - nox ; extra == 'dev' + - pytest ; extra == 'dev' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.1.1-pyhd8ed1ab_0.conda - sha256: 6eaee417d33f298db79bc7185ab1208604c0e6cf51dade34cd513c6f9db9c6f3 - md5: 11adc78451c998c0fd162584abfa3559 - depends: - - python >=3.10 - license: MPL-2.0 - license_family: MOZILLA - purls: - - pkg:pypi/pathspec?source=compressed-mapping - size: 56559 - timestamp: 1777271601895 -- pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - name: periodictable - version: 2.1.0 - sha256: e9155d2bf5ac10050abeff2f99096d4f04312c0c8a6bb432e28744367c5064b3 +- pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl + name: spdx-headers + version: 1.5.1 + sha256: 73bcb1ed087824b55ccaa497d03d8f0f0b0eaf30e5f0f7d5bbd29d2c4fe78fcf requires_dist: - - pyparsing>=3.0.0 - - numpy - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a - md5: d0d408b1f18883a944376da5cf8101ea - depends: - - ptyprocess >=0.5 - - python >=3.9 - license: ISC - purls: - - pkg:pypi/pexpect?source=hash-mapping - size: 53561 - timestamp: 1733302019362 + - chardet>=5.2.0 + - requests>=2.32.3 + - black>=23.0.0 ; extra == 'dev' + - build>=0.10.0 ; extra == 'dev' + - hatch>=1.9.0 ; extra == 'dev' + - isort>=5.12.0 ; extra == 'dev' + - mypy>=1.0.0 ; extra == 'dev' + - pre-commit>=4.3.0 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'dev' + - pytest>=7.0.0 ; extra == 'dev' + - ruff>=0.5.0 ; extra == 'dev' + - twine>=4.0.0 ; extra == 'dev' + - types-requests>=2.31.0.6 ; extra == 'dev' + - pytest-cov>=4.0.0 ; extra == 'test' + - pytest-mock>=3.10.0 ; extra == 'test' + - pytest>=7.0.0 ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3e/9a/a9383ff12698d5306522122e7007d9fd58f903e00a7715506e3ff5be1678/python_bidi-0.6.9-cp311-cp311-macosx_11_0_arm64.whl + name: python-bidi + version: 0.6.9 + sha256: c3554a3cb9b95e503bc348a8c461b330bf1812c500f51bc9bbd789544fc4a8aa + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl + name: widgetsnbextension + version: 4.0.15 + sha256: 8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366 + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl name: pillow version: 12.2.0 @@ -9576,138 +9828,199 @@ packages: - trove-classifiers>=2024.10.12 ; extra == 'tests' - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl - name: pillow - version: 12.2.0 - sha256: 71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65 +- pypi: https://files.pythonhosted.org/packages/3f/d0/7b958df957e4827837b590944008f0b28078f552b451f7407b4b3d54f574/asciichartpy-1.5.25-py2.py3-none-any.whl + name: asciichartpy + version: 1.5.25 + sha256: 33c417a3c8ef7d0a11b98eb9ea6dd9b2c1b17559e539b207a17d26d4302d0258 requires_dist: - - furo ; extra == 'docs' - - olefile ; extra == 'docs' - - sphinx>=8.2 ; extra == 'docs' - - sphinx-autobuild ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-inline-tabs ; extra == 'docs' - - sphinxext-opengraph ; extra == 'docs' - - olefile ; extra == 'fpx' - - olefile ; extra == 'mic' - - arro3-compute ; extra == 'test-arrow' - - arro3-core ; extra == 'test-arrow' - - nanoarrow ; extra == 'test-arrow' - - pyarrow ; extra == 'test-arrow' - - check-manifest ; extra == 'tests' - - coverage>=7.4.2 ; extra == 'tests' - - defusedxml ; extra == 'tests' - - markdown2 ; extra == 'tests' - - olefile ; extra == 'tests' - - packaging ; extra == 'tests' - - pyroma>=5 ; extra == 'tests' - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-timeout ; extra == 'tests' - - pytest-xdist ; extra == 'tests' - - trove-classifiers>=2024.10.12 ; extra == 'tests' - - defusedxml ; extra == 'xmp' + - setuptools + - flake8 ; extra == 'qa' +- pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl + name: typer + version: 0.25.1 + sha256: 75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89 + requires_dist: + - click>=8.2.1 + - shellingham>=1.3.0 + - rich>=13.8.0 + - annotated-doc>=0.0.2 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: pillow - version: 12.2.0 - sha256: eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3 +- pypi: https://files.pythonhosted.org/packages/42/d9/27b13bc9419bf5dae02905b348f16ca827646cd76244ddd326f1a8139a6a/cyclebane-24.10.0-py3-none-any.whl + name: cyclebane + version: 24.10.0 + sha256: 902dd318667e4a222afc270cc5bc72c67d5d6047d2e0e1c36018885fb80f5e5d requires_dist: - - furo ; extra == 'docs' - - olefile ; extra == 'docs' - - sphinx>=8.2 ; extra == 'docs' - - sphinx-autobuild ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-inline-tabs ; extra == 'docs' - - sphinxext-opengraph ; extra == 'docs' - - olefile ; extra == 'fpx' - - olefile ; extra == 'mic' - - arro3-compute ; extra == 'test-arrow' - - arro3-core ; extra == 'test-arrow' - - nanoarrow ; extra == 'test-arrow' - - pyarrow ; extra == 'test-arrow' - - check-manifest ; extra == 'tests' - - coverage>=7.4.2 ; extra == 'tests' - - defusedxml ; extra == 'tests' - - markdown2 ; extra == 'tests' - - olefile ; extra == 'tests' - - packaging ; extra == 'tests' - - pyroma>=5 ; extra == 'tests' - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-timeout ; extra == 'tests' - - pytest-xdist ; extra == 'tests' - - trove-classifiers>=2024.10.12 ; extra == 'tests' - - defusedxml ; extra == 'xmp' + - networkx requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl - name: pillow - version: 12.2.0 - sha256: 6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60 +- pypi: https://files.pythonhosted.org/packages/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.whl + name: pycairo + version: 1.29.0 + sha256: 91114e4b3fbf4287c2b0788f83e1f566ce031bda49cf1c3c3c19c3e986e95c38 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl + name: mpmath + version: 1.3.0 + sha256: a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c requires_dist: - - furo ; extra == 'docs' - - olefile ; extra == 'docs' - - sphinx>=8.2 ; extra == 'docs' - - sphinx-autobuild ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-inline-tabs ; extra == 'docs' - - sphinxext-opengraph ; extra == 'docs' - - olefile ; extra == 'fpx' - - olefile ; extra == 'mic' - - arro3-compute ; extra == 'test-arrow' - - arro3-core ; extra == 'test-arrow' - - nanoarrow ; extra == 'test-arrow' - - pyarrow ; extra == 'test-arrow' - - check-manifest ; extra == 'tests' - - coverage>=7.4.2 ; extra == 'tests' - - defusedxml ; extra == 'tests' - - markdown2 ; extra == 'tests' - - olefile ; extra == 'tests' - - packaging ; extra == 'tests' - - pyroma>=5 ; extra == 'tests' - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-timeout ; extra == 'tests' - - pytest-xdist ; extra == 'tests' - - trove-classifiers>=2024.10.12 ; extra == 'tests' - - defusedxml ; extra == 'xmp' + - pytest>=4.6 ; extra == 'develop' + - pycodestyle ; extra == 'develop' + - pytest-cov ; extra == 'develop' + - codecov ; extra == 'develop' + - wheel ; extra == 'develop' + - sphinx ; extra == 'docs' + - gmpy2>=2.1.0a4 ; platform_python_implementation != 'PyPy' and extra == 'gmpy' + - pytest>=4.6 ; extra == 'tests' +- pypi: https://files.pythonhosted.org/packages/44/0b/0615dbedb98f5b32a35a53290fbdc6e22306968109278d7e58df82d7a9f6/numba-0.65.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: numba + version: 0.65.1 + sha256: f80ed83774b5173abd6581cd8d2165d1d38e13d2e5c8155c0c0b421784745420 + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl + name: periodictable + version: 2.1.0 + sha256: e9155d2bf5ac10050abeff2f99096d4f04312c0c8a6bb432e28744367c5064b3 + requires_dist: + - pyparsing>=3.0.0 + - numpy + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl + name: arviz + version: 0.23.4 + sha256: c46c7faf8a06abadc9b5b64000584062ecbc20c2298e2bd6dfba04bb01a684ca + requires_dist: + - setuptools>=60.0.0 + - matplotlib>=3.8 + - numpy>=1.26.0 + - scipy>=1.11.0 + - packaging + - pandas>=2.1.0 + - xarray>=2023.7.0 + - h5netcdf>=1.0.2 + - h5py + - typing-extensions>=4.1.0 + - xarray-einstats>=0.3 + - platformdirs + - numba ; extra == 'all' + - netcdf4 ; extra == 'all' + - bokeh>=3 ; extra == 'all' + - contourpy ; extra == 'all' + - ujson ; extra == 'all' + - dask[distributed] ; extra == 'all' + - zarr>=2.5.0,<3 ; extra == 'all' + - xarray>=2024.11.0 ; extra == 'all' + - dm-tree>=0.1.8 ; extra == 'all' + - arviz-base[h5netcdf] ; extra == 'preview' + - arviz-stats[xarray] ; extra == 'preview' + - arviz-plots ; extra == 'preview' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl + name: pandas + version: 3.0.2 + sha256: ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: llvmlite + version: 0.47.0 + sha256: ca14f02e29134e837982497959a8e2193d6035235de1cb41a9cb2bd6da4eedbb requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl - name: pillow - version: 12.2.0 - sha256: 56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f - requires_dist: - - furo ; extra == 'docs' - - olefile ; extra == 'docs' - - sphinx>=8.2 ; extra == 'docs' - - sphinx-autobuild ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-inline-tabs ; extra == 'docs' - - sphinxext-opengraph ; extra == 'docs' - - olefile ; extra == 'fpx' - - olefile ; extra == 'mic' - - arro3-compute ; extra == 'test-arrow' - - arro3-core ; extra == 'test-arrow' - - nanoarrow ; extra == 'test-arrow' - - pyarrow ; extra == 'test-arrow' - - check-manifest ; extra == 'tests' - - coverage>=7.4.2 ; extra == 'tests' - - defusedxml ; extra == 'tests' - - markdown2 ; extra == 'tests' - - olefile ; extra == 'tests' - - packaging ; extra == 'tests' - - pyroma>=5 ; extra == 'tests' - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-timeout ; extra == 'tests' - - pytest-xdist ; extra == 'tests' - - trove-classifiers>=2024.10.12 ; extra == 'tests' - - defusedxml ; extra == 'xmp' +- pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl + name: llvmlite + version: 0.47.0 + sha256: c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl name: pillow version: 12.2.0 - sha256: e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176 + sha256: 71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65 requires_dist: - furo ; extra == 'docs' - olefile ; extra == 'docs' @@ -9726,1625 +10039,1381 @@ packages: - coverage>=7.4.2 ; extra == 'tests' - defusedxml ; extra == 'tests' - markdown2 ; extra == 'tests' - - olefile ; extra == 'tests' - - packaging ; extra == 'tests' - - pyroma>=5 ; extra == 'tests' - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-timeout ; extra == 'tests' - - pytest-xdist ; extra == 'tests' - - trove-classifiers>=2024.10.12 ; extra == 'tests' - - defusedxml ; extra == 'xmp' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl - name: pip - version: 26.1.1 - sha256: 99cb1c2899893b075ff56e4ed0af55669a955b49ad7fb8d8603ecdaf4ed653fb - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/pixi-kernel-0.7.1-pyhbbac1ac_0.conda - sha256: 506c9330b8dc5ae98f4c32629fa59fa40e6bdd42a681c48d2f9554693dd01156 - md5: d57ef7cb7ad6b5d62cef8b9bdf1d400b - depends: - - ipykernel >=6 - - jupyter_client >=7 - - jupyter_server >=2.4 - - msgspec >=0.18 - - python >=3.10 - - returns >=0.23 - - tomli >=2 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pixi-kernel?source=hash-mapping - size: 39509 - timestamp: 1764156429044 -- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.9.6-pyhcf101f3_0.conda - sha256: 8f29915c172f1f7f4f7c9391cd5dac3ebf5d13745c8b7c8006032615246345a5 - md5: 89c0b6d1793601a2a3a3f7d2d3d8b937 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/platformdirs?source=compressed-mapping - size: 25862 - timestamp: 1775741140609 -- pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - name: plopp - version: 26.4.2 - sha256: 5cab99bb0905ce08a1d1d7d82f0f64cee7d594269ec1bd01a8a361bd14ab7bff - requires_dist: - - lazy-loader>=0.4 - - matplotlib>=3.8 - - 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' - - ipywidgets>=8.1.0 ; extra == 'test' - - ipykernel>=6.26,<7 ; extra == 'test' - - mpltoolbox>=24.6.0 ; extra == 'test' - - pandas>=2.2.2 ; extra == 'test' - - plotly>=5.15.0 ; extra == 'test' - - pooch>=1.5 ; extra == 'test' - - pyarrow>=13.0.0 ; extra == 'test' - - pytest>=8.0 ; extra == 'test' - - pythreejs>=2.4.1 ; extra == 'test' - - 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/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl - name: plotly - version: 6.7.0 - sha256: ac8aca1c25c663a59b5b9140a549264a5badde2e057d79b8c772ae2920e32ff0 - requires_dist: - - narwhals>=1.15.1 - - packaging - - anywidget ; extra == 'dev' - - build ; extra == 'dev' - - colorcet ; extra == 'dev' - - fiona<=1.9.6 ; python_full_version < '3.9' and extra == 'dev' - - geopandas ; extra == 'dev' - - inflect ; extra == 'dev' - - jupyterlab ; extra == 'dev' - - kaleido>=1.1.0 ; extra == 'dev' - - numpy>=1.22 ; extra == 'dev' - - orjson ; extra == 'dev' - - pandas ; extra == 'dev' - - pdfrw ; extra == 'dev' - - pillow ; extra == 'dev' - - plotly-geo ; extra == 'dev' - - polars[timezone] ; extra == 'dev' - - pyarrow ; extra == 'dev' - - pyshp ; extra == 'dev' - - pytest ; extra == 'dev' - - pytz ; extra == 'dev' - - requests ; extra == 'dev' - - ruff==0.11.12 ; extra == 'dev' - - scikit-image ; extra == 'dev' - - scipy ; extra == 'dev' - - shapely ; extra == 'dev' - - statsmodels ; extra == 'dev' - - vaex ; python_full_version < '3.10' and extra == 'dev' - - xarray ; extra == 'dev' - - build ; extra == 'dev-build' - - jupyterlab ; extra == 'dev-build' - - pytest ; extra == 'dev-build' - - requests ; extra == 'dev-build' - - ruff==0.11.12 ; extra == 'dev-build' - - pytest ; extra == 'dev-core' - - requests ; extra == 'dev-core' - - ruff==0.11.12 ; extra == 'dev-core' - - anywidget ; extra == 'dev-optional' - - build ; 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' - - jupyterlab ; extra == 'dev-optional' - - kaleido>=1.1.0 ; extra == 'dev-optional' - - numpy>=1.22 ; 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' - - pytest ; extra == 'dev-optional' - - pytz ; extra == 'dev-optional' - - requests ; extra == 'dev-optional' - - ruff==0.11.12 ; 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' - - numpy>=1.22 ; extra == 'express' - - kaleido>=1.1.0 ; extra == 'kaleido' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - name: pluggy - version: 1.6.0 - sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + name: pyhanko + version: 0.35.1 + sha256: 57c085f29d7f87334be2a5fbd38a1ca3c987f40f51aea38bde628497d6d9ef2e requires_dist: - - pre-commit ; extra == 'dev' - - tox ; extra == 'dev' - - pytest ; extra == 'testing' - - pytest-benchmark ; extra == 'testing' - - coverage ; extra == 'testing' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda - sha256: 972e0c1c9f0b1763d482e885732baef7f9a0cbe8686f5e2c6bdd88838619a59a - md5: 7fd2e38f4e608f5ebd80f871352c5495 - depends: - - python >=3.10 - - pywin32-on-windows - - paramiko - - importlib_resources - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/plumbum?source=hash-mapping - size: 103911 - timestamp: 1761911487086 -- pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl - name: ply - version: '3.11' - sha256: 096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce -- pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - name: pooch - version: 1.9.0 - sha256: f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b + - asn1crypto>=1.5.1 + - tzlocal>=4.3 + - pyhanko-certvalidator>=0.31.1,<0.32 + - requests>=2.31.0 + - pyyaml>=6.0 + - cryptography>=48.0.0 + - lxml>=5.4.0 + - fonttools>=4.33.3 ; extra == 'opentype' + - uharfbuzz>=0.25.0,<0.55.0 ; extra == 'opentype' + - qrcode>=7.3.1 ; extra == 'qr' + - pillow>=7.2.0 ; extra == 'image-support' + - python-barcode==0.16.1 ; extra == 'image-support' + - python-pkcs11~=0.9.3 ; extra == 'pkcs11' + - aiohttp>=3.9,<3.14 ; extra == 'async-http' + - xsdata>=24.4,<27.0 ; extra == 'etsi' + - signxml>=4.2.0 ; extra == 'etsi' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/4a/f3/00bb1e867fba351e2d784170955713bee200c43ea306c59f30bd7e748192/dask-2026.3.0-py3-none-any.whl + name: dask + version: 2026.3.0 + sha256: be614b9242b0b38288060fb2d7696125946469c98a1c30e174883fd199e0428d requires_dist: - - platformdirs>=2.5.0 + - click>=8.1 + - cloudpickle>=3.0.0 + - fsspec>=2021.9.0 - packaging>=20.0 - - requests>=2.19.0 - - tqdm>=4.41.0,<5.0.0 ; extra == 'progress' - - paramiko>=2.7.0 ; extra == 'sftp' - - xxhash>=1.4.3 ; extra == 'xxhash' - - pytest-httpserver ; extra == 'test' - - pytest-localftpserver ; extra == 'test' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl - name: pre-commit - version: 4.6.0 - sha256: e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b - requires_dist: - - cfgv>=2.0.0 - - identify>=1.0.0 - - nodeenv>=0.11.1 - - pyyaml>=5.1 - - virtualenv>=20.10.0 + - partd>=1.4.0 + - pyyaml>=5.3.1 + - toolz>=0.12.0 + - importlib-metadata>=4.13.0 ; python_full_version < '3.12' + - numpy>=1.24 ; extra == 'array' + - dask[array] ; extra == 'dataframe' + - pandas>=2.0 ; extra == 'dataframe' + - pyarrow>=16.0 ; extra == 'dataframe' + - distributed>=2026.3.0,<2026.3.1 ; extra == 'distributed' + - bokeh>=3.1.0 ; extra == 'diagnostics' + - jinja2>=2.10.3 ; extra == 'diagnostics' + - dask[array,dataframe,diagnostics,distributed] ; extra == 'complete' + - pyarrow>=16.0 ; extra == 'complete' + - lz4>=4.3.2 ; extra == 'complete' + - pandas[test] ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-rerunfailures ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - pre-commit ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl - name: prettytable - version: 3.17.0 - sha256: aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287 +- pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: contourpy + version: 1.3.3 + sha256: 4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9 requires_dist: - - wcwidth - - pytest ; extra == 'tests' - - pytest-cov ; extra == 'tests' - - pytest-lazy-fixtures ; extra == 'tests' + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl + name: matplotlib + version: 3.10.9 + sha256: dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.25.0-pyhd8ed1ab_0.conda - sha256: 4d7ec90d4f9c1f3b4a50623fefe4ebba69f651b102b373f7c0e9dbbfa43d495c - md5: a11ab1f31af799dd93c3a39881528884 - depends: - - python >=3.10 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/prometheus-client?source=compressed-mapping - size: 57113 - timestamp: 1775771465170 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b - md5: d17ae9db4dc594267181bd199bf9a551 - depends: - - python >=3.9 - - wcwidth - constrains: - - prompt_toolkit 3.0.51 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/prompt-toolkit?source=hash-mapping - size: 271841 - timestamp: 1744724188108 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - sha256: 936189f0373836c1c77cd2d6e71ba1e583e2d3920bf6d015e96ee2d729b5e543 - md5: 1e61ab85dd7c60e5e73d853ea035dc29 - depends: - - prompt-toolkit >=3.0.51,<3.0.52.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 7182 - timestamp: 1744724189376 +- pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl + name: simple-websocket + version: 1.1.0 + sha256: 4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c + requires_dist: + - wsproto + - tox ; extra == 'dev' + - flake8 ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - sphinx ; extra == 'docs' + requires_python: '>=3.6' - pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: propcache version: 0.4.1 sha256: fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl - name: propcache - version: 0.4.1 - sha256: cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74 - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl - name: propcache - version: 0.4.1 - sha256: 6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e +- pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl + name: h5py + version: 3.16.0 + sha256: fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: spglib + version: 2.6.0 + sha256: cc9d856d7cc936dc73b6b303aaf8b2fb4230a8527659373450c6e1139cbb2a5c + requires_dist: + - numpy>=1.20,<3 + - importlib-resources ; python_full_version < '3.10' + - typing-extensions>=4.9.0 ; python_full_version < '3.13' + - pytest ; extra == 'test' + - pyyaml ; extra == 'test' + - sphinx>=7.0 ; extra == 'docs' + - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - myst-parser>=2.0 ; extra == 'docs' + - linkify-it-py ; extra == 'docs' + - sphinx-tippy ; extra == 'docs' + - spglib[test] ; extra == 'test-cov' + - pytest-cov ; extra == 'test-cov' + - spglib[test] ; extra == 'test-benchmark' + - pytest-benchmark ; extra == 'test-benchmark' + - spglib[test] ; extra == 'dev' + - pre-commit ; extra == 'dev' + - spglib[docs] ; extra == 'doc' + - spglib[test] ; extra == 'testing' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: propcache - version: 0.4.1 - sha256: d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f +- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl + name: pluggy + version: 1.6.0 + sha256: e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + requires_dist: + - pre-commit ; extra == 'dev' + - tox ; extra == 'dev' + - pytest ; extra == 'testing' + - pytest-benchmark ; extra == 'testing' + - coverage ; extra == 'testing' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl - name: propcache - version: 0.4.1 - sha256: 364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6 +- pypi: https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl + name: chardet + version: 7.4.3 + sha256: 4fbff1907925b0c5a1064cffb5e040cd5e338585c9c552625f30de6bc2f3107a + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz + name: svglib + version: 1.5.1 + sha256: 3ae765d3a9409ee60c0fb4d24c2deb6a80617aa927054f5bcd7fc98f0695e587 + requires_dist: + - reportlab + - lxml + - tinycss2>=0.6.0 + - cssselect2>=0.2.0 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl + name: ipywidgets + version: 8.1.8 + sha256: ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e + requires_dist: + - comm>=0.1.3 + - ipython>=6.1.0 + - traitlets>=4.3.1 + - widgetsnbextension~=4.0.14 + - jupyterlab-widgets~=3.0.15 + - jsonschema ; extra == 'test' + - ipykernel ; extra == 'test' + - pytest>=3.6.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytz ; extra == 'test' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl + name: numpy + version: 2.4.4 + sha256: 5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: python-bidi + version: 0.6.9 + sha256: 6cf6941062f0589291a396b3d81636b7e35a99105098bc147c48e077c0fb4b45 + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl - name: propcache - version: 0.4.1 - sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e +- pypi: https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: multidict + version: 6.7.1 + sha256: 439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py311haee01d2_0.conda - sha256: 8d9325af538a8f56013e42bbb91a4dc6935aece34476e20bafacf6007b571e86 - md5: 2ed8f6fe8b51d8e19f7621941f7bb95f - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 231786 - timestamp: 1769678156460 -- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda - sha256: f19fd682d874689dfde20bf46d7ec1a28084af34583e0405685981363af47c91 - md5: 25fe6e02c2083497b3239e21b49d8093 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 228663 - timestamp: 1769678153829 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py311he363849_0.conda - sha256: 2b774e8f4ccaac8783d1908f281e4bb20b7036d6708dc913ee7811b070f5b4b5 - md5: 7cff50265141513c960f00ba586780ea - depends: - - python - - python 3.11.* *_cpython - - __osx >=11.0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 245203 - timestamp: 1769678306347 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda - sha256: 1d2a6039fb71d61134b1d6816202529f2f6286c83b59bc1491fd288f5c08046e - md5: ba2d89e51a855963c767648f44c03871 - depends: - - python - - __osx >=11.0 - - python 3.13.* *_cp313 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 242596 - timestamp: 1769678288893 -- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py311hf893f09_0.conda - sha256: 32da17824abadd1f5b46faedfa4964c7b1817b11887c2e8bb4e48628da51b93a - md5: fd968cdacc7967efd0ff5ef1805b812c - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 249478 - timestamp: 1769678166841 -- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda - sha256: 3ec3373748f83069bef93b540de416e637ee30231b222d5df8f712e93f2f9195 - md5: 761b299a6289c77459defea3563f8fc0 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/psutil?source=hash-mapping - size: 246062 - timestamp: 1769678176886 -- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 - md5: 7d9daffbb8d8e0af0f769dbbcd173a54 - depends: - - python >=3.9 - license: ISC - purls: - - pkg:pypi/ptyprocess?source=hash-mapping - size: 19457 - timestamp: 1733302371990 -- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 - md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pure-eval?source=hash-mapping - size: 16668 - timestamp: 1733569518868 -- pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl - name: py - version: 1.11.0 - sha256: 607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 - requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' -- pypi: https://files.pythonhosted.org/packages/25/7d/cea3531f77df694ac7f169378250d85f19f69b09a5f4fa45f650837ae7cc/py3dmol-2.5.4-py2.py3-none-any.whl - name: py3dmol - version: 2.5.4 - sha256: 32806726b5310524a2b5bfee320737f7feef635cafc945c991062806daa9e43a +- pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl + name: mkdocs-material-extensions + version: 1.3.1 + sha256: adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl + name: scipy + version: 1.17.1 + sha256: 7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d requires_dist: - - ipython ; extra == 'ipython' -- pypi: https://files.pythonhosted.org/packages/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.whl - name: pycairo - version: 1.29.0 - sha256: 91114e4b3fbf4287c2b0788f83e1f566ce031bda49cf1c3c3c19c3e986e95c38 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/db/09/a0ab6a246a7ede89e817d749a941df34f27a74bedf15551da51e86ae105e/pycairo-1.29.0-cp311-cp311-win_amd64.whl - name: pycairo - version: 1.29.0 - sha256: 3391532db03f9601c1cee9ebfa15b7d1db183c6020f3e75c1348cee16825934f - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: pycifrw - version: 5.0.1 - sha256: 65a90ee34e46e6be20834493382585277d12daaa8d1a145cb158c163b3d9fc1c + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5b/69/93b34728cc386efdde0c342f8c680b9187dea7beb7adaf6b58a0713be101/mpld3-0.5.12-py3-none-any.whl + name: mpld3 + version: 0.5.12 + sha256: bea31799a4041029a906f53f2662bbf1c49903e0c0bc712b412354158ec7cf54 requires_dist: - - prettytable - - ply - - numpy -- pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl - name: pycifrw - version: 5.0.1 - sha256: e9ad2fdb4fca6398ed5ae50c19908bf6238434893b68d0125bda79a54a03d708 + - jinja2 + - matplotlib +- pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl + name: scippnexus + version: 26.1.1 + sha256: 899a0a5e71291b7809d902c17b6c74addf5a805397eabcec557491ff74eead12 requires_dist: - - prettytable - - ply - - numpy -- pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl + - scipp>=24.2.0 + - scipy>=1.10.0 + - h5py>=3.12 + - pytest>=7.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5c/a5/a8c7562ec39f2647245b52ea4aeb13b5b125b3f48c0c152e9ebce7047a0a/pycifrw-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: pycifrw version: 5.0.1 - sha256: 8632df76b99932408ab432e09b30aa9a6c390df884a5f34f1e1f76201e625156 + sha256: 65a90ee34e46e6be20834493382585277d12daaa8d1a145cb158c163b3d9fc1c requires_dist: - prettytable - ply - numpy -- pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz - name: pycifstar - version: 0.3.0 - sha256: 5892fdf16c83372ee5f32557127d5f36e14b0bbe520883a4e2e70365382f70ed -- pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - name: pycodestyle - version: 2.14.0 - sha256: dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d +- pypi: https://files.pythonhosted.org/packages/5d/a6/e9b8f8a3e99602792b01fa7d0a731737615ab56d8bfd0b52935a0ef88b85/chardet-7.4.3-cp311-cp311-win_amd64.whl + name: chardet + version: 7.4.3 + sha256: ccc1f83ab4bcfb901cf39e0c4ba6bc6e726fc6264735f10e24ceb5cb47387578 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 - md5: 12c566707c80111f9799308d9e265aef - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pycparser?source=hash-mapping - size: 110100 - timestamp: 1733195786147 -- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - sha256: 69700e31165df070e9716315e042196aa92525dae5deb5107785847ab9f4189f - md5: 729843edafc0899b3348bd3f19525b9d - depends: - - typing-inspection >=0.4.2 - - typing_extensions >=4.14.1 - - python >=3.10 - - annotated-types >=0.6.0 - - pydantic-core ==2.46.4 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic?source=compressed-mapping - size: 346511 - timestamp: 1778103405862 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py311h902ca64_0.conda - sha256: ac3faa31154a85f4b1a4618957b4a06623ff2c6a85f8eac20efe9dbac5757d91 - md5: b6cfa054aec29586d383f9e666bd0e9a - depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.11.* *_cp311 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1884122 - timestamp: 1778084220486 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda - sha256: d6705962afa2655cc22edcd075ce1f7e67c4407c005137d6d63c0dc5eaa76f47 - md5: ef0f9efec6ec28b17e22f787c7672c67 - depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.13.* *_cp313 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1893749 - timestamp: 1778084222642 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py311hf7c400d_0.conda - sha256: 194e8872dcda301703c8f1427e480fee6421e3901803ffc863b68eb886372eaf - md5: 80d6864b30e0697b652a9bd580b2d351 - depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - __osx >=11.0 - - python 3.11.* *_cpython - - python_abi 3.11.* *_cp311 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1730463 - timestamp: 1778084304998 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda - sha256: 5bcc20f2c21d8a20d8f2821caf31d8c0ef2fa3bcdaf7a9bdce62e37be819f1d7 - md5: 32bb0d4dbb1be2064c06248a1190aa96 - depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - python 3.13.* *_cp313 - - __osx >=11.0 - - python_abi 3.13.* *_cp313 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1720475 - timestamp: 1778084300413 -- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py311hf51aa87_0.conda - sha256: 4cf860418a6a1a678bcc23bc29a83f4f0916682fd6ab1ff209b4e5ee3dc5e15f - md5: 85efdb748cbec9210e718c4a8860749c - depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1898998 - timestamp: 1778084255268 -- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda - sha256: 6466b7e441adb71e29308de2a87c9787d5d0100601ede2d02ed4f85c251699d6 - md5: 9981bc46be6341306a5473b290b2f366 - depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pydantic-core?source=hash-mapping - size: 1888029 - timestamp: 1778084253466 -- pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl - name: pydoclint - version: 0.8.3 - sha256: 5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164 +- pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + name: pandas + version: 3.0.2 + sha256: 07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl + name: lxml + version: 6.1.0 + sha256: cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: contourpy + version: 1.3.3 + sha256: 51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db requires_dist: - - click>=8.1.0 - - docstring-parser-fork>=0.0.12 - - tomli>=2.0.1 ; python_full_version < '3.11' - - flake8>=4 ; extra == 'flake8' + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl + name: scipp + version: 26.3.1 + sha256: 67d275fc83b062053df9aa7ce3af4d2205109c2bc3ab22467bcd73ceb0a83df2 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/61/2b/e260d50e64690d2a9e405d52ccd18a63c286c5088937dd0107cb23eb3195/diffpy_utils-3.7.2-py3-none-any.whl + name: diffpy-utils + version: 3.7.2 + sha256: 6100600736791a8e4638e3dd476704f4dabe3cab75bcb5c60c83c16a2032519a + requires_dist: + - numpy + - xraydb + - scipy + requires_python: '>=3.10,<3.15' +- pypi: https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl + name: matplotlib + version: 3.10.9 + sha256: b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda - sha256: cf70b2f5ad9ae472b71235e5c8a736c9316df3705746de419b59d442e8348e86 - md5: 16c18772b340887160c79a6acc022db0 - depends: - - python >=3.10 - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/pygments?source=compressed-mapping - size: 893031 - timestamp: 1774796815820 -- pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - name: pyhanko - version: 0.35.1 - sha256: 57c085f29d7f87334be2a5fbd38a1ca3c987f40f51aea38bde628497d6d9ef2e +- pypi: https://files.pythonhosted.org/packages/64/2e/ba6b404a8a0e7c954cdf0fdd2b21723dc41def60f16b69b9b1cbb2e22d91/crysfml-0.6.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: crysfml + version: 0.6.2 + sha256: 734e2180b2c427e77cc9cf9cee4a3651ad563adab28dfe79051f004489f8546c requires_dist: - - asn1crypto>=1.5.1 - - tzlocal>=4.3 - - pyhanko-certvalidator>=0.31.1,<0.32 - - requests>=2.31.0 - - pyyaml>=6.0 - - cryptography>=48.0.0 - - lxml>=5.4.0 - - fonttools>=4.33.3 ; extra == 'opentype' - - uharfbuzz>=0.25.0,<0.55.0 ; extra == 'opentype' - - qrcode>=7.3.1 ; extra == 'qr' - - pillow>=7.2.0 ; extra == 'image-support' - - python-barcode==0.16.1 ; extra == 'image-support' - - python-pkcs11~=0.9.3 ; extra == 'pkcs11' - - aiohttp>=3.9,<3.14 ; extra == 'async-http' - - xsdata>=24.4,<27.0 ; extra == 'etsi' - - signxml>=4.2.0 ; extra == 'etsi' + - numpy + requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl + name: arviz + version: 1.1.0 + sha256: 87ebd21ce052f30d21f932b4166fc31b91c0bc7443f8da7fed3518b342267010 + requires_dist: + - arviz-base>=1.1.0,<1.2.0 + - arviz-stats[xarray]>=1.1.0,<1.2.0 + - arviz-plots>=1.1.0,<1.2.0 + - arviz-plots[bokeh] ; extra == 'bokeh' + - build ; extra == 'check' + - pre-commit ; extra == 'check' + - h5netcdf ; extra == 'doc' + - h5py ; extra == 'doc' + - jupyter-sphinx ; extra == 'doc' + - matplotlib ; extra == 'doc' + - myst-parser[linkify] ; extra == 'doc' + - myst-nb ; extra == 'doc' + - pydata-sphinx-theme>=0.13 ; extra == 'doc' + - sphinx>=5 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - sphinx-notfound-page ; extra == 'doc' + - sphinxcontrib-youtube ; extra == 'doc' + - sphinx-togglebutton ; extra == 'doc' + - sphobjinv ; extra == 'doc' + - arviz-base[h5netcdf] ; extra == 'h5netcdf' + - arviz-plots[matplotlib] ; extra == 'matplotlib' + - arviz-base[netcdf4] ; extra == 'netcdf4' + - arviz-plots[plotly] ; extra == 'plotly' + - pytest ; extra == 'test' + - arviz-base[zarr] ; extra == 'zarr' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl + name: coverage + version: 7.13.5 + sha256: 631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl - name: pyhanko-certvalidator - version: 0.31.1 - sha256: 0aabe25b536ed555f3b18e5eb2c62ff46adeff9d15e27f0c9d5d81952d956f45 +- pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: yarl + version: 1.23.0 + sha256: 34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4 requires_dist: - - asn1crypto>=1.5.1 - - oscrypto>=1.1.0 - - cryptography>=48.0.0 - - uritools>=3.0.1 - - requests>=2.31.0 - - aiohttp>=3.9,<3.14 ; extra == 'async-http' + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: pillow + version: 12.2.0 + sha256: eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl + name: pillow + version: 12.2.0 + sha256: 6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl + name: arviz-base + version: 1.1.0 + sha256: 4f97016f697751038f45d144331a1830c921f0ebc2739d5df343120fba453e83 + requires_dist: + - numpy>=2 + - xarray>=2024.11.0 + - typing-extensions>=3.10 + - lazy-loader>=0.4 + - build ; extra == 'check' + - pre-commit ; extra == 'check' + - docstub==0.4 ; extra == 'check' + - mypy ; extra == 'check' + - pre-commit ; extra == 'ci' + - cloudpickle ; extra == 'ci' + - sphinx-book-theme ; extra == 'doc' + - myst-parser[linkify] ; extra == 'doc' + - myst-nb ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - numpydoc ; extra == 'doc' + - sphinx>=5 ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - jupyter-sphinx ; extra == 'doc' + - h5netcdf[h5py] ; extra == 'doc' + - h5netcdf[h5py] ; extra == 'h5netcdf' + - netcdf4 ; extra == 'netcdf4' + - xarray!=2025.8.0 ; extra == 'test' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - scipy ; extra == 'test' + - zarr ; extra == 'zarr' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl + name: rlpycairo + version: 0.4.0 + sha256: 3ce83825d5761c03bc3571c7db12a336ad51417e63189e3512d11b8922576aa9 + requires_dist: + - pycairo>=1.20.0 + - freetype-py>=2.3 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/6c/25/4f103d1bedb3593718713b3f743df7b3ff3fc68d36d6666c30265ef59c8a/ase-3.28.0-py3-none-any.whl + name: ase + version: 3.28.0 + sha256: 0e24056302d7307b7247f90de281de15e3031c14cf400bedb1116c3b0d0e50b8 + requires_dist: + - numpy>=1.21.6 + - scipy>=1.8.1 + - matplotlib>=3.5.2 + - sphinx ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinxcontrib-video ; extra == 'docs' + - sphinx-gallery ; extra == 'docs' + - pillow ; extra == 'docs' + - pytest>=7.4.0 ; extra == 'test' + - pytest-xdist>=3.2.0 ; extra == 'test' + - spglib>=1.9 ; extra == 'spglib' + - mypy ; extra == 'lint' + - ruff ; extra == 'lint' + - types-docutils ; extra == 'lint' + - types-pymysql ; extra == 'lint' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - name: pymdown-extensions - version: 10.21.2 - sha256: 5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638 +- pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + name: html5lib + version: '1.1' + sha256: 0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d requires_dist: - - markdown>=3.6 - - pyyaml - - pygments>=2.19.1 ; extra == 'extra' + - six>=1.9 + - webencodings + - genshi ; extra == 'all' + - chardet>=2.2 ; extra == 'all' + - lxml ; platform_python_implementation == 'CPython' and extra == 'all' + - chardet>=2.2 ; extra == 'chardet' + - genshi ; extra == 'genshi' + - lxml ; platform_python_implementation == 'CPython' and extra == 'lxml' + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl + name: svglib + version: 1.6.0 + sha256: 9aea8e2e81cbbf9c844460e4c7dc90e0a06aea7983bc201975ccd279d7b2d194 + requires_dist: + - cssselect2>=0.2.0 + - lxml>=6.0.0 + - reportlab>=4.4.3 + - rlpycairo>=0.4.0 + - tinycss2>=0.6.0 + - mypy>=1.18.1 ; extra == 'dev' + - pre-commit>=4.3.0 ; extra == 'dev' + - pytest-cov>=7.0.0 ; extra == 'dev' + - pytest-runner>=6.0.1 ; extra == 'dev' + - pytest>=8.3.5 ; extra == 'dev' + - ruff>=0.13.0 ; extra == 'dev' + - tox>=4.30.2 ; extra == 'dev' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py311hdf6b40b_1.conda - sha256: 4b89f502dc58df0abb27d4677ca4b0ef5230f5c28e24b80eef3dc0c9c968e225 - md5: fb7e00fed4fddee97e6cf0bd3ae9a27e - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.4.1 - - libgcc >=14 - - libsodium >=1.0.21,<1.0.22.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - six - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/pynacl?source=hash-mapping - size: 1151915 - timestamp: 1772171237221 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h5008379_1.conda - sha256: 51e80a7bef95025ad47a92acb69ee0e78f01e107655c86fe76abcde2ac688166 - md5: c4426edfc5514a2c9be6871557bce52b - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.4.1 - - libgcc >=14 - - libsodium >=1.0.21,<1.0.22.0a0 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - six - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/pynacl?source=hash-mapping - size: 1191901 - timestamp: 1772171244923 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py311h38a6bc0_1.conda - sha256: 503b3a2543577f2fb70ddc44113a4da7ecdfdfb2bf51ecd1203adcb93e7257df - md5: 64d4dd58b1eb87d7e6ba084c7dc9259b - depends: - - __osx >=11.0 - - cffi >=1.4.1 - - libsodium >=1.0.21,<1.0.22.0a0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - - six - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/pynacl?source=hash-mapping - size: 1188895 - timestamp: 1772171487639 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pynacl-1.6.2-py313h6940bce_1.conda - sha256: ef6ab85953ebf81b3b0c6d0b6377eb1a318b07823e76d2c8e850365e60d9ca5c - md5: fca94b9ddea6d5c63ccb6b5ad6d8e232 - depends: - - __osx >=11.0 - - cffi >=1.4.1 - - libsodium >=1.0.21,<1.0.22.0a0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - - six - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/pynacl?source=hash-mapping - size: 1192924 - timestamp: 1772171603602 -- conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py311hcd7b28f_1.conda - sha256: cd35ad681895b45f578bac8fd8dafc87116c2db42489dda9bef76ff3518bbc73 - md5: e2197089006b3cda7e03c7a51fa7050e - depends: - - cffi >=1.4.1 - - libsodium >=1.0.21,<1.0.22.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - six - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/pynacl?source=hash-mapping - size: 1147862 - timestamp: 1772171327674 -- conda: https://conda.anaconda.org/conda-forge/win-64/pynacl-1.6.2-py313hed4ef92_1.conda - sha256: 0960e727d0bd70e07bca0c049c4c4afa0b41bcea050135db49f54c1c0c02ed7f - md5: 99b7617a643bed6a403fc908c1e167f0 - depends: - - cffi >=1.4.1 - - libsodium >=1.0.21,<1.0.22.0a0 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - six - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/pynacl?source=hash-mapping - size: 1186552 - timestamp: 1772171311072 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py311hce6e4fa_0.conda - sha256: f6dced0ea4f220abc1278f6217e22ca1c631f6154dd086b0a7a2866589d6b78c - md5: 812e22c5c7859e719ec225ece0c6f2c1 - depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 481434 - timestamp: 1763151508974 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.1-py313h40b429f_0.conda - sha256: 307ca29ebf2317bd2561639b1ee0290fd8c03c3450fa302b9f9437d8df6a5280 - md5: 31a0a72f3466682d0ea2ebcbd7d319b8 - depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - - setuptools - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-core?source=hash-mapping - size: 481508 - timestamp: 1763152124940 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py311h9049b8e_0.conda - sha256: 67e209a5e9ebaf08aa36e2a4b9139ff91a7ecee7c17408d0a3a1ea1dc3a67681 - md5: b215a767b5ef6f16347cacebc409e66b - depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - pyobjc-core 12.1.* - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 383445 - timestamp: 1763160541362 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.1-py313hcc5defa_0.conda - sha256: 194e188d8119befc952d04157079733e2041a7a502d50340ddde632658799fdc - md5: a6d28c8fc266a3d3c3dae183e25c4d31 - depends: - - __osx >=11.0 - - libffi >=3.5.2,<3.6.0a0 - - pyobjc-core 12.1.* - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyobjc-framework-cocoa?source=hash-mapping - size: 376136 - timestamp: 1763160678792 -- pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - name: pyparsing - version: 3.3.2 - sha256: 850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d +- pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl + name: scipy + version: 1.17.1 + sha256: a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl + name: arabic-reshaper + version: 3.0.1 + sha256: 41c5adc2420f85758eada7e880251c4b6a2adbd83377bd27e5d4eba71f648bc7 requires_dist: - - railroad-diagrams ; extra == 'diagrams' - - jinja2 ; extra == 'diagrams' + - fonttools>=4.0 ; extra == 'with-fonttools' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl + name: mkdocstrings + version: 1.0.4 + sha256: 63464b4b29053514f32a1dbbf604e52876d5e638111b0c295ab7ed3cac73ca9b + requires_dist: + - jinja2>=3.1 + - markdown>=3.6 + - markupsafe>=1.1 + - mkdocs>=1.6 + - mkdocs-autorefs>=1.4 + - pymdown-extensions>=6.3 + - mkdocstrings-crystal>=0.3.4 ; extra == 'crystal' + - mkdocstrings-python-legacy>=0.2.1 ; extra == 'python-legacy' + - mkdocstrings-python>=1.16.2 ; extra == 'python' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl + name: frozenlist + version: 1.8.0 + sha256: fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/0c/d6/1d5c60cc17bbdf37c1552d9c03862fc6d32c5836732a0415b2d637edc2d0/pypdf-6.10.2-py3-none-any.whl - name: pypdf - version: 6.10.2 - sha256: aa53be9826655b51c96741e5d7983ca224d898ac0a77896e64636810517624aa +- pypi: https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl + name: pillow + version: 12.2.0 + sha256: 56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f requires_dist: - - typing-extensions>=4.0 ; python_full_version < '3.11' - - cryptography ; extra == 'crypto' - - pycryptodome ; extra == 'cryptodome' - - flit ; extra == 'dev' - - pip-tools ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - pytest-socket ; extra == 'dev' - - pytest-timeout ; extra == 'dev' - - pytest-xdist ; extra == 'dev' - - wheel ; extra == 'dev' - - myst-parser ; extra == 'docs' - - sphinx ; extra == 'docs' - - sphinx-rtd-theme ; extra == 'docs' - - cryptography ; extra == 'full' - - pillow>=8.0.0 ; extra == 'full' - - pillow>=8.0.0 ; extra == 'image' + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl + name: partd + version: 1.4.2 + sha256: 978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f + requires_dist: + - locket + - toolz + - numpy>=1.20.0 ; extra == 'complete' + - pandas>=1.3 ; extra == 'complete' + - pyzmq ; extra == 'complete' + - blosc ; extra == 'complete' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - name: pyproject-hooks - version: 1.2.0 - sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca - md5: e2fd202833c4a981ce8a65974fe4abd1 - depends: - - __win - - python >=3.9 - - win_inet_pton - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pysocks?source=hash-mapping - size: 21784 - timestamp: 1733217448189 -- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 - md5: 461219d1a5bd61342293efa2c0c90eac - depends: - - __unix - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pysocks?source=hash-mapping - size: 21085 - timestamp: 1733217331982 -- pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl - name: pytest - version: 9.0.3 - sha256: 2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 +- pypi: https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl + name: msgpack + version: 1.1.2 + sha256: a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl + name: mike + version: 2.2.0 + sha256: e1f4981c1152eec7c2490a3401142292cc47d686194188416db2648fdfe1d040 requires_dist: - - colorama>=0.4 ; sys_platform == 'win32' - - exceptiongroup>=1 ; python_full_version < '3.11' - - iniconfig>=1.0.1 - - packaging>=22 - - pluggy>=1.5,<2 - - pygments>=2.7.2 - - tomli>=1 ; python_full_version < '3.11' - - argcomplete ; extra == 'dev' - - attrs>=19.2 ; extra == 'dev' - - hypothesis>=3.56 ; extra == 'dev' - - mock ; extra == 'dev' - - requests ; extra == 'dev' + - jinja2>=2.7 + - mkdocs~=1.0 + - pyparsing>=3.0 + - pyyaml>=5.1 + - pyyaml-env-tag + - verspec + - importlib-metadata ; python_full_version < '3.10' + - importlib-resources ; python_full_version < '3.10' + - coverage ; extra == 'dev' + - flake8-quotes ; extra == 'dev' + - flake8>=3.0 ; extra == 'dev' + - shtab ; extra == 'dev' + - coverage ; extra == 'test' + - flake8-quotes ; extra == 'test' + - flake8>=3.0 ; extra == 'test' + - shtab ; extra == 'test' +- pypi: https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl + name: llvmlite + version: 0.47.0 + sha256: a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/78/3c/2a612b95ddbb9a6bdcb47b7a93c4884f74c6ff22356b2f7b213b16e65c35/pycifstar-0.3.0.tar.gz + name: pycifstar + version: 0.3.0 + sha256: 5892fdf16c83372ee5f32557127d5f36e14b0bbe520883a4e2e70365382f70ed +- pypi: https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl + name: numba + version: 0.65.1 + sha256: 1735c15c1134a5108b4d6a5c77fc0947924ea066a738dc09a52008c13df9cad3 + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl + name: multidict + version: 6.7.1 + sha256: 935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: matplotlib + version: 3.10.9 + sha256: 8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl + name: scipp + version: 26.3.1 + sha256: 8dfe8adedb5cba05acaaea15e3b6fe1820ac2f497e87c1e581ba4be9d82c53bb + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl + name: yarl + version: 1.23.0 + sha256: baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3 + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + 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.4 + sha256: 78b8cfaf9fbcbf2fd77f6d4a2f8c906b0e03a794804ba6caf64d56d6f6cce4d4 + requires_dist: + - numpy + - scipy + - h5py + - dill + - cloudpickle + - matplotlib + - blinker + - aiohttp + - python-socketio + - plotly + - mpld3 + - msgpack + - uncertainties + - build ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff ; extra == 'dev' + - wheel ; extra == 'dev' - setuptools ; extra == 'dev' - - xmlschema ; extra == 'dev' + - sphinx ; extra == 'dev' + - versioningit ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - name: pytest-cov - version: 7.1.0 - sha256: a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 +- pypi: https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl + name: matplotlib + version: 3.10.9 + sha256: d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f requires_dist: - - coverage[toml]>=7.10.6 - - pluggy>=1.2 - - pytest>=7 - - process-tests ; extra == 'testing' - - pytest-xdist ; extra == 'testing' - - virtualenv ; extra == 'testing' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl - name: pytest-xdist - version: 3.8.0 - sha256: 202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88 + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: aiohttp + version: 3.13.5 + sha256: 3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc requires_dist: - - execnet>=2.1 - - pytest>=7.0.0 - - filelock ; extra == 'testing' - - psutil>=3.0 ; extra == 'psutil' - - setproctitle ; extra == 'setproctitle' + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.15-hd63d673_0_cpython.conda - sha256: bf6a32c69889d38482436a786bea32276756cedf0e9805cc856ffd088e8d00f0 - md5: a5ebcefec0c12a333bcd6d7bf3bddc1f - depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.4,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - liblzma >=5.8.2,<6.0a0 - - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libuuid >=2.41.3,<3.0a0 - - libxcrypt >=4.4.36 - - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.5,<4.0a0 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - constrains: - - python_abi 3.11.* *_cp311 - license: Python-2.0 - purls: [] - size: 30949404 - timestamp: 1772730362552 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.13-h6add32d_100_cp313.conda - build_number: 100 - sha256: 7f77eb57648f545c1f58e10035d0d9d66b0a0efb7c4b58d3ed89ec7269afdde1 - md5: 05051be49267378d2fcd12931e319ac3 - depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.5,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - liblzma >=5.8.2,<6.0a0 - - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.52.0,<4.0a0 - - libuuid >=2.42,<3.0a0 - - libzlib >=1.3.2,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.6,<4.0a0 - - python_abi 3.13.* *_cp313 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - license: Python-2.0 - purls: [] - size: 37358322 - timestamp: 1775614712638 - python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.11.15-h8561d8f_0_cpython.conda - sha256: 9a846065863925b2562126a5c6fecd7a972e84aaa4de9e686ad3715ca506acfa - md5: 49c7d96c58b969585cf09fb01d74e08e - depends: - - __osx >=11.0 - - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.4,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.2,<6.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.5,<4.0a0 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - constrains: - - python_abi 3.11.* *_cp311 - license: Python-2.0 - purls: [] - size: 14753109 - timestamp: 1772730203101 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.13-h20e6be0_100_cp313.conda - build_number: 100 - sha256: d0fffc5fde21d1ae350da545dfb9e115a8c53bed8a9c5761f9efd4a5581853c1 - md5: 9991a930e81d3873eba7a299ba783ec4 - depends: - - __osx >=11.0 - - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.5,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.2,<6.0a0 - - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.52.0,<4.0a0 - - libzlib >=1.3.2,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.6,<4.0a0 - - python_abi 3.13.* *_cp313 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - license: Python-2.0 - purls: [] - size: 12966447 - timestamp: 1775615694085 - python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.11.15-h0159041_0_cpython.conda - sha256: a1f1031088ce69bc99c82b95980c1f54e16cbd5c21f042e9c1ea25745a8fc813 - md5: d09dbf470b41bca48cbe6a78ba1e009b - depends: - - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.4,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.2,<6.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.5,<4.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - constrains: - - python_abi 3.11.* *_cp311 - license: Python-2.0 - purls: [] - size: 18416208 - timestamp: 1772728847666 -- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.13-h09917c8_100_cp313.conda - build_number: 100 - sha256: b8108d7f83f71fb15fbb4a263406c2065a8990b3d7eba2cbd7a3075b9a6392ba - md5: 7065f7067762c4c2bda1912f18d16239 - depends: - - bzip2 >=1.0.8,<2.0a0 - - libexpat >=2.7.5,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - liblzma >=5.8.2,<6.0a0 - - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.52.0,<4.0a0 - - libzlib >=1.3.2,<2.0a0 - - openssl >=3.5.6,<4.0a0 - - python_abi 3.13.* *_cp313 - - tk >=8.6.13,<8.7.0a0 - - tzdata - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Python-2.0 - purls: [] - size: 16618694 - timestamp: 1775613654892 - python_site_packages_path: Lib/site-packages -- pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: python-bidi - version: 0.6.9 - sha256: 29e40e02bf2bddec225730eb3ba9f4ca948dced9877358458b74d7a447d936ac +- pypi: https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: kiwisolver + version: 1.5.0 + sha256: 2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl + name: pre-commit + version: 4.6.0 + sha256: e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b requires_dist: - - nox ; extra == 'dev' - - pytest ; extra == 'dev' + - cfgv>=2.0.0 + - identify>=1.0.0 + - nodeenv>=0.11.1 + - pyyaml>=5.1 + - virtualenv>=20.10.0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl + name: filelock + version: 3.29.0 + sha256: 96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl + name: rich + version: 15.0.0 + sha256: 33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb + requires_dist: + - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' + - markdown-it-py>=2.2.0 + - pygments>=2.13.0,<3.0.0 + requires_python: '>=3.9.0' +- pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl + name: scippneutron + version: 26.4.1 + sha256: 3b9865ebdb7923eb25739b7cda624555d45275341000f099c1dcf371b1dd7e35 + requires_dist: + - python-dateutil>=2.8 + - email-validator>=2 + - h5py>=3.12 + - lazy-loader>=0.4 + - mpltoolbox>=24.6.0 + - numpy>=1.20 + - plopp>=26.4.1 + - pydantic>=2 + - scipp>=24.7.0 + - scippnexus>=23.11.0 + - scipy>=1.7.0 + - scipp[all]>=23.7.0 ; extra == 'all' + - pooch>=1.5 ; extra == 'all' + - hypothesis>=6.100 ; extra == 'test' + - ipympl>0.9.0 ; extra == 'test' + - ipykernel>6.30 ; extra == 'test' + - pace-neutrons>=0.3 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - psutil>=5.0 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pythreejs>=2.4.1 ; extra == 'test' + - sciline>=25.1.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/3c/0c/c73042d7ae043f4e3ff51406821ad70904249708a6bc4f0dc94f5f78ce9b/python_bidi-0.6.9.tar.gz - name: python-bidi - version: 0.6.9 - sha256: 001c1769893fd859216b0dc39dd4679c7260bf292c3637b727a928402a254322 +- pypi: https://files.pythonhosted.org/packages/83/81/bdd4bfabe70b7c9a8c0716a722ced4ebd27311afd1f4800cd405d3229c1b/pycifrw-5.0.1-cp313-cp313-macosx_11_0_arm64.whl + name: pycifrw + version: 5.0.1 + sha256: e9ad2fdb4fca6398ed5ae50c19908bf6238434893b68d0125bda79a54a03d708 requires_dist: - - nox ; extra == 'dev' - - pytest ; extra == 'dev' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/3e/9a/a9383ff12698d5306522122e7007d9fd58f903e00a7715506e3ff5be1678/python_bidi-0.6.9-cp311-cp311-macosx_11_0_arm64.whl - name: python-bidi - version: 0.6.9 - sha256: c3554a3cb9b95e503bc348a8c461b330bf1812c500f51bc9bbd789544fc4a8aa + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/85/d7/9b6ac05350ab7f7d3a730ff143ff3e2cada54514117c37be37e26dc91242/docstripy-0.7.2-py3-none-any.whl + name: docstripy + version: 0.7.2 + sha256: c4ba35de6c1b1c51f7afad4a46d8953aad55dce1a490d198f7e98c8c63efefda requires_dist: - - nox ; extra == 'dev' - - pytest ; extra == 'dev' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/5a/38/f1efc72736fb39e75802efcb5e2ba8eb02c7ee3687940047234bc607e3a5/python_bidi-0.6.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: python-bidi - version: 0.6.9 - sha256: 6cf6941062f0589291a396b3d81636b7e35a99105098bc147c48e077c0fb4b45 + - nbformat + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/87/6f/cc2b231dc78d8c3aaa674a676db190b8f8071c50134af8f8cf39b9b8e8df/pydoclint-0.8.3-py3-none-any.whl + name: pydoclint + version: 0.8.3 + sha256: 5fc9b82d0d515afce0908cb70e8ff695a68b19042785c248c4f227ad66b4a164 requires_dist: - - nox ; extra == 'dev' - - pytest ; extra == 'dev' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/95/95/685e5a31f6c4cc24826d3a0df9e6a4bcf13d00f90d0c1f1060f13f5c16f8/python_bidi-0.6.9-cp313-cp313-macosx_11_0_arm64.whl - name: python-bidi - version: 0.6.9 - sha256: c3e4445f0fd1e2c16d3efdb28622a38f57ab4b971eb47803a334be477cee173e + - click>=8.1.0 + - docstring-parser-fork>=0.0.12 + - tomli>=2.0.1 ; python_full_version < '3.11' + - flake8>=4 ; extra == 'flake8' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl + name: aiohttp + version: 3.13.5 + sha256: e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c requires_dist: - - nox ; extra == 'dev' - - pytest ; extra == 'dev' + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/ac/a6/1ca2d290381911c17975ba00353671217bb9ed6c56f395871b274435b020/python_bidi-0.6.9-cp311-cp311-win_amd64.whl - name: python-bidi - version: 0.6.9 - sha256: be28092eef062fad59e7e0402bf2c1db797ccadedd866a53bbd7ddb97e42cac1 +- pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl + name: mkdocs-get-deps + version: 0.2.2 + sha256: e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650 requires_dist: - - nox ; extra == 'dev' - - pytest ; extra == 'dev' + - importlib-metadata>=4.3 ; python_full_version < '3.10' + - mergedeep>=1.3.4 + - platformdirs>=2.2.0 + - pyyaml>=5.1 requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 - md5: 5b8d21249ff20967101ffa321cab24e8 - depends: - - python >=3.9 - - six >=1.5 - - python - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/python-dateutil?source=hash-mapping - size: 233310 - timestamp: 1751104122689 -- pypi: https://files.pythonhosted.org/packages/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl - name: python-discovery - version: 1.3.0 - sha256: 441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f - requires_dist: - - filelock>=3.15.4 - - platformdirs>=4.3.6,<5 - - furo>=2025.12.19 ; extra == 'docs' - - sphinx-autodoc-typehints>=3.6.3 ; extra == 'docs' - - sphinx>=9.1 ; extra == 'docs' - - sphinxcontrib-mermaid>=2 ; extra == 'docs' - - covdefaults>=2.3 ; extra == 'testing' - - coverage>=7.5.4 ; extra == 'testing' - - pytest-mock>=3.14 ; extra == 'testing' - - pytest>=8.3.5 ; extra == 'testing' - - setuptools>=75.1 ; extra == 'testing' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - name: python-engineio - version: 4.13.1 - sha256: f32ad10589859c11053ad7d9bb3c9695cdf862113bfb0d20bc4d890198287399 - requires_dist: - - simple-websocket>=0.10.0 - - requests>=2.21.0 ; extra == 'client' - - websocket-client>=0.54.0 ; extra == 'client' - - aiohttp>=3.11 ; extra == 'asyncio-client' - - tox ; extra == 'dev' - - sphinx ; extra == 'docs' - - furo ; extra == 'docs' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.21.2-pyhe01879c_0.conda - sha256: df9aa74e9e28e8d1309274648aac08ec447a92512c33f61a8de0afa9ce32ebe8 - md5: 23029aae904a2ba587daba708208012f - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/fastjsonschema?source=hash-mapping - size: 244628 - timestamp: 1755304154927 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.13-h4df99d1_100.conda - sha256: b2e51d83e5ebeb7e9fde1cde822a60e8564cc9dabd786ad853056afbf708a466 - md5: fd00e4b24ea88093c93f5c9bad27b52f - depends: - - cpython 3.13.13.* - - python_abi * *_cp313 - license: Python-2.0 - purls: [] - size: 48536 - timestamp: 1775613791711 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-3.2.1-pyh332efcf_0.conda - sha256: 1c55116c22512cef7b01d55ae49697707f2c1fd829407183c19817e2d300fd8d - md5: 1cd2f3e885162ee1366312bd1b1677fd - depends: - - python >=3.10 - - typing_extensions - license: BSD-2-Clause - license_family: BSD - purls: - - 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 - sha256: a3eb1702e92aa2f2b5d3ba00261b61f062cce51f1cfb6900bf3ab4d1934d2d35 +- pypi: https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl + name: fonttools + version: 4.62.1 + sha256: 40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7 requires_dist: - - bidict>=0.21.0 - - python-engineio>=4.11.0 - - requests>=2.21.0 ; extra == 'client' - - websocket-client>=0.54.0 ; extra == 'client' - - aiohttp>=3.4 ; extra == 'asyncio-client' - - tox ; extra == 'dev' - - sphinx ; extra == 'docs' - - furo ; extra == 'docs' - requires_python: '>=3.8' -- 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: 146639 - timestamp: 1777068997932 -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda - build_number: 8 - sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 - md5: 8fcb6b0e2161850556231336dae58358 - constrains: - - python 3.11.* *_cpython - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 7003 - timestamp: 1752805919375 -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - build_number: 8 - sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 - md5: 94305520c52a4aa3f6c2b1ff6008d9f8 - constrains: - - python 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 7002 - timestamp: 1752805902938 -- pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl - name: pythreejs - version: 2.4.2 - sha256: 8418807163ad91f4df53b58c4e991b26214852a1236f28f1afeaadf99d095818 + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl + name: cloudpickle + version: 3.1.2 + sha256: 9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl + name: nodeenv + version: 1.10.0 + sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' +- pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl + name: propcache + version: 0.4.1 + sha256: cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/8a/06/2c1bd1ee9eee3e65b0b6395dcf960e71b11576995eae3b4ab9ec63d89bea/essreduce-26.4.1-py3-none-any.whl + name: essreduce + version: 26.4.1 + sha256: 1758a18fffca9c7c2a6fa9547cf87bf45f9d52fc3ccbdffcf7524f71bc060424 requires_dist: - - ipywidgets>=7.2.1 - - ipydatawidgets>=1.1.1 - - numpy - - traitlets - - sphinx>=1.5 ; extra == 'docs' - - nbsphinx>=0.2.13 ; extra == 'docs' - - nbsphinx-link ; extra == 'docs' - - sphinx-rtd-theme ; extra == 'docs' - - scipy ; extra == 'examples' - - matplotlib ; extra == 'examples' - - scikit-image ; extra == 'examples' - - ipywebrtc ; extra == 'examples' - - nbval ; extra == 'test' - - pytest-check-links ; extra == 'test' - - numpy>=1.14 ; extra == 'test' - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py311hefeebc8_1.conda - sha256: e3ef7e0cc53111ab81b8a9dd3eabc1374d7420d4c9fce3c8631e73310203ad55 - md5: c1cfe9f5d8e278cc4d2d4c7b0126634d - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/pywin32?source=hash-mapping - size: 6729388 - timestamp: 1756487145061 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-311-py313h40c08fc_1.conda - sha256: 87eaeb79b5961e0f216aa840bc35d5f0b9b123acffaecc4fda4de48891901f20 - md5: 1ce4f826332dca56c76a5b0cc89fb19e - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/pywin32?source=hash-mapping - size: 6695114 - timestamp: 1756487139550 -- conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh07e9846_2.tar.bz2 - sha256: 09803b75cccc16d8586d2f41ea890658d165f4afc359973fa1c7904a2c140eae - md5: 91733394059b880d9cc0d010c20abda0 - depends: - - python >=2.7 - - pywin32 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 5282 - timestamp: 1646866839398 -- conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb - md5: 2807a0becd1d986fe1ef9b7f8135f215 - depends: - - __unix - - python >=2.7 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 4856 - timestamp: 1646866525560 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py311hda3d55a_1.conda - sha256: b1f6b3a907e36f7af486faf3892f47fab42993c13c934cc19855bbae227f2b18 - md5: e5dd9afed138ff193d4593f1b15a388b - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - winpty - license: MIT - license_family: MIT - purls: - - pkg:pypi/pywinpty?source=hash-mapping - size: 215911 - timestamp: 1759557817579 -- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-2.0.15-py313h5813708_1.conda - sha256: d34a7cd0a4a7dc79662cb6005e01d630245d9a942e359eb4d94b2fb464ed2552 - md5: 8f01ed27e2baa455e753301218e054fd - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - - winpty - license: MIT - license_family: MIT - purls: - - pkg:pypi/pywinpty?source=hash-mapping - size: 216075 - timestamp: 1759556799508 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py311h3778330_1.conda - sha256: c9a6cd2c290d7c3d2b30ea34a0ccda30f770e8ddb2937871f2c404faf60d0050 - md5: a24add9a3bababee946f3bc1c829acfe - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 206190 - timestamp: 1770223702917 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda - sha256: ef7df29b38ef04ec67a8888a4aa039973eaa377e8c4b59a7be0a1c50cd7e4ac6 - md5: f256753e840c3cd3766488c9437a8f8b - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=compressed-mapping - size: 201616 - timestamp: 1770223543730 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py311hc290fe0_1.conda - sha256: 984e73d7957460689e10533059de8adb38a308853d298900a37acc58edd84cec - md5: e4b908da7cd496b3fa6798c0f60a2a19 - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 192948 - timestamp: 1770223655988 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_1.conda - sha256: 950725516f67c9691d81bb8dde8419581c5332c5da3da10c9ba8cbb1698b825d - md5: 5d0c8b92128c93027632ca8f8dc1190f - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 188763 - timestamp: 1770224094408 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py311h3f79411_1.conda - sha256: 301c3ba100d25cd5ae37895988ee3ab986210d4d972aa58efed948fbe857773d - md5: a0153c033dc55203e11d1cac8f6a9cf2 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 187108 - timestamp: 1770223467913 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_1.conda - sha256: dfaed50de8ee72a51096163b87631921688851001e38c78a841eba1ae8b35889 - md5: c1bdb8dd255c79fb9c428ad25cc6ee54 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - yaml >=0.2.5,<0.3.0a0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/pyyaml?source=hash-mapping - size: 180992 - timestamp: 1770223457761 -- pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - name: pyyaml-env-tag - version: '1.1' - sha256: 17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 + - sciline>=25.11.0 + - scipp>=26.3.1 + - scippneutron>=25.11.1 + - scippnexus>=25.6.0 + - graphviz>=0.20 ; extra == 'test' + - ipywidgets>=8.1 ; extra == 'test' + - matplotlib>=3.10.7 ; extra == 'test' + - numba>=0.63 ; extra == 'test' + - pooch>=1.9.0 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - scipy>=1.14 ; extra == 'test' + - tof>=25.12.0 ; extra == 'test' + - autodoc-pydantic ; extra == 'docs' + - graphviz>=0.20 ; extra == 'docs' + - ipykernel ; extra == 'docs' + - ipython!=8.7.0 ; extra == 'docs' + - ipywidgets>=8.1 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbsphinx ; extra == 'docs' + - numba>=0.63 ; extra == 'docs' + - plopp ; extra == 'docs' + - pydata-sphinx-theme>=0.14 ; extra == 'docs' + - sphinx>=7 ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-design ; extra == 'docs' + - tof>=25.12.0 ; extra == 'docs' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: matplotlib + version: 3.10.9 + sha256: 8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f requires_dist: - - pyyaml + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: greenlet + version: 3.5.0 + sha256: 2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13 + requires_dist: + - sphinx ; extra == 'docs' + - furo ; extra == 'docs' + - objgraph ; extra == 'test' + - psutil ; extra == 'test' + - setuptools ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl + name: refnx + version: 0.1.63 + sha256: 3a12accac6e469fd2c0ef49357301299f6b2e4a9ecdfdfb90b592dad59214eb9 + requires_dist: + - numpy>=2.0 + - scipy + - orsopy + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - traitlets ; extra == 'all' + - matplotlib ; extra == 'all' + - xlrd ; extra == 'all' + - h5py ; extra == 'all' + - jupyter ; extra == 'all' + - tqdm ; extra == 'all' + - pymc ; extra == 'all' + - pytensor ; extra == 'all' + - attrs ; extra == 'all' + - pandas ; extra == 'all' + - pyparsing ; extra == 'all' + - periodictable ; extra == 'all' + - pyqt6 ; extra == 'all' + - qtpy ; extra == 'all' + - corner ; extra == 'all' + - numba ; extra == 'all' + - pytest ; extra == 'test' + - uncertainties ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl + name: lazy-loader + version: '0.5' + sha256: ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005 + requires_dist: + - packaging + - pytest>=8.0 ; extra == 'test' + - pytest-cov>=5.0 ; extra == 'test' + - coverage[toml]>=7.2 ; extra == 'test' + - pre-commit==4.3.0 ; extra == 'lint' + - changelist==0.5 ; extra == 'dev' + - spin==0.15 ; extra == 'dev' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py311h57d2397_2.conda - sha256: 4137226663b353919396f8cf239971cfa54107cd077cf3b65e979ba3e1f8a037 - md5: 759edfe34f07c5c4565b6c5ec0c7fb17 - depends: - - python - - libgcc >=14 - - libstdcxx >=14 - - __glibc >=2.17,<3.0.a0 - - zeromq >=4.3.5,<4.4.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 383627 - timestamp: 1771716979818 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.1.0-py312hda471dd_2.conda - noarch: python - sha256: be66c1f85c3b48137200d62c12d918f4f8ad329423daef04fed292818efd3c28 - md5: 082985717303dab433c976986c674b35 - depends: - - python - - libgcc >=14 - - libstdcxx >=14 - - __glibc >=2.17,<3.0.a0 - - zeromq >=4.3.5,<4.4.0a0 - - _python_abi3_support 1.* - - cpython >=3.12 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 211567 - timestamp: 1771716961404 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py311h745ac33_2.conda - sha256: c5d65e1192241d764a68eb0e95ef3ec4800a7e8584260aa2bdcfad0a2d769609 - md5: 9648524ed194922a084f0c2c2c2ada6a - depends: - - python - - __osx >=11.0 - - python 3.11.* *_cpython - - libcxx >=19 - - zeromq >=4.3.5,<4.4.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 359869 - timestamp: 1771717160649 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.1.0-py312h022ad19_2.conda - noarch: python - sha256: 2f31f799a46ed75518fae0be75ecc8a1b84360dbfd55096bc2fe8bd9c797e772 - md5: 2f6b79700452ef1e91f45a99ab8ffe5a - depends: - - python - - libcxx >=19 - - __osx >=11.0 - - _python_abi3_support 1.* - - cpython >=3.12 - - zeromq >=4.3.5,<4.4.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 191641 - timestamp: 1771717073430 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py311hff25285_2.conda - sha256: d37e831618e8df0506d32e16820adab550b74b36d039da753f7543bb967b700d - md5: 0ad824e928af5d6200a97649d810ab2b - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - - zeromq >=4.3.5,<4.3.6.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 363314 - timestamp: 1771716977369 -- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.1.0-py312h343a6d4_2.conda - noarch: python - sha256: d84bcc19a945ca03d1fd794be3e9896ab6afc9f691d58d9c2da514abe584d4df - md5: eb1ec67a70b4d479f7dd76e6c8fe7575 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - zeromq >=4.3.5,<4.3.6.0a0 - - _python_abi3_support 1.* - - cpython >=3.12 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pyzmq?source=hash-mapping - size: 183235 - timestamp: 1771716967192 -- conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda - sha256: 0604c6dff3af5f53e34fceb985395d08287137f220450108a795bcd1959caf14 - md5: 34fa231b5c5927684b03bb296bb94ddc - depends: - - prompt_toolkit >=2.0,<4.0 - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/questionary?source=hash-mapping - size: 31257 - timestamp: 1757356458097 -- pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - name: radon - version: 6.0.1 - sha256: 632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859 +- pypi: https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl + name: numpy + version: 2.4.4 + sha256: 86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl + name: traittypes + version: 0.2.3 + sha256: 49016082ce740d6556d9bb4672ee2d899cd14f9365f17cbb79d5d96b47096d4e requires_dist: - - mando>=0.6,<0.8 - - colorama==0.4.1 ; python_full_version < '3.5' - - colorama>=0.4.1 ; python_full_version >= '3.5' - - tomli>=2.0.1 ; extra == 'toml' -- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 - md5: d7d95fc8287ea7bf33e0e7116d2b95ec - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - ncurses >=6.5,<7.0a0 - license: GPL-3.0-only - license_family: GPL - purls: [] - size: 345073 - timestamp: 1765813471974 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - sha256: a77010528efb4b548ac2a4484eaf7e1c3907f2aec86123ed9c5212ae44502477 - md5: f8381319127120ce51e081dce4865cf4 - depends: - - __osx >=11.0 - - ncurses >=6.5,<7.0a0 - license: GPL-3.0-only - license_family: GPL - purls: [] - size: 313930 - timestamp: 1765813902568 -- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda - sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 - md5: 870293df500ca7e18bedefa5838a22ab - depends: - - attrs >=22.2.0 - - python >=3.10 - - rpds-py >=0.7.0 - - typing_extensions >=4.4.0 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/referencing?source=hash-mapping - size: 51788 - timestamp: 1760379115194 -- pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - name: refl1d - version: 1.0.1 - sha256: 8cc52121790df465e3a659d0fb0298f2501256c6f2d7fca26085927297112eb1 + - traitlets>=4.2.2 + - numpy ; extra == 'test' + - pandas ; extra == 'test' + - xarray ; extra == 'test' + - pytest ; extra == 'test' +- pypi: https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl + name: lxml + version: 6.1.0 + sha256: 4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16 + requires_dist: + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl + name: uncertainties + version: 3.2.3 + sha256: 313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a + requires_dist: + - numpy ; extra == 'arrays' + - pytest ; extra == 'test' + - pytest-codspeed ; extra == 'test' + - pytest-cov ; extra == 'test' + - scipy ; extra == 'test' + - sphinx ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - python-docs-theme ; extra == 'doc' + - uncertainties[arrays,doc,test] ; extra == 'all' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl + name: paginate + version: 0.5.7 + sha256: b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591 requires_dist: - - bumps==1.0.4 - - matplotlib - - numba - - numpy - - periodictable - - scipy - - orsopy - - ipython ; extra == 'dev' - - matplotlib ; extra == 'dev' - - nbsphinx ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pydantic ; extra == 'dev' - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - ruff ; extra == 'dev' - - sphinx<8.2 ; extra == 'dev' - - versioningit ; extra == 'dev' - - wxpython ; extra == 'full' - - ipython ; extra == 'full' - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/8a/46/7e2d150dd574039c2f8d09c4e102bb90a6cb6634be4040fc29f212fc42bf/refnx-0.1.63-cp311-abi3-win_amd64.whl + - tox ; extra == 'dev' + - black ; extra == 'lint' +- pypi: https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl + name: plotly + version: 6.7.0 + sha256: ac8aca1c25c663a59b5b9140a549264a5badde2e057d79b8c772ae2920e32ff0 + requires_dist: + - narwhals>=1.15.1 + - packaging + - anywidget ; extra == 'dev' + - build ; extra == 'dev' + - colorcet ; extra == 'dev' + - fiona<=1.9.6 ; python_full_version < '3.9' and extra == 'dev' + - geopandas ; extra == 'dev' + - inflect ; extra == 'dev' + - jupyterlab ; extra == 'dev' + - kaleido>=1.1.0 ; extra == 'dev' + - numpy>=1.22 ; extra == 'dev' + - orjson ; extra == 'dev' + - pandas ; extra == 'dev' + - pdfrw ; extra == 'dev' + - pillow ; extra == 'dev' + - plotly-geo ; extra == 'dev' + - polars[timezone] ; extra == 'dev' + - pyarrow ; extra == 'dev' + - pyshp ; extra == 'dev' + - pytest ; extra == 'dev' + - pytz ; extra == 'dev' + - requests ; extra == 'dev' + - ruff==0.11.12 ; extra == 'dev' + - scikit-image ; extra == 'dev' + - scipy ; extra == 'dev' + - shapely ; extra == 'dev' + - statsmodels ; extra == 'dev' + - vaex ; python_full_version < '3.10' and extra == 'dev' + - xarray ; extra == 'dev' + - build ; extra == 'dev-build' + - jupyterlab ; extra == 'dev-build' + - pytest ; extra == 'dev-build' + - requests ; extra == 'dev-build' + - ruff==0.11.12 ; extra == 'dev-build' + - pytest ; extra == 'dev-core' + - requests ; extra == 'dev-core' + - ruff==0.11.12 ; extra == 'dev-core' + - anywidget ; extra == 'dev-optional' + - build ; 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' + - jupyterlab ; extra == 'dev-optional' + - kaleido>=1.1.0 ; extra == 'dev-optional' + - numpy>=1.22 ; 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' + - pytest ; extra == 'dev-optional' + - pytz ; extra == 'dev-optional' + - requests ; extra == 'dev-optional' + - ruff==0.11.12 ; 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' + - numpy>=1.22 ; extra == 'express' + - kaleido>=1.1.0 ; extra == 'kaleido' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl name: refnx version: 0.1.63 - sha256: 3a12accac6e469fd2c0ef49357301299f6b2e4a9ecdfdfb90b592dad59214eb9 + sha256: 9bc88eba956e97892805d2fb18a7268780f943c5d74feb5d1c58f5ae90699fa2 requires_dist: - numpy>=2.0 - scipy @@ -11370,1062 +11439,1200 @@ packages: - pytest ; extra == 'test' - uncertainties ; extra == 'test' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/90/df/cbd75ba033d712a7bbe8efff2a764c15a3904003f78ed3c19f259c91a4a4/refnx-0.1.63-cp311-abi3-macosx_11_0_arm64.whl - name: refnx - version: 0.1.63 - sha256: 9bc88eba956e97892805d2fb18a7268780f943c5d74feb5d1c58f5ae90699fa2 +- pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl + name: graphviz + version: '0.21' + sha256: 54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42 + requires_dist: + - build ; extra == 'dev' + - wheel ; extra == 'dev' + - twine ; extra == 'dev' + - flake8 ; extra == 'dev' + - flake8-pyproject ; extra == 'dev' + - pep8-naming ; extra == 'dev' + - tox>=3 ; extra == 'dev' + - pytest>=7,<8.1 ; extra == 'test' + - pytest-mock>=3 ; extra == 'test' + - pytest-cov ; extra == 'test' + - coverage ; extra == 'test' + - sphinx>=5,<7 ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-rtd-theme>=0.2.5 ; extra == 'docs' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl + name: typeguard + version: 4.5.1 + sha256: 44d2bf329d49a244110a090b55f5f91aa82d9a9834ebfd30bcc73651e4a8cc40 + requires_dist: + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - typing-extensions>=4.14.0 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.13.5 + sha256: ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl + name: msgpack + version: 1.1.2 + sha256: 42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl + name: yarl + version: 1.23.0 + sha256: bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46 + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl + name: freetype-py + version: 2.5.1 + sha256: 0b7f8e0342779f65ca13ef8bc103938366fecade23e6bb37cb671c2b8ad7f124 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl + name: xhtml2pdf + version: 0.2.17 + sha256: 61a7ecac829fed518f7dbcb916e9d56bea6e521e02e54644b3d0ca33f0658315 + requires_dist: + - arabic-reshaper>=3.0.0 + - html5lib>=1.1 + - pillow>=8.1.1 + - pyhanko>=0.12.1 + - pyhanko-certvalidator>=0.19.5 + - pypdf>=3.1.0 + - python-bidi>=0.5.0 + - reportlab>=4.0.4,<5 + - svglib>=1.2.1 + - reportlab[pycairo]>=4.0.4,<5 ; extra == 'pycairo' + - reportlab[renderpm]>=4.0.4,<5 ; extra == 'renderpm' + - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'test' + - tox ; extra == 'test' + - coverage>=5.3 ; extra == 'test' + - sphinx>=6 ; extra == 'docs' + - sphinx-rtd-theme>=0.5.0 ; extra == 'docs' + - sphinx-reredirects>=0.1.3 ; extra == 'docs' + - build ; extra == 'release' + - twine ; extra == 'release' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl + name: radon + version: 6.0.1 + sha256: 632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859 + requires_dist: + - mando>=0.6,<0.8 + - colorama==0.4.1 ; python_full_version < '3.5' + - colorama>=0.4.1 ; python_full_version >= '3.5' + - tomli>=2.0.1 ; extra == 'toml' +- pypi: https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl + name: identify + version: 2.6.19 + sha256: 20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a + requires_dist: + - ukkonen ; extra == 'license' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/95/95/685e5a31f6c4cc24826d3a0df9e6a4bcf13d00f90d0c1f1060f13f5c16f8/python_bidi-0.6.9-cp313-cp313-macosx_11_0_arm64.whl + name: python-bidi + version: 0.6.9 + sha256: c3e4445f0fd1e2c16d3efdb28622a38f57ab4b971eb47803a334be477cee173e + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl + name: scipy + version: 1.17.1 + sha256: d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff + requires_dist: + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/96/5d/0c59079aa7ef34980a5925a06a90ad2b7c94e486c194b3527d557cabb042/cryspy-0.11.0-py3-none-any.whl + name: cryspy + version: 0.11.0 + sha256: 0b650655a0fbdc3cfcb28826c2ab9fbc5f491e32e1ea9a47d9b75976cd43f26f requires_dist: - - numpy>=2.0 + - numpy - scipy - - orsopy - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - traitlets ; extra == 'all' - - matplotlib ; extra == 'all' - - xlrd ; extra == 'all' - - h5py ; extra == 'all' - - jupyter ; extra == 'all' - - tqdm ; extra == 'all' - - pymc ; extra == 'all' - - pytensor ; extra == 'all' - - attrs ; extra == 'all' - - pandas ; extra == 'all' - - pyparsing ; extra == 'all' - - periodictable ; extra == 'all' - - pyqt6 ; extra == 'all' - - qtpy ; extra == 'all' - - corner ; extra == 'all' - - numba ; extra == 'all' - - pytest ; extra == 'test' - - uncertainties ; extra == 'test' + - pycifstar + - matplotlib +- pypi: https://files.pythonhosted.org/packages/96/b3/650500c2eab4534d98e9166f4298e0f3c69c742afdf24e6eabccd1f16ad8/numba-0.65.1-cp311-cp311-macosx_12_0_arm64.whl + name: numba + version: 0.65.1 + sha256: 7020d74b19cdb8cff16506542fdd510756e28c5e7f3bd0b7f574f0f42272fcd9 + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl + name: contourpy + version: 1.3.3 + sha256: d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1 + requires_dist: + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - name: refnx - version: 0.1.63 - sha256: 4f397b0d6e18ac1bb793372ec92c9f4a122383387a706dea456df5a2c90dff5e +- pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl + name: contourpy + version: 1.3.3 + sha256: 3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42 requires_dist: - - numpy>=2.0 - - scipy - - orsopy - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - traitlets ; extra == 'all' - - matplotlib ; extra == 'all' - - xlrd ; extra == 'all' - - h5py ; extra == 'all' - - jupyter ; extra == 'all' - - tqdm ; extra == 'all' - - pymc ; extra == 'all' - - pytensor ; extra == 'all' - - attrs ; extra == 'all' - - pandas ; extra == 'all' - - pyparsing ; extra == 'all' - - periodictable ; extra == 'all' - - pyqt6 ; extra == 'all' - - qtpy ; extra == 'all' - - corner ; extra == 'all' - - numba ; extra == 'all' - - pytest ; extra == 'test' - - uncertainties ; extra == 'test' + - numpy>=1.25 + - furo ; extra == 'docs' + - sphinx>=7.2 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - name: reportlab - version: 4.5.0 - sha256: b8cc8996947d84e805368b47b2376070966f091d029351a0d8a1f238984c2c7f +- pypi: https://files.pythonhosted.org/packages/99/31/6cf181011dc738c33bf6ba7aea2e8e1d3c1f71b7dab1942f3054f66f6202/asteval-1.0.8-py3-none-any.whl + name: asteval + version: 1.0.8 + sha256: 6c64385c6ff859a474953c124987c7ee8354d781c76509b2c598741c4d1d28e9 requires_dist: - - pillow>=9.0.0 - - charset-normalizer - - rl-accel>=0.9.0,<1.1 ; extra == 'accel' - - rl-renderpm>=4.0.3,<4.1 ; extra == 'renderpm' - - rlpycairo>=0.2.0,<1 ; extra == 'pycairo' - - freetype-py>=2.3.0,<2.4 ; extra == 'pycairo' - - rlbidi ; extra == 'bidi' - - uharfbuzz ; extra == 'shaping' - requires_python: '>=3.9,<4' -- 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 - - charset-normalizer >=2,<4 - - idna >=2.5,<4 - - urllib3 >=1.26,<3 - - python - constrains: - - chardet >=3.0.2,<8 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/requests?source=compressed-mapping - 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.11 - - typing_extensions >=4.0,<5.0 - - python - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/returns?source=hash-mapping - size: 100559 - timestamp: 1776176903101 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda - sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 - md5: 36de09a8d3e5d5e6f4ee63af49e59706 - depends: - - python >=3.9 - - six - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3339-validator?source=hash-mapping - size: 10209 - timestamp: 1733600040800 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 - sha256: 2a5b495a1de0f60f24d8a74578ebc23b24aa53279b1ad583755f223097c41c37 - md5: 912a71cc01012ee38e6b90ddd561e36f - depends: - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3986-validator?source=hash-mapping - size: 7818 - timestamp: 1598024297745 -- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda - sha256: 70001ac24ee62058557783d9c5a7bbcfd97bd4911ef5440e3f7a576f9e43bc92 - md5: 7234f99325263a5af6d4cd195035e8f2 - depends: - - python >=3.9 - - lark >=1.2.2 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/rfc3987-syntax?source=hash-mapping - size: 22913 - timestamp: 1752876729969 -- pypi: https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl - name: rich - version: 15.0.0 - sha256: 33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb + - build ; extra == 'dev' + - twine ; extra == 'dev' + - sphinx ; extra == 'doc' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - coverage ; extra == 'test' + - asteval[dev,doc,test] ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl + name: bidict + version: 0.23.1 + sha256: 5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl + name: tabulate + version: 0.10.0 + sha256: f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3 + requires_dist: + - wcwidth ; extra == 'widechars' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: yarl + version: 1.23.0 + sha256: 99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598 + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl + name: aiohttp + version: 3.13.5 + sha256: a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl + name: pytest-cov + version: 7.1.0 + sha256: a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 requires_dist: - - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' - - markdown-it-py>=2.2.0 - - pygments>=2.13.0,<3.0.0 - requires_python: '>=3.9.0' -- pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - name: rlpycairo - version: 0.4.0 - sha256: 3ce83825d5761c03bc3571c7db12a336ad51417e63189e3512d11b8922576aa9 + - coverage[toml]>=7.10.6 + - pluggy>=1.2 + - pytest>=7 + - process-tests ; extra == 'testing' + - pytest-xdist ; extra == 'testing' + - virtualenv ; extra == 'testing' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl + name: autopep8 + version: 2.3.2 + sha256: ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128 requires_dist: - - pycairo>=1.20.0 - - freetype-py>=2.3 + - pycodestyle>=2.12.0 + - tomli ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl + name: networkx + version: 3.6.1 + sha256: d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762 + requires_dist: + - asv ; extra == 'benchmarking' + - virtualenv ; extra == 'benchmarking' + - numpy>=1.25 ; extra == 'default' + - scipy>=1.11.2 ; extra == 'default' + - matplotlib>=3.8 ; extra == 'default' + - pandas>=2.0 ; extra == 'default' + - pre-commit>=4.1 ; extra == 'developer' + - mypy>=1.15 ; extra == 'developer' + - sphinx>=8.0 ; extra == 'doc' + - pydata-sphinx-theme>=0.16 ; extra == 'doc' + - sphinx-gallery>=0.18 ; extra == 'doc' + - numpydoc>=1.8.0 ; extra == 'doc' + - pillow>=10 ; extra == 'doc' + - texext>=0.6.7 ; extra == 'doc' + - myst-nb>=1.1 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - osmnx>=2.0.0 ; extra == 'example' + - momepy>=0.7.2 ; extra == 'example' + - contextily>=1.6 ; extra == 'example' + - seaborn>=0.13 ; extra == 'example' + - cairocffi>=1.7 ; extra == 'example' + - igraph>=0.11 ; extra == 'example' + - scikit-learn>=1.5 ; extra == 'example' + - iplotx>=0.9.0 ; extra == 'example' + - lxml>=4.6 ; extra == 'extra' + - pygraphviz>=1.14 ; extra == 'extra' + - pydot>=3.0.1 ; extra == 'extra' + - sympy>=1.10 ; extra == 'extra' + - build>=0.10 ; extra == 'release' + - twine>=4.0 ; extra == 'release' + - wheel>=0.40 ; extra == 'release' + - changelist==0.5 ; extra == 'release' + - pytest>=7.2 ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pytest-mpl ; extra == 'test-extras' + - pytest-randomly ; extra == 'test-extras' + requires_python: '>=3.11,!=3.14.1' +- pypi: https://files.pythonhosted.org/packages/9f/9b/50835e8fd86073fa7aa921df61b4cebc1f0ff400e4338541675cb72b5507/pycifrw-5.0.1-cp313-cp313-win_amd64.whl + name: pycifrw + version: 5.0.1 + sha256: 8632df76b99932408ab432e09b30aa9a6c390df884a5f34f1e1f76201e625156 + requires_dist: + - prettytable + - ply + - numpy +- pypi: https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: numba + version: 0.65.1 + sha256: c09f49117ef255e1f1c6dad0c7a1ed39868243862a73be5706793241a3755f1b + requires_dist: + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl + name: gitdb + version: 4.0.12 + sha256: 67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf + requires_dist: + - smmap>=3.0.1,<6 requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py311h902ca64_0.conda - sha256: bf5e6197fb08b8c6e421ca0126e966b7c3ae62b84d7b98523356b4fd5ae6f8ae - md5: 3893f7b40738f9fe87510cb4468cdda5 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.11.* *_cp311 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 383153 - timestamp: 1764543197251 -- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py313h843e2db_0.conda - sha256: 076d26e51c62c8ecfca6eb19e3c1febdd7632df1990a7aa53da5df5e54482b1c - md5: 779e3307a0299518713765b83a36f4b1 - depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.13.* *_cp313 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 383230 - timestamp: 1764543223529 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py311h71babbd_0.conda - sha256: 15873755f078583cea046f7ca7fc0d5348d1f29a16a30b73bdb53dd62f2ba379 - md5: 4408829b022e8e0d19365c0c00be00c4 - depends: - - python - - python 3.11.* *_cpython - - __osx >=11.0 - - python_abi 3.11.* *_cp311 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 357600 - timestamp: 1764543142990 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.30.0-py313h2c089d5_0.conda - sha256: db63344f91e8bfe77703c6764aa9eeafb44d165e286053214722814eabda0264 - md5: 190c2d0d4e98ec97df48cdb74caf44d8 - depends: - - python - - __osx >=11.0 - - python 3.13.* *_cp313 - - python_abi 3.13.* *_cp313 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 358961 - timestamp: 1764543165314 -- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py311hf51aa87_0.conda - sha256: 6edeab1412def450e72f0e96a5d8bb31a2a0b4e56624699c916d3bafd4d9b475 - md5: 43ab63451a9df29f2c499da524665de9 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 241288 - timestamp: 1764543026991 -- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-0.30.0-py313hfbe8231_0.conda - sha256: 27bd383787c0df7a0a926b11014fd692d60d557398dcf1d50c55aa2378507114 - md5: 58ae648b12cfa6df3923b5fd219931cb - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.13.* *_cp313 - license: MIT - license_family: MIT - purls: - - pkg:pypi/rpds-py?source=hash-mapping - size: 243419 - timestamp: 1764543047271 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.12-h994f30f_0.conda - noarch: python - sha256: 98c771464ed93a2733bae460c39cfa9640feb20972d2e651b44e85db296942eb - md5: 3c8f229055ad244fa3a97c6c45b77099 - depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - constrains: - - __glibc >=2.17 - license: MIT - license_family: MIT - purls: - - pkg:pypi/ruff?source=compressed-mapping - size: 9266480 - timestamp: 1778119386343 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.15.12-hbd3f8a3_0.conda - noarch: python - sha256: 91992a557456cbe9980e8ffb0f0d1200f65f741adeefc7ecf568d1935690a7bc - md5: 7151a0851207b88c0031f388a7825e9b - depends: - - python - - __osx >=11.0 - constrains: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/ruff?source=compressed-mapping - size: 8485007 - timestamp: 1778119519471 -- conda: https://conda.anaconda.org/conda-forge/win-64/ruff-0.15.12-hd7ccaa8_0.conda - noarch: python - sha256: 9b47c39449fd0102315bb62bd632707a754d32cf6c6f08d2a9c74b0a476f2282 - md5: 2db0d3419f3e6fc05516da561f81cda5 - depends: - - python - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/ruff?source=compressed-mapping - size: 9719930 - timestamp: 1778119435755 -- pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl - name: sciline - version: 25.11.1 - sha256: 13c378287b8157e819b9b67d7e973c65bc6bdc545a3602d18204c365b0c336f9 +- pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl + name: sympy + version: 1.14.0 + sha256: e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5 + requires_dist: + - mpmath>=1.1.0,<1.4 + - pytest>=7.1.0 ; extra == 'dev' + - hypothesis>=6.70.0 ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl + name: llvmlite + version: 0.47.0 + sha256: c37d6eb7aaabfa83ab9c2ff5b5cdb95a5e6830403937b2c588b7490724e05327 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl + name: ply + version: '3.11' + sha256: 096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce +- pypi: https://files.pythonhosted.org/packages/a3/8c/db8e79c4c744ebae1dcf25f7dbcc5d7df912cdbcdf7221e761479e8bd04b/gemmi-0.7.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: gemmi + version: 0.7.5 + sha256: 750b4d9751aaf1460ac4f0f45308ddced25f47bcf7a30355eb3b1f779f03952a + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl + name: varname + version: 1.0.0 + sha256: 1125bfe981c3bbbe56988f5cb85fdcd7cad923b153283c2d464aea8b4c833d51 requires_dist: - - cyclebane>=24.6.0 - - pytest ; extra == 'test' - - pytest-randomly>=3 ; extra == 'test' - - dask ; extra == 'test' - - graphviz ; extra == 'test' - - jsonschema ; extra == 'test' - - numpy ; extra == 'test' - - pandas ; extra == 'test' - - pydantic ; extra == 'test' - - rich ; extra == 'test' - - rich ; extra == 'progress' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/2e/75/5604f4d17ab607510d4702f156329194d8edfff7e29644ca9200b085e9a2/scipp-26.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: scipp - version: 26.3.1 - sha256: 993706e7c31f0317be2db5f528f9142ba67b2e52d7af174fcad195f702e1d6c7 + - executing>=2.1 + - typing-extensions>=4.13 ; python_full_version < '3.10' + - asttokens==3.* ; extra == 'all' + - pure-eval==0.* ; extra == 'all' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl + name: verspec + version: 0.1.0 + sha256: 741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31 requires_dist: - - numpy>=2 + - coverage ; extra == 'test' + - flake8>=3.7 ; extra == 'test' + - mypy ; extra == 'test' + - pretend ; extra == 'test' - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/37/fd/22621d3ee9e3ee87ef4c89b63bba55b265ab85039b3c1ba88ed2380a24c1/scipp-26.3.1-cp313-cp313-win_amd64.whl - name: scipp - version: 26.3.1 - sha256: 03c6dbf8936a2ed62587c5abe8ab5266a5098834a0709321ce799bd1328eb3e6 +- pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl + name: wsproto + version: 1.3.2 + sha256: 61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584 requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/60/54/5011adb56853caabfd90686c2e543d1e3c76a8ef2755809b7e12e3f3583b/scipp-26.3.1-cp311-cp311-macosx_14_0_arm64.whl - name: scipp - version: 26.3.1 - sha256: 67d275fc83b062053df9aa7ce3af4d2205109c2bc3ab22467bcd73ceb0a83df2 + - h11>=0.16.0,<1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl + name: kiwisolver + version: 1.5.0 + sha256: dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl + name: mkdocs-markdownextradata-plugin + version: 0.2.6 + sha256: 34dd40870781784c75809596b2d8d879da783815b075336d541de1f150c94242 requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/79/fe/b14d806894cf05178f1e77d0d619f071db50cf698bc654c54f9241223bcf/scipp-26.3.1-cp313-cp313-macosx_14_0_arm64.whl - name: scipp - version: 26.3.1 - sha256: 8dfe8adedb5cba05acaaea15e3b6fe1820ac2f497e87c1e581ba4be9d82c53bb + - mkdocs + - pyyaml + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/a8/88/802c82060c54bc7dde21eb0033e337838b8181a1323254aa9ec41cbfc3d1/markdown_it_py-4.1.0-py3-none-any.whl + name: markdown-it-py + version: 4.1.0 + sha256: d4939a62a2dd0cd9cb80a191a711ba1d39bac8ed5ef9e9966895b0171c01c46d + requires_dist: + - mdurl~=0.1 + - psutil ; extra == 'benchmarking' + - pytest ; extra == 'benchmarking' + - pytest-benchmark ; extra == 'benchmarking' + - commonmark~=0.9 ; extra == 'compare' + - markdown~=3.4 ; extra == 'compare' + - mistletoe~=1.0 ; extra == 'compare' + - mistune~=3.0 ; extra == 'compare' + - panflute~=2.3 ; extra == 'compare' + - markdown-it-pyrs ; extra == 'compare' + - linkify-it-py>=1,<3 ; extra == 'linkify' + - mdit-py-plugins>=0.5.0 ; extra == 'plugins' + - gprof2dot ; extra == 'profiling' + - mdit-py-plugins>=0.5.0 ; extra == 'rtd' + - myst-parser ; extra == 'rtd' + - pyyaml ; extra == 'rtd' + - sphinx ; extra == 'rtd' + - sphinx-copybutton ; extra == 'rtd' + - sphinx-design ; extra == 'rtd' + - sphinx-book-theme~=1.0 ; extra == 'rtd' + - jupyter-sphinx ; extra == 'rtd' + - ipykernel ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + - pytest-timeout ; extra == 'testing' + - requests ; extra == 'testing' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a8/b1/3338e121cbd4c8a126b8ccb1061170c2ce51a53f678c502793ea49c6fd6d/chardet-7.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: chardet + version: 7.4.3 + sha256: bfc134b70c846c21ead8e43ada3ae1a805fff732f6922f8abcf2ff27b8f6493d + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl + name: python-engineio + version: 4.13.1 + sha256: f32ad10589859c11053ad7d9bb3c9695cdf862113bfb0d20bc4d890198287399 + requires_dist: + - simple-websocket>=0.10.0 + - requests>=2.21.0 ; extra == 'client' + - websocket-client>=0.54.0 ; extra == 'client' + - aiohttp>=3.11 ; extra == 'asyncio-client' + - tox ; extra == 'dev' + - sphinx ; extra == 'docs' + - furo ; extra == 'docs' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl + name: execnet + version: 2.1.2 + sha256: 67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec + requires_dist: + - hatch ; extra == 'testing' + - pre-commit ; extra == 'testing' + - pytest ; extra == 'testing' + - tox ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl + name: jupyterlab-widgets + version: 3.0.16 + sha256: 45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: coverage + version: 7.13.5 + sha256: 78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3 + requires_dist: + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ac/a6/1ca2d290381911c17975ba00353671217bb9ed6c56f395871b274435b020/python_bidi-0.6.9-cp311-cp311-win_amd64.whl + name: python-bidi + version: 0.6.9 + sha256: be28092eef062fad59e7e0402bf2c1db797ccadedd866a53bbd7ddb97e42cac1 + requires_dist: + - nox ; extra == 'dev' + - pytest ; extra == 'dev' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl + name: click + version: 8.3.3 + sha256: a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613 + requires_dist: + - colorama ; sys_platform == 'win32' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl + name: yarl + version: 1.23.0 + sha256: 7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl + name: sqlalchemy + version: 2.0.49 + sha256: df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120 + requires_dist: + - importlib-metadata ; python_full_version < '3.8' + - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' + - typing-extensions>=4.6.0 + - greenlet>=1 ; extra == 'asyncio' + - mypy>=0.910 ; extra == 'mypy' + - pyodbc ; extra == 'mssql' + - pymssql ; extra == 'mssql-pymssql' + - pyodbc ; extra == 'mssql-pyodbc' + - mysqlclient>=1.4.0 ; extra == 'mysql' + - mysql-connector-python ; extra == 'mysql-connector' + - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector' + - cx-oracle>=8 ; extra == 'oracle' + - oracledb>=1.0.1 ; extra == 'oracle-oracledb' + - psycopg2>=2.7 ; extra == 'postgresql' + - pg8000>=1.29.1 ; extra == 'postgresql-pg8000' + - greenlet>=1 ; extra == 'postgresql-asyncpg' + - asyncpg ; extra == 'postgresql-asyncpg' + - psycopg2-binary ; extra == 'postgresql-psycopg2binary' + - psycopg2cffi ; extra == 'postgresql-psycopg2cffi' + - psycopg>=3.0.7 ; extra == 'postgresql-psycopg' + - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary' + - pymysql ; extra == 'pymysql' + - greenlet>=1 ; extra == 'aiomysql' + - aiomysql>=0.2.0 ; extra == 'aiomysql' + - greenlet>=1 ; extra == 'aioodbc' + - aioodbc ; extra == 'aioodbc' + - greenlet>=1 ; extra == 'asyncmy' + - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy' + - greenlet>=1 ; extra == 'aiosqlite' + - aiosqlite ; extra == 'aiosqlite' + - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' + - sqlcipher3-binary ; extra == 'sqlcipher' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/af/46/661159ad844034ba8b3f4e0516215c41e4ee17db4213d13a82227670764f/sciline-25.11.1-py3-none-any.whl + name: sciline + version: 25.11.1 + sha256: 13c378287b8157e819b9b67d7e973c65bc6bdc545a3602d18204c365b0c336f9 requires_dist: - - numpy>=2 + - cyclebane>=24.6.0 - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' + - pytest-randomly>=3 ; extra == 'test' + - dask ; extra == 'test' + - graphviz ; extra == 'test' + - jsonschema ; extra == 'test' + - numpy ; extra == 'test' + - pandas ; extra == 'test' + - pydantic ; extra == 'test' + - rich ; extra == 'test' + - rich ; extra == 'progress' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: scipp - version: 26.3.1 - sha256: ab5859a24b3150b588dd2c67e68b0c7f07c9444eae501f3b6326d6b4a34cbf10 +- pypi: https://files.pythonhosted.org/packages/af/78/bc3850efda66712370ebb2a52130111b5cbbdee7a5b9d1672aadfc73234f/orsopy-1.2.3-py2.py3-none-any.whl + name: orsopy + version: 1.2.3 + sha256: 11d3ee9a1185467ba4363b24d6b98b268956ee0fb528aeff7ff106b49f363180 requires_dist: - - numpy>=2 - - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl - name: scipp - version: 26.3.1 - sha256: 37877cf07b4f54f224d5465c265d6a1e591d605d0c23dd350a4b48d95c26ab7b + - pyyaml>=5.4.1 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl + name: coverage + version: 7.13.5 + sha256: 258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9 requires_dist: - - numpy>=2 + - tomli ; python_full_version <= '3.11' and extra == 'toml' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + name: h5py + version: 3.16.0 + sha256: 42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: multidict + version: 6.7.1 + sha256: 9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b1/10/31932775c94a86814f76b41c4a772b52abfb0e6125324f32c6da1196c297/chardet-7.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: chardet + version: 7.4.3 + sha256: 4c3da294de1a681097848ab58bd3f2771a674f8039d2d87a5538b28856b815e9 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl + name: virtualenv + 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.2.2 + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-py3-none-any.whl + name: h5netcdf + version: 1.8.1 + sha256: a76ed7cfc9b8a8908ea7057c4e57e27307acff1049b7f5ed52db6c2247636879 + requires_dist: + - packaging + - numpy + - h5py ; extra == 'h5py' + - pyfive>=1.0.0 ; extra == 'pyfive' + - h5pyd ; extra == 'h5pyd' + - h5py ; extra == 'test' + - netcdf4 ; extra == 'test' + - pyfive>=1.0.0 ; extra == 'test' - pytest ; extra == 'test' - - matplotlib ; extra == 'test' - - beautifulsoup4 ; extra == 'test' - - ipython ; extra == 'test' - - h5py ; extra == 'extra' - - scipy>=1.7.0 ; extra == 'extra' - - graphviz ; extra == 'extra' - - pooch ; extra == 'extra' - - plopp ; extra == 'extra' - - matplotlib ; extra == 'extra' - - scipp[extra] ; extra == 'all' - - ipympl ; extra == 'all' - - ipython ; extra == 'all' - - ipywidgets ; extra == 'all' - - jupyterlab ; extra == 'all' - - jupyterlab-widgets ; extra == 'all' - - jupyter-nbextensions-configurator ; extra == 'all' - - nodejs ; extra == 'all' - - pythreejs ; extra == 'all' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/82/9b/cf7e6f157c53d2c8bc0165580350c53939aea3b2a8390ec8e07f0adb82de/scippneutron-26.4.1-py3-none-any.whl - name: scippneutron - version: 26.4.1 - sha256: 3b9865ebdb7923eb25739b7cda624555d45275341000f099c1dcf371b1dd7e35 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl + name: yarl + version: 1.23.0 + sha256: dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432 requires_dist: - - python-dateutil>=2.8 - - email-validator>=2 - - h5py>=3.12 - - lazy-loader>=0.4 - - mpltoolbox>=24.6.0 - - numpy>=1.20 - - plopp>=26.4.1 - - pydantic>=2 - - scipp>=24.7.0 - - scippnexus>=23.11.0 - - scipy>=1.7.0 - - scipp[all]>=23.7.0 ; extra == 'all' - - pooch>=1.5 ; extra == 'all' - - hypothesis>=6.100 ; extra == 'test' - - ipympl>0.9.0 ; extra == 'test' - - ipykernel>6.30 ; extra == 'test' - - pace-neutrons>=0.3 ; extra == 'test' - - pooch>=1.5 ; extra == 'test' - - psutil>=5.0 ; extra == 'test' - - pytest>=7.0 ; extra == 'test' - - pytest-xdist>=3.0 ; extra == 'test' - - pythreejs>=2.4.1 ; extra == 'test' - - sciline>=25.1.0 ; extra == 'test' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/5c/01/6cb4d63c6b6933be4b7945b2f64638336420f04ea71ca5b9a7539c008bc5/scippnexus-26.1.1-py3-none-any.whl - name: scippnexus - version: 26.1.1 - sha256: 899a0a5e71291b7809d902c17b6c74addf5a805397eabcec557491ff74eead12 + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl + name: multidict + version: 6.7.1 + sha256: 960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5 requires_dist: - - scipp>=24.2.0 - - scipy>=1.10.0 - - h5py>=3.12 - - pytest>=7.0 ; extra == 'test' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: scipy - version: 1.17.1 - sha256: 43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4 + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl + name: greenlet + version: 3.5.0 + sha256: 728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5 requires_dist: - - numpy>=1.26.4,<2.7 - - pytest>=8.0.0 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-timeout ; extra == 'test' - - pytest-xdist ; extra == 'test' - - asv ; extra == 'test' - - mpmath ; extra == 'test' - - gmpy2 ; extra == 'test' - - threadpoolctl ; extra == 'test' - - scikit-umfpack ; extra == 'test' - - pooch ; extra == 'test' - - hypothesis>=6.30 ; extra == 'test' - - array-api-strict>=2.3.1 ; extra == 'test' - - cython ; extra == 'test' - - meson ; extra == 'test' - - ninja ; sys_platform != 'emscripten' and extra == 'test' - - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - - sphinx-design>=0.4.0 ; extra == 'doc' - - matplotlib>=3.5 ; extra == 'doc' - - numpydoc ; extra == 'doc' - - jupytext ; extra == 'doc' - - myst-nb>=1.2.0 ; extra == 'doc' - - pooch ; extra == 'doc' - - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - - jupyterlite-pyodide-kernel ; extra == 'doc' - - linkify-it-py ; extra == 'doc' - - tabulate ; extra == 'doc' - - click<8.3.0 ; extra == 'dev' - - spin ; extra == 'dev' - - mypy==1.10.0 ; extra == 'dev' - - typing-extensions ; extra == 'dev' - - types-psutil ; extra == 'dev' - - pycodestyle ; extra == 'dev' - - ruff>=0.12.0 ; extra == 'dev' - - cython-lint>=0.12.2 ; extra == 'dev' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl - name: scipy - version: 1.17.1 - sha256: 37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448 + - sphinx ; extra == 'docs' + - furo ; extra == 'docs' + - objgraph ; extra == 'test' + - psutil ; extra == 'test' + - setuptools ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + name: mdurl + version: 0.1.2 + sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/b3/52/bc858b1665d0dec3a2511f4e6f5c18ea85c0977563d624d597c95d6d0fd7/jupyterquiz-2.9.6.4-py2.py3-none-any.whl + name: jupyterquiz + version: 2.9.6.4 + sha256: f8c4418f6c827454523fc882a30d744b585cb58ac1ae277769c3059d04fc272b +- pypi: https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl + name: markdown-it-py + version: 4.2.0 + sha256: 9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a requires_dist: - - numpy>=1.26.4,<2.7 - - pytest>=8.0.0 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-timeout ; extra == 'test' - - pytest-xdist ; extra == 'test' - - asv ; extra == 'test' - - mpmath ; extra == 'test' - - gmpy2 ; extra == 'test' - - threadpoolctl ; extra == 'test' - - scikit-umfpack ; extra == 'test' - - pooch ; extra == 'test' - - hypothesis>=6.30 ; extra == 'test' - - array-api-strict>=2.3.1 ; extra == 'test' - - cython ; extra == 'test' - - meson ; extra == 'test' - - ninja ; sys_platform != 'emscripten' and extra == 'test' - - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - - sphinx-design>=0.4.0 ; extra == 'doc' - - matplotlib>=3.5 ; extra == 'doc' - - numpydoc ; extra == 'doc' - - jupytext ; extra == 'doc' - - myst-nb>=1.2.0 ; extra == 'doc' - - pooch ; extra == 'doc' - - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - - jupyterlite-pyodide-kernel ; extra == 'doc' - - linkify-it-py ; extra == 'doc' - - tabulate ; extra == 'doc' - - click<8.3.0 ; extra == 'dev' - - spin ; extra == 'dev' - - mypy==1.10.0 ; extra == 'dev' - - typing-extensions ; extra == 'dev' - - types-psutil ; extra == 'dev' - - pycodestyle ; extra == 'dev' - - ruff>=0.12.0 ; extra == 'dev' - - cython-lint>=0.12.2 ; extra == 'dev' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl - name: scipy - version: 1.17.1 - sha256: 7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d + - mdurl~=0.1 + - psutil ; extra == 'benchmarking' + - pytest ; extra == 'benchmarking' + - pytest-benchmark ; extra == 'benchmarking' + - commonmark~=0.9 ; extra == 'compare' + - markdown~=3.4 ; extra == 'compare' + - mistletoe~=1.0 ; extra == 'compare' + - mistune~=3.0 ; extra == 'compare' + - panflute~=2.3 ; extra == 'compare' + - markdown-it-pyrs ; extra == 'compare' + - linkify-it-py>=1,<3 ; extra == 'linkify' + - mdit-py-plugins>=0.5.0 ; extra == 'plugins' + - gprof2dot ; extra == 'profiling' + - mdit-py-plugins>=0.5.0 ; extra == 'rtd' + - myst-parser ; extra == 'rtd' + - pyyaml ; extra == 'rtd' + - sphinx ; extra == 'rtd' + - sphinx-copybutton ; extra == 'rtd' + - sphinx-design ; extra == 'rtd' + - sphinx-book-theme~=1.0 ; extra == 'rtd' + - jupyter-sphinx ; extra == 'rtd' + - ipykernel ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + - pytest-timeout ; extra == 'testing' + - requests ; extra == 'testing' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b4/5e/bf11645aebb9af7d8d35927c40d3855816a0855c799e8156eeca8d632c90/diffpy_structure-3.4.0-py3-none-any.whl + name: diffpy-structure + version: 3.4.0 + sha256: bd0f06a96635d80316f51ebc0a08003bdeb2cb48c9bb18cbed1455ac60645e48 requires_dist: - - numpy>=1.26.4,<2.7 - - pytest>=8.0.0 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-timeout ; extra == 'test' - - pytest-xdist ; extra == 'test' - - asv ; extra == 'test' - - mpmath ; extra == 'test' - - gmpy2 ; extra == 'test' - - threadpoolctl ; extra == 'test' - - scikit-umfpack ; extra == 'test' - - pooch ; extra == 'test' - - hypothesis>=6.30 ; extra == 'test' - - array-api-strict>=2.3.1 ; extra == 'test' - - cython ; extra == 'test' - - meson ; extra == 'test' - - ninja ; sys_platform != 'emscripten' and extra == 'test' - - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - - sphinx-design>=0.4.0 ; extra == 'doc' - - matplotlib>=3.5 ; extra == 'doc' - - numpydoc ; extra == 'doc' - - jupytext ; extra == 'doc' - - myst-nb>=1.2.0 ; extra == 'doc' - - pooch ; extra == 'doc' - - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - - jupyterlite-pyodide-kernel ; extra == 'doc' - - linkify-it-py ; extra == 'doc' - - tabulate ; extra == 'doc' - - click<8.3.0 ; extra == 'dev' - - spin ; extra == 'dev' - - mypy==1.10.0 ; extra == 'dev' - - typing-extensions ; extra == 'dev' - - types-psutil ; extra == 'dev' - - pycodestyle ; extra == 'dev' - - ruff>=0.12.0 ; extra == 'dev' - - cython-lint>=0.12.2 ; extra == 'dev' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl - name: scipy - version: 1.17.1 - sha256: a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee + - numpy + - pycifrw + - diffpy-utils + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl + name: watchdog + version: 6.0.0 + sha256: 20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 requires_dist: - - numpy>=1.26.4,<2.7 - - pytest>=8.0.0 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-timeout ; extra == 'test' - - pytest-xdist ; extra == 'test' - - asv ; extra == 'test' - - mpmath ; extra == 'test' - - gmpy2 ; extra == 'test' - - threadpoolctl ; extra == 'test' - - scikit-umfpack ; extra == 'test' - - pooch ; extra == 'test' - - hypothesis>=6.30 ; extra == 'test' - - array-api-strict>=2.3.1 ; extra == 'test' - - cython ; extra == 'test' - - meson ; extra == 'test' - - ninja ; sys_platform != 'emscripten' and extra == 'test' - - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - - sphinx-design>=0.4.0 ; extra == 'doc' - - matplotlib>=3.5 ; extra == 'doc' - - numpydoc ; extra == 'doc' - - jupytext ; extra == 'doc' - - myst-nb>=1.2.0 ; extra == 'doc' - - pooch ; extra == 'doc' - - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - - jupyterlite-pyodide-kernel ; extra == 'doc' - - linkify-it-py ; extra == 'doc' - - tabulate ; extra == 'doc' - - click<8.3.0 ; extra == 'dev' - - spin ; extra == 'dev' - - mypy==1.10.0 ; extra == 'dev' - - typing-extensions ; extra == 'dev' - - types-psutil ; extra == 'dev' - - pycodestyle ; extra == 'dev' - - ruff>=0.12.0 ; extra == 'dev' - - cython-lint>=0.12.2 ; extra == 'dev' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl - name: scipy - version: 1.17.1 - sha256: d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl + name: validate-pyproject + version: '0.25' + sha256: f9d05e2686beff82f9ea954f582306b036ced3d3feb258c1110f2c2a495b1981 requires_dist: - - numpy>=1.26.4,<2.7 - - pytest>=8.0.0 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-timeout ; extra == 'test' - - pytest-xdist ; extra == 'test' - - asv ; extra == 'test' - - mpmath ; extra == 'test' - - gmpy2 ; extra == 'test' - - threadpoolctl ; extra == 'test' - - scikit-umfpack ; extra == 'test' - - pooch ; extra == 'test' - - hypothesis>=6.30 ; extra == 'test' - - array-api-strict>=2.3.1 ; extra == 'test' - - cython ; extra == 'test' - - meson ; extra == 'test' - - ninja ; sys_platform != 'emscripten' and extra == 'test' - - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - - sphinx-design>=0.4.0 ; extra == 'doc' - - matplotlib>=3.5 ; extra == 'doc' - - numpydoc ; extra == 'doc' - - jupytext ; extra == 'doc' - - myst-nb>=1.2.0 ; extra == 'doc' - - pooch ; extra == 'doc' - - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - - jupyterlite-pyodide-kernel ; extra == 'doc' - - linkify-it-py ; extra == 'doc' - - tabulate ; extra == 'doc' - - click<8.3.0 ; extra == 'dev' - - spin ; extra == 'dev' - - mypy==1.10.0 ; extra == 'dev' - - typing-extensions ; extra == 'dev' - - types-psutil ; extra == 'dev' - - pycodestyle ; extra == 'dev' - - ruff>=0.12.0 ; extra == 'dev' - - cython-lint>=0.12.2 ; extra == 'dev' + - fastjsonschema>=2.16.2,<=3 + - packaging>=24.2 ; extra == 'all' + - trove-classifiers>=2021.10.20 ; extra == 'all' + - tomli>=1.2.1 ; python_full_version < '3.11' and extra == 'all' + - validate-pyproject-schema-store ; extra == 'store' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl + name: dnspython + version: 2.8.0 + sha256: 01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af + requires_dist: + - black>=25.1.0 ; extra == 'dev' + - coverage>=7.0 ; extra == 'dev' + - flake8>=7 ; extra == 'dev' + - hypercorn>=0.17.0 ; extra == 'dev' + - mypy>=1.17 ; extra == 'dev' + - pylint>=3 ; extra == 'dev' + - pytest-cov>=6.2.0 ; extra == 'dev' + - pytest>=8.4 ; extra == 'dev' + - quart-trio>=0.12.0 ; extra == 'dev' + - sphinx-rtd-theme>=3.0.0 ; extra == 'dev' + - sphinx>=8.2.0 ; extra == 'dev' + - twine>=6.1.0 ; extra == 'dev' + - wheel>=0.45.0 ; extra == 'dev' + - cryptography>=45 ; extra == 'dnssec' + - h2>=4.2.0 ; extra == 'doh' + - httpcore>=1.0.0 ; extra == 'doh' + - httpx>=0.28.0 ; extra == 'doh' + - aioquic>=1.2.0 ; extra == 'doq' + - idna>=3.10 ; extra == 'idna' + - trio>=0.30 ; extra == 'trio' + - wmi>=1.5.1 ; sys_platform == 'win32' and extra == 'wmi' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl + name: numpy + version: 2.4.4 + sha256: 6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - name: scipy - version: 1.17.1 - sha256: 581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464 +- pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + name: h5py + version: 3.16.0 + sha256: 9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402 requires_dist: - - numpy>=1.26.4,<2.7 - - pytest>=8.0.0 ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-timeout ; extra == 'test' - - pytest-xdist ; extra == 'test' - - asv ; extra == 'test' - - mpmath ; extra == 'test' - - gmpy2 ; extra == 'test' - - threadpoolctl ; extra == 'test' - - scikit-umfpack ; extra == 'test' - - pooch ; extra == 'test' - - hypothesis>=6.30 ; extra == 'test' - - array-api-strict>=2.3.1 ; extra == 'test' - - cython ; extra == 'test' - - meson ; extra == 'test' - - ninja ; sys_platform != 'emscripten' and extra == 'test' - - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' - - intersphinx-registry ; extra == 'doc' - - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - - sphinx-design>=0.4.0 ; extra == 'doc' - - matplotlib>=3.5 ; extra == 'doc' - - numpydoc ; extra == 'doc' - - jupytext ; extra == 'doc' - - myst-nb>=1.2.0 ; extra == 'doc' - - pooch ; extra == 'doc' - - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' - - jupyterlite-pyodide-kernel ; extra == 'doc' - - linkify-it-py ; extra == 'doc' - - tabulate ; extra == 'doc' - - click<8.3.0 ; extra == 'dev' - - spin ; extra == 'dev' - - mypy==1.10.0 ; extra == 'dev' - - typing-extensions ; extra == 'dev' - - types-psutil ; extra == 'dev' - - pycodestyle ; extra == 'dev' - - ruff>=0.12.0 ; extra == 'dev' - - cython-lint>=0.12.2 ; extra == 'dev' - requires_python: '>=3.11' -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda - sha256: 8fc024bf1a7b99fc833b131ceef4bef8c235ad61ecb95a71a6108be2ccda63e8 - md5: b70e2d44e6aa2beb69ba64206a16e4c6 - depends: - - __osx - - pyobjc-framework-cocoa - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 22519 - timestamp: 1770937603551 -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda - sha256: 305446a0b018f285351300463653d3d3457687270e20eda37417b12ee386ef76 - md5: 6ac53f3fff2c416d63511843a04646fa - depends: - - __win - - pywin32 - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 22864 - timestamp: 1770937641143 -- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda - sha256: 59656f6b2db07229351dfb3a859c35e57cc8e8bcbc86d4e501bff881a6f771f1 - md5: 28eb91468df04f655a57bcfbb35fc5c5 - depends: - - __linux - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/send2trash?source=hash-mapping - size: 24108 - timestamp: 1770937597662 -- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 - md5: 8e194e7b992f99a5015edbd4ebd38efd - depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/setuptools?source=hash-mapping - size: 639697 - timestamp: 1773074868565 -- pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl - name: shellingham - version: 1.5.4 - sha256: 7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - name: simple-websocket - version: 1.1.0 - sha256: 4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bd/9a/af61ec03b3116c161fd7a06b9e8a265729a8718458333e8ffbb06d9a3978/numba-0.65.1-cp311-cp311-win_amd64.whl + name: numba + version: 0.65.1 + sha256: df40a5028a975b9ea66f6a2a3f7abbdbd541a863070e34ed367aff21141248e4 requires_dist: - - wsproto - - tox ; extra == 'dev' - - flake8 ; extra == 'dev' + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl + name: kiwisolver + version: 1.5.0 + sha256: beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl + name: kiwisolver + version: 1.5.0 + sha256: 0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl + name: h5py + version: 3.16.0 + sha256: c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/bf/50/98b146aea0f1cd7531d25f12bea69fa9ce8d1662124f93fb30dc4511b65e/docstring_parser_fork-0.0.14-py3-none-any.whl + name: docstring-parser-fork + version: 0.0.14 + sha256: 4c544f234ef2cc2749a3df32b70c437d77888b1099143a1ad5454452c574b9af + requires_dist: + - docstring-parser[docs] ; extra == 'dev' + - docstring-parser[test] ; extra == 'dev' + - pre-commit>=2.16.0 ; python_full_version >= '3.9' and extra == 'dev' + - pydoctor>=25.4.0 ; extra == 'docs' + - pytest ; extra == 'test' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/bf/e1/9e8e09ab8fc5c77f228e3271188dc4f569d12692548c42bff81b1fbba5e1/easydiffraction-0.16.0-py3-none-any.whl + name: easydiffraction + version: 0.16.0 + sha256: 2691a1e175974ca79e0ec3c219d92b77f277c38fb3b0b8d25f6f7e99696bf70f + requires_dist: + - asciichartpy + - asteval + - bumps + - colorama + - crysfml + - cryspy + - darkdetect + - dfo-ls + - diffpy-pdffit2 + - diffpy-utils + - essdiffraction + - gemmi + - lmfit + - numpy + - pandas + - plotly + - pooch + - py3dmol + - rich + - scipy + - sympy + - tabulate + - typeguard + - typer + - uncertainties + - varname + - build ; extra == 'dev' + - copier ; extra == 'dev' + - docstripy ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - mike ; extra == 'dev' + - mkdocs ; extra == 'dev' + - mkdocs-autorefs ; extra == 'dev' + - mkdocs-jupyter ; extra == 'dev' + - mkdocs-markdownextradata-plugin ; extra == 'dev' + - mkdocs-material ; extra == 'dev' + - mkdocs-plugin-inline-svg ; extra == 'dev' + - mkdocstrings-python ; extra == 'dev' + - nbmake ; extra == 'dev' + - nbqa ; extra == 'dev' + - nbstripout ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' - pytest ; extra == 'dev' + - pytest-benchmark ; extra == 'dev' - pytest-cov ; extra == 'dev' - - sphinx ; extra == 'docs' - requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d - md5: 3339e3b65d58accf4ca4fb8748ab16b3 - depends: - - python >=3.9 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/six?source=hash-mapping - size: 18455 - timestamp: 1753199211006 + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' + - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl + name: aiohttp + version: 3.13.5 + sha256: 69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl + name: aiohttp + version: 3.13.5 + sha256: d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8 + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c1/9c/1236dd7d22ed48527286b613c84e3376ea731b65e6734b6e6a0b4d03744c/gemmi-0.7.5-cp313-cp313-macosx_11_0_arm64.whl + name: gemmi + version: 0.7.5 + sha256: c7d8b08c33fe6ba375223306149092440c69cbfbd55c3d3e3436e5fb315a225d + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl name: smmap version: 5.0.3 sha256: c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda - sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad - md5: 03fe290994c5e4ec17293cfb6bdce520 - depends: - - python >=3.10 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/sniffio?source=hash-mapping - size: 15698 - timestamp: 1762941572482 -- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8.3-pyhd8ed1ab_0.conda - sha256: 23b71ecf089967d2900126920e7f9ff18cdcef82dbff3e2f54ffa360243a17ac - md5: 18de09b20462742fe093ba39185d9bac - depends: - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/soupsieve?source=hash-mapping - size: 38187 - timestamp: 1769034509657 -- pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - name: spdx-headers - version: 1.5.1 - sha256: 73bcb1ed087824b55ccaa497d03d8f0f0b0eaf30e5f0f7d5bbd29d2c4fe78fcf +- pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl + name: tzlocal + version: 5.3.1 + sha256: eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d requires_dist: - - chardet>=5.2.0 - - requests>=2.32.3 - - black>=23.0.0 ; extra == 'dev' - - build>=0.10.0 ; extra == 'dev' - - hatch>=1.9.0 ; extra == 'dev' - - isort>=5.12.0 ; extra == 'dev' - - mypy>=1.0.0 ; extra == 'dev' - - pre-commit>=4.3.0 ; extra == 'dev' - - pytest-cov>=4.0.0 ; extra == 'dev' - - pytest>=7.0.0 ; extra == 'dev' - - ruff>=0.5.0 ; extra == 'dev' - - twine>=4.0.0 ; extra == 'dev' - - types-requests>=2.31.0.6 ; extra == 'dev' - - pytest-cov>=4.0.0 ; extra == 'test' - - pytest-mock>=3.10.0 ; extra == 'test' - - pytest>=7.0.0 ; extra == 'test' + - tzdata ; sys_platform == 'win32' + - pytest>=4.3 ; extra == 'devenv' + - pytest-mock>=3.3 ; extra == 'devenv' + - pytest-cov ; extra == 'devenv' + - check-manifest ; extra == 'devenv' + - zest-releaser ; extra == 'devenv' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/16/56/a31e8d3c9e8d21100b83bbe1c1f3f7c94db317393a229e193461e5e6d2a4/spglib-2.6.0-cp313-cp313-win_amd64.whl - name: spglib - version: 2.6.0 - sha256: ff1632524f6ac0031423474e48d6b69f4932ecb7eb4446a501f59619e2b5cbc9 +- pypi: https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: lxml + version: 6.1.0 + sha256: 363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c requires_dist: - - numpy>=1.20,<3 - - importlib-resources ; python_full_version < '3.10' - - typing-extensions>=4.9.0 ; python_full_version < '3.13' - - pytest ; extra == 'test' - - pyyaml ; extra == 'test' - - sphinx>=7.0 ; extra == 'docs' - - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl + name: h5py + version: 3.16.0 + sha256: 18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c4/6d/82e65254354ba651dc966775270a9bbc02414a3eb3f1704e6c87dab2ea83/essdiffraction-26.5.1-py3-none-any.whl + name: essdiffraction + version: 26.5.1 + sha256: 8a6c779078c71be250714619214069221ab7968a69580d4e4d3f4b3e9a1a53ad + requires_dist: + - dask>=2022.1.0 + - essreduce>=26.4.0 + - graphviz + - numpy>=2 + - plopp>=26.2.0 + - pythreejs>=2.4.1 + - sciline>=25.4.1 + - scipp>=25.11.0 + - scippneutron>=26.3.0 + - scippnexus>=23.12.0 + - tof>=25.12.0 + - ncrystal[cif]>=4.1.0 + - spglib!=2.7 + - pandas>=2.1.2 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - pytest>=7.0 ; extra == 'test' + - ipywidgets>=8.1.7 ; extra == 'test' + - autodoc-pydantic ; extra == 'docs' + - ipykernel ; extra == 'docs' + - ipympl ; extra == 'docs' + - ipython!=8.7.0 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - nbsphinx ; extra == 'docs' + - pandas ; extra == 'docs' + - pooch ; extra == 'docs' + - pydata-sphinx-theme>=0.14 ; extra == 'docs' + - sphinx ; extra == 'docs' - sphinx-autodoc-typehints ; extra == 'docs' - - myst-parser>=2.0 ; extra == 'docs' - - linkify-it-py ; extra == 'docs' - - sphinx-tippy ; extra == 'docs' - - spglib[test] ; extra == 'test-cov' - - pytest-cov ; extra == 'test-cov' - - spglib[test] ; extra == 'test-benchmark' - - pytest-benchmark ; extra == 'test-benchmark' - - spglib[test] ; extra == 'dev' - - pre-commit ; extra == 'dev' - - spglib[docs] ; extra == 'doc' - - spglib[test] ; extra == 'testing' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/2d/47/634fe8323c6c2bfa86e10eb41ebfe410db5e6231aa1727a31ce4f002480f/spglib-2.6.0-cp313-cp313-macosx_11_0_arm64.whl - name: spglib - version: 2.6.0 - sha256: 12db7a0d6ad84c55e61eda67590a438edeb48e57ffd5df868cd931b57fb8c630 + - sphinx-copybutton ; extra == 'docs' + - sphinx-design ; extra == 'docs' + - sphinxcontrib-bibtex ; extra == 'docs' + - pyarrow ; extra == 'docs' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl + name: pandas + version: 3.0.2 + sha256: dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2024.2 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2024.2 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl + name: matplotlib + version: 3.10.9 + sha256: f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921 + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.22.0 + - kiwisolver>=1.3.1 + - numpy>=1.23 + - packaging>=20.0 + - pillow>=8 + - pyparsing>=3 + - python-dateutil>=2.7 + - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' + - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' + - setuptools-scm>=7,<10 ; extra == 'dev' + - setuptools>=64 ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c6/3d/020a6b6248c3d4a37797db068256f0b3f15b01bc481327ba888c50309aa8/mkdocs_plugin_inline_svg-0.1.0-py3-none-any.whl + name: mkdocs-plugin-inline-svg + version: 0.1.0 + sha256: a5aab2d98a19b24019f8e650f54fc647c2f31e7d0e36fc5cf2d2161acc0ea49a requires_dist: - - numpy>=1.20,<3 - - importlib-resources ; python_full_version < '3.10' - - typing-extensions>=4.9.0 ; python_full_version < '3.13' - - pytest ; extra == 'test' - - pyyaml ; extra == 'test' - - sphinx>=7.0 ; extra == 'docs' - - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - myst-parser>=2.0 ; extra == 'docs' - - linkify-it-py ; extra == 'docs' - - sphinx-tippy ; extra == 'docs' - - spglib[test] ; extra == 'test-cov' - - pytest-cov ; extra == 'test-cov' - - spglib[test] ; extra == 'test-benchmark' - - pytest-benchmark ; extra == 'test-benchmark' - - spglib[test] ; extra == 'dev' - - pre-commit ; extra == 'dev' - - spglib[docs] ; extra == 'doc' - - spglib[test] ; extra == 'testing' + - mkdocs + requires_python: '>=3.5' +- pypi: https://files.pythonhosted.org/packages/c7/6b/6c02f55c2ce2f137ccca0986be7dd89bea31d5bee4346b4377fa3b8586df/ncrystal_core-4.4.2-py3-none-win_amd64.whl + name: ncrystal-core + version: 4.4.2 + sha256: 9b28a90b63849e6a3a807a0a59f7c2ee57e4c64f5643b2dcb6a798ac8ccf666a + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c7/a0/5ff05d1919ca249508012cad89f08fdc6cfbdaa15b41651c5fe6dffaf1d3/dfo_ls-1.6.5-py3-none-any.whl + name: dfo-ls + version: 1.6.5 + sha256: d147d42e471e240f9abf8bc38351a88f555ea6a8fcfd83119bbbf93c36f75ab2 + requires_dist: + - setuptools + - numpy + - scipy>=1.11 + - pandas + - pytest ; extra == 'dev' + - sphinx ; extra == 'dev' + - sphinx-rtd-theme ; extra == 'dev' + - trustregion>=1.1 ; extra == 'trustregion' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/53/01/1c0485ae02e645bc517bf5d5a6ca674f62c97e247890b954cbfe85c64dae/spglib-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: spglib - version: 2.6.0 - sha256: cc9d856d7cc936dc73b6b303aaf8b2fb4230a8527659373450c6e1139cbb2a5c +- pypi: https://files.pythonhosted.org/packages/c7/ea/7988934c8e3e3418aa043f70421817df28d06aef50bfd85f5ad3ec6e70f1/ncrystal_core-4.4.2-py3-none-macosx_11_0_arm64.whl + name: ncrystal-core + version: 4.4.2 + sha256: b7e6101a6850aa18cf441825214381614db444ffcba648de8266fe1c4d1024ce + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl + name: multidict + version: 6.7.1 + sha256: bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b requires_dist: - - numpy>=1.20,<3 - - importlib-resources ; python_full_version < '3.10' - - typing-extensions>=4.9.0 ; python_full_version < '3.13' - - pytest ; extra == 'test' - - pyyaml ; extra == 'test' - - sphinx>=7.0 ; extra == 'docs' - - sphinxcontrib-bibtex>=2.5 ; extra == 'docs' - - sphinx-book-theme ; extra == 'docs' - - sphinx-autodoc-typehints ; extra == 'docs' - - myst-parser>=2.0 ; extra == 'docs' - - linkify-it-py ; extra == 'docs' - - sphinx-tippy ; extra == 'docs' - - spglib[test] ; extra == 'test-cov' - - pytest-cov ; extra == 'test-cov' - - spglib[test] ; extra == 'test-benchmark' - - pytest-benchmark ; extra == 'test-benchmark' - - spglib[test] ; extra == 'dev' - - pre-commit ; extra == 'dev' - - spglib[docs] ; extra == 'doc' - - spglib[test] ; extra == 'testing' + - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl - name: sqlalchemy - version: 2.0.49 - sha256: 12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148 +- pypi: https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl + name: asn1crypto + version: 1.5.1 + sha256: db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67 +- pypi: https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl + name: backrefs + version: '7.0' + sha256: a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475 requires_dist: - - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - - typing-extensions>=4.6.0 - - greenlet>=1 ; extra == 'asyncio' - - mypy>=0.910 ; extra == 'mypy' - - pyodbc ; extra == 'mssql' - - pymssql ; extra == 'mssql-pymssql' - - pyodbc ; extra == 'mssql-pyodbc' - - mysqlclient>=1.4.0 ; extra == 'mysql' - - mysql-connector-python ; extra == 'mysql-connector' - - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector' - - cx-oracle>=8 ; extra == 'oracle' - - oracledb>=1.0.1 ; extra == 'oracle-oracledb' - - psycopg2>=2.7 ; extra == 'postgresql' - - pg8000>=1.29.1 ; extra == 'postgresql-pg8000' - - greenlet>=1 ; extra == 'postgresql-asyncpg' - - asyncpg ; extra == 'postgresql-asyncpg' - - psycopg2-binary ; extra == 'postgresql-psycopg2binary' - - psycopg2cffi ; extra == 'postgresql-psycopg2cffi' - - psycopg>=3.0.7 ; extra == 'postgresql-psycopg' - - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary' - - pymysql ; extra == 'pymysql' - - greenlet>=1 ; extra == 'aiomysql' - - aiomysql>=0.2.0 ; extra == 'aiomysql' - - greenlet>=1 ; extra == 'aioodbc' - - aioodbc ; extra == 'aioodbc' - - greenlet>=1 ; extra == 'asyncmy' - - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy' - - greenlet>=1 ; extra == 'aiosqlite' - - aiosqlite ; extra == 'aiosqlite' - - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - - sqlcipher3-binary ; extra == 'sqlcipher' - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl - name: sqlalchemy - version: 2.0.49 - sha256: df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120 + - regex ; extra == 'extras' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl + name: pytest-xdist + version: 3.8.0 + sha256: 202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88 requires_dist: - - importlib-metadata ; python_full_version < '3.8' - - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' - - typing-extensions>=4.6.0 - - greenlet>=1 ; extra == 'asyncio' - - mypy>=0.910 ; extra == 'mypy' - - pyodbc ; extra == 'mssql' - - pymssql ; extra == 'mssql-pymssql' - - pyodbc ; extra == 'mssql-pyodbc' - - mysqlclient>=1.4.0 ; extra == 'mysql' - - mysql-connector-python ; extra == 'mysql-connector' - - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector' - - cx-oracle>=8 ; extra == 'oracle' - - oracledb>=1.0.1 ; extra == 'oracle-oracledb' - - psycopg2>=2.7 ; extra == 'postgresql' - - pg8000>=1.29.1 ; extra == 'postgresql-pg8000' - - greenlet>=1 ; extra == 'postgresql-asyncpg' - - asyncpg ; extra == 'postgresql-asyncpg' - - psycopg2-binary ; extra == 'postgresql-psycopg2binary' - - psycopg2cffi ; extra == 'postgresql-psycopg2cffi' - - psycopg>=3.0.7 ; extra == 'postgresql-psycopg' - - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary' - - pymysql ; extra == 'pymysql' - - greenlet>=1 ; extra == 'aiomysql' - - aiomysql>=0.2.0 ; extra == 'aiomysql' - - greenlet>=1 ; extra == 'aioodbc' - - aioodbc ; extra == 'aioodbc' - - greenlet>=1 ; extra == 'asyncmy' - - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy' - - greenlet>=1 ; extra == 'aiosqlite' - - aiosqlite ; extra == 'aiosqlite' - - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - - sqlcipher3-binary ; extra == 'sqlcipher' - requires_python: '>=3.7' + - execnet>=2.1 + - pytest>=7.0.0 + - filelock ; extra == 'testing' + - psutil>=3.0 ; extra == 'psutil' + - setproctitle ; extra == 'setproctitle' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl + name: iniconfig + version: 2.3.0 + sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: fonttools + version: 4.62.1 + sha256: 1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: sqlalchemy version: 2.0.49 @@ -12464,836 +12671,890 @@ packages: - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite' - sqlcipher3-binary ; extra == 'sqlcipher' requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 - md5: b1b505328da7a6b246787df4b5a49fbc - depends: - - asttokens - - executing - - pure_eval - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/stack-data?source=hash-mapping - size: 26988 - timestamp: 1733569565672 -- pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz - name: svglib - version: 1.5.1 - sha256: 3ae765d3a9409ee60c0fb4d24c2deb6a80617aa927054f5bcd7fc98f0695e587 +- pypi: https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: numpy + version: 2.4.4 + sha256: df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d0/69/f24d3d1c38ad69e256138b4ec2452a8c7cf66be49dc214771ae99dd4f0a0/narwhals-2.20.0-py3-none-any.whl + name: narwhals + version: 2.20.0 + sha256: 16e750ea5507d4ba6e8d03455b5f93a535e0405976561baea235bca5dc9f475d + requires_dist: + - cudf-cu12>=24.10.0 ; extra == 'cudf' + - dask[dataframe]>=2024.8 ; extra == 'dask' + - duckdb>=1.1 ; extra == 'duckdb' + - ibis-framework>=6.0.0 ; extra == 'ibis' + - packaging ; extra == 'ibis' + - pyarrow-hotfix ; extra == 'ibis' + - rich ; extra == 'ibis' + - modin ; extra == 'modin' + - pandas>=1.1.3 ; extra == 'pandas' + - polars>=0.20.4 ; extra == 'polars' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - pyspark>=3.5.0 ; extra == 'pyspark' + - pyspark[connect]>=3.5.0 ; extra == 'pyspark-connect' + - duckdb>=1.1 ; extra == 'sql' + - sqlparse ; extra == 'sql' + - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl + name: watchdog + version: 6.0.0 + sha256: afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c requires_dist: - - reportlab - - lxml - - tinycss2>=0.6.0 - - cssselect2>=0.2.0 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl - name: svglib - version: 1.6.0 - sha256: 9aea8e2e81cbbf9c844460e4c7dc90e0a06aea7983bc201975ccd279d7b2d194 + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: numpy + version: 2.4.4 + sha256: c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl + name: mando + version: 0.7.1 + sha256: 26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a requires_dist: - - cssselect2>=0.2.0 - - lxml>=6.0.0 - - reportlab>=4.4.3 - - rlpycairo>=0.4.0 - - tinycss2>=0.6.0 - - mypy>=1.18.1 ; extra == 'dev' - - pre-commit>=4.3.0 ; extra == 'dev' - - pytest-cov>=7.0.0 ; extra == 'dev' - - pytest-runner>=6.0.1 ; extra == 'dev' - - pytest>=8.3.5 ; extra == 'dev' - - ruff>=0.13.0 ; extra == 'dev' - - tox>=4.30.2 ; extra == 'dev' + - six + - argparse ; python_full_version < '2.7' + - funcsigs ; python_full_version < '3.3' + - rst2ansi ; extra == 'restructuredtext' +- pypi: https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl + name: propcache + version: 0.4.1 + sha256: 6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl - name: sympy - version: 1.14.0 - sha256: e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5 +- pypi: https://files.pythonhosted.org/packages/d3/80/722402571443031989e87a5861f26f2e897778768906dff1f998b42f235d/diffpy_pdffit2-1.6.0-cp313-cp313-macosx_11_0_arm64.whl + name: diffpy-pdffit2 + version: 1.6.0 + sha256: 8837088196ddabd688deb77740c3f03ddc33f0df6964a8e3507701c32b67b8b2 requires_dist: - - mpmath>=1.1.0,<1.4 - - pytest>=7.1.0 ; extra == 'dev' - - hypothesis>=6.70.0 ; extra == 'dev' + - diffpy-structure + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl + name: fonttools + version: 4.62.1 + sha256: e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2 + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d4/06/19ff1efd58b85906149ce83dfddce23252cea5bec7e0fa5f834336cfe836/scipp-26.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipp + version: 26.3.1 + sha256: ab5859a24b3150b588dd2c67e68b0c7f07c9444eae501f3b6326d6b4a34cbf10 + requires_dist: + - numpy>=2 + - pytest ; extra == 'test' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl + name: pytest + version: 9.0.3 + sha256: 2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 + requires_dist: + - colorama>=0.4 ; sys_platform == 'win32' + - exceptiongroup>=1 ; python_full_version < '3.11' + - iniconfig>=1.0.1 + - packaging>=22 + - pluggy>=1.5,<2 + - pygments>=2.7.2 + - tomli>=1 ; python_full_version < '3.11' + - argcomplete ; extra == 'dev' + - attrs>=19.2 ; extra == 'dev' + - hypothesis>=3.56 ; extra == 'dev' + - mock ; extra == 'dev' + - requests ; extra == 'dev' + - setuptools ; extra == 'dev' + - xmlschema ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl + name: fsspec + version: 2026.4.0 + sha256: 11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2 + requires_dist: + - adlfs ; extra == 'abfs' + - adlfs ; extra == 'adl' + - pyarrow>=1 ; extra == 'arrow' + - dask ; extra == 'dask' + - distributed ; extra == 'dask' + - pre-commit ; extra == 'dev' + - ruff>=0.5 ; extra == 'dev' + - numpydoc ; extra == 'doc' + - sphinx ; extra == 'doc' + - sphinx-design ; extra == 'doc' + - sphinx-rtd-theme ; extra == 'doc' + - yarl ; extra == 'doc' + - dropbox ; extra == 'dropbox' + - dropboxdrivefs ; extra == 'dropbox' + - requests ; extra == 'dropbox' + - adlfs ; extra == 'full' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'full' + - dask ; extra == 'full' + - distributed ; extra == 'full' + - dropbox ; extra == 'full' + - dropboxdrivefs ; extra == 'full' + - fusepy ; extra == 'full' + - gcsfs>2024.2.0 ; extra == 'full' + - libarchive-c ; extra == 'full' + - ocifs ; extra == 'full' + - panel ; extra == 'full' + - paramiko ; extra == 'full' + - pyarrow>=1 ; extra == 'full' + - pygit2 ; extra == 'full' + - requests ; extra == 'full' + - s3fs>2024.2.0 ; extra == 'full' + - smbprotocol ; extra == 'full' + - tqdm ; extra == 'full' + - fusepy ; extra == 'fuse' + - gcsfs>2024.2.0 ; extra == 'gcs' + - pygit2 ; extra == 'git' + - requests ; extra == 'github' + - gcsfs ; extra == 'gs' + - panel ; extra == 'gui' + - pyarrow>=1 ; extra == 'hdfs' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'http' + - libarchive-c ; extra == 'libarchive' + - ocifs ; extra == 'oci' + - s3fs>2024.2.0 ; extra == 's3' + - paramiko ; extra == 'sftp' + - smbprotocol ; extra == 'smb' + - paramiko ; extra == 'ssh' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test' + - numpy ; extra == 'test' + - pytest ; extra == 'test' + - pytest-asyncio!=0.22.0 ; extra == 'test' + - pytest-benchmark ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-recording ; extra == 'test' + - pytest-rerunfailures ; extra == 'test' + - requests ; extra == 'test' + - aiobotocore>=2.5.4,<3.0.0 ; extra == 'test-downstream' + - dask[dataframe,test] ; extra == 'test-downstream' + - moto[server]>4,<5 ; extra == 'test-downstream' + - pytest-timeout ; extra == 'test-downstream' + - xarray ; extra == 'test-downstream' + - adlfs ; extra == 'test-full' + - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test-full' + - backports-zstd ; python_full_version < '3.14' and extra == 'test-full' + - cloudpickle ; extra == 'test-full' + - dask ; extra == 'test-full' + - distributed ; extra == 'test-full' + - dropbox ; extra == 'test-full' + - dropboxdrivefs ; extra == 'test-full' + - fastparquet ; extra == 'test-full' + - fusepy ; extra == 'test-full' + - gcsfs ; extra == 'test-full' + - jinja2 ; extra == 'test-full' + - kerchunk ; extra == 'test-full' + - libarchive-c ; extra == 'test-full' + - lz4 ; extra == 'test-full' + - notebook ; extra == 'test-full' + - numpy ; extra == 'test-full' + - ocifs ; extra == 'test-full' + - pandas<3.0.0 ; extra == 'test-full' + - panel ; extra == 'test-full' + - paramiko ; extra == 'test-full' + - pyarrow ; extra == 'test-full' + - pyarrow>=1 ; extra == 'test-full' + - pyftpdlib ; extra == 'test-full' + - pygit2 ; extra == 'test-full' + - pytest ; extra == 'test-full' + - pytest-asyncio!=0.22.0 ; extra == 'test-full' + - pytest-benchmark ; extra == 'test-full' + - pytest-cov ; extra == 'test-full' + - pytest-mock ; extra == 'test-full' + - pytest-recording ; extra == 'test-full' + - pytest-rerunfailures ; extra == 'test-full' + - python-snappy ; extra == 'test-full' + - requests ; extra == 'test-full' + - smbprotocol ; extra == 'test-full' + - tqdm ; extra == 'test-full' + - urllib3 ; extra == 'test-full' + - zarr ; extra == 'test-full' + - zstandard ; python_full_version < '3.14' and extra == 'test-full' + - tqdm ; extra == 'tqdm' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: frozenlist + version: 1.8.0 + sha256: fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl + name: trove-classifiers + version: 2026.4.28.13 + sha256: 8f4b1eb4e16296b57d612965444f87a83861cc989a0451ac97fe4265ddef03b8 +- pypi: https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl + name: pycodestyle + version: 2.14.0 + sha256: dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl - name: tabulate - version: 0.10.0 - sha256: f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3 - requires_dist: - - wcwidth ; extra == 'widechars' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda - sha256: b375e8df0d5710717c31e7c8e93c025c37fa3504aea325c7a55509f64e5d4340 - md5: e43ca10d61e55d0a8ec5d8c62474ec9e - depends: - - __win - - pywinpty >=1.1.0 - - python >=3.10 - - tornado >=6.1.0 - - python - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 23665 - timestamp: 1766513806974 -- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda - sha256: 6b6727a13d1ca6a23de5e6686500d0669081a117736a87c8abf444d60c1e40eb - md5: 17b43cee5cc84969529d5d0b0309b2cb - depends: - - __unix - - ptyprocess - - python >=3.10 - - tornado >=6.1.0 - - python - license: BSD-2-Clause - license_family: BSD - purls: - - pkg:pypi/terminado?source=hash-mapping - size: 24749 - timestamp: 1766513766867 -- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 - md5: f1acf5fdefa8300de697982bcb1761c9 - depends: - - python >=3.5 - - webencodings >=0.4 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/tinycss2?source=hash-mapping - size: 28285 - timestamp: 1729802975370 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac - md5: cffd3bdd58090148f4cfcd831f4b26ab - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - constrains: - - xorg-libx11 >=1.8.12,<2.0a0 - license: TCL - license_family: BSD - purls: [] - size: 3301196 - timestamp: 1769460227866 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 - md5: a9d86bc62f39b94c4661716624eb21b0 - depends: - - __osx >=11.0 - - libzlib >=1.3.1,<2.0a0 - license: TCL - license_family: BSD - purls: [] - size: 3127137 - timestamp: 1769460817696 -- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - sha256: 0e79810fae28f3b69fe7391b0d43f5474d6bd91d451d5f2bde02f55ae481d5e3 - md5: 0481bfd9814bf525bd4b3ee4b51494c4 - depends: - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: TCL - license_family: BSD - purls: [] - size: 3526350 - timestamp: 1769460339384 -- pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl - name: tof - version: 26.3.0 - sha256: e89783a072b05fdb53d9e76fbf919dc8935e75e118fdaf17ca5cc33727ef002b +- pypi: https://files.pythonhosted.org/packages/d8/8b/e2bbeb42068f0c48899e8eddd34902afc0f7429d4d2a152d2dc2670dc661/pythreejs-2.4.2-py3-none-any.whl + name: pythreejs + version: 2.4.2 + sha256: 8418807163ad91f4df53b58c4e991b26214852a1236f28f1afeaadf99d095818 requires_dist: - - plopp>=23.10.0 - - pooch>=1.5.0 - - scipp>=25.1.0 - - lazy-loader>=0.3 - - pytest>=8.0 ; extra == 'test' - - scippneutron>=24.12.0 ; extra == 'test' - requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/33/f0/3fe8c6e69135a845f4106f2ff8b6805638d4e85c264e70114e8126689587/tokenize_rt-6.2.0-py2.py3-none-any.whl - name: tokenize-rt - version: 6.2.0 - sha256: a152bf4f249c847a66497a4a95f63376ed68ac6abf092a2f7cfb29d044ecff44 + - ipywidgets>=7.2.1 + - ipydatawidgets>=1.1.1 + - numpy + - traitlets + - sphinx>=1.5 ; extra == 'docs' + - nbsphinx>=0.2.13 ; extra == 'docs' + - nbsphinx-link ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - scipy ; extra == 'examples' + - matplotlib ; extra == 'examples' + - scikit-image ; extra == 'examples' + - ipywebrtc ; extra == 'examples' + - nbval ; extra == 'test' + - pytest-check-links ; extra == 'test' + - numpy>=1.14 ; extra == 'test' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl + name: frozenlist + version: 1.8.0 + sha256: 878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231 requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd - md5: b5325cf06a000c5b14970462ff5e4d58 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/tomli?source=hash-mapping - size: 21561 - timestamp: 1774492402955 -- pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - name: toolz - version: 1.1.0 - sha256: 15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8 +- pypi: https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: msgpack + version: 1.1.2 + sha256: 454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py311h49ec1c0_0.conda - sha256: cd8bc08ca661291cfbb05581b79f7e6a6971a072a54a335abcea5460c1230880 - md5: 73b44a114241e564deb5846e7394bf19 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=compressed-mapping - size: 876817 - timestamp: 1774358035290 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.5-py313h07c4f96_0.conda - sha256: 9e8497e1ecca77d03c6be2d3b5f901dfe0ab99686af4fb94ab418b7d449ac547 - md5: 6c0b0ae017b5bfd9c8d718217efd8f14 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 882996 - timestamp: 1774358035145 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py311hc949640_0.conda - sha256: 572923958ec987f3f68e228dfec243c89ab94b50398731215d36e2e040db8703 - md5: 6f12d9ff025ab1875dff67ad779ca29d - depends: - - __osx >=11.0 - - python >=3.11,<3.12.0a0 - - python >=3.11,<3.12.0a0 *_cpython - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 875022 - timestamp: 1774358570256 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.5-py313h0997733_0.conda - sha256: c5b0ee042d8a0b88a3823226dc95b794c042c498aee330aa9b4d78bfad01d099 - md5: 303333dd882dfeb303cc8bfac178464b - depends: - - __osx >=11.0 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 - - python_abi 3.13.* *_cp313 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 883472 - timestamp: 1774358832451 -- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py311h3485c13_0.conda - sha256: 71f58fe09f7729ad82c8eb942e775ddeffcef454483911d19d1041ac5d81eec3 - md5: b004afcc680af88cb877978e71d42667 - depends: - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 878544 - timestamp: 1774358187588 -- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.5-py313h5ea7bf4_0.conda - sha256: b97fab804ab457cf4157103289317e3619e801a77410e756bb35c6223418cc6e - md5: 7d53f0d25ad5fd7d6962ce4eb385fb07 - depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/tornado?source=hash-mapping - size: 883689 - timestamp: 1774358224157 -- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.15.0-pyhcf101f3_0.conda - sha256: dfb681579be59c2e790c95f7f49b7529a9b0511d6385ad276e3c8988cbd54d2c - md5: 4bada6a6d908a27262af8ebddf4f7492 - depends: - - python >=3.10 - - python - license: BSD-3-Clause - purls: - - pkg:pypi/traitlets?source=compressed-mapping - size: 115165 - timestamp: 1778074251714 -- pypi: https://files.pythonhosted.org/packages/8d/c0/fdf9d3ee103ce66a55f0532835ad5e154226c5222423c6636ba049dc42fc/traittypes-0.2.3-py2.py3-none-any.whl - name: traittypes - version: 0.2.3 - sha256: 49016082ce740d6556d9bb4672ee2d899cd14f9365f17cbb79d5d96b47096d4e +- pypi: https://files.pythonhosted.org/packages/db/09/a0ab6a246a7ede89e817d749a941df34f27a74bedf15551da51e86ae105e/pycairo-1.29.0-cp311-cp311-win_amd64.whl + name: pycairo + version: 1.29.0 + sha256: 3391532db03f9601c1cee9ebfa15b7d1db183c6020f3e75c1348cee16825934f + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + name: cfgv + version: 3.5.0 + sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl + name: locket + version: 1.0.0 + sha256: b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl + name: watchdog + version: 6.0.0 + sha256: cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 requires_dist: - - traitlets>=4.2.2 - - numpy ; extra == 'test' - - pandas ; extra == 'test' - - xarray ; extra == 'test' - - pytest ; extra == 'test' -- pypi: https://files.pythonhosted.org/packages/d6/bb/fbdc4e57731efb86b4209ffdd2519520782bf27b3c961beac3e5c20d2b87/trove_classifiers-2026.4.28.13-py3-none-any.whl - name: trove-classifiers - version: 2026.4.28.13 - sha256: 8f4b1eb4e16296b57d612965444f87a83861cc989a0451ac97fe4265ddef03b8 -- pypi: https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl - name: typeguard - version: 4.5.1 - sha256: 44d2bf329d49a244110a090b55f5f91aa82d9a9834ebfd30bcc73651e4a8cc40 + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-py3-none-any.whl + name: xarray + version: 2026.4.0 + sha256: d43751d9fb4a90f9249c30431684f00c41bc874f1edccd862631a40cbc0edf08 + requires_dist: + - numpy>=1.26 + - packaging>=24.2 + - pandas>=2.2 + - scipy>=1.15 ; extra == 'accel' + - bottleneck ; extra == 'accel' + - numbagg>=0.9 ; extra == 'accel' + - numba>=0.62 ; extra == 'accel' + - flox>=0.10 ; extra == 'accel' + - opt-einsum ; extra == 'accel' + - xarray[accel,etc,io,parallel,viz] ; extra == 'complete' + - netcdf4>=1.6.0 ; extra == 'io' + - h5netcdf[h5py]>=1.5.0 ; extra == 'io' + - pydap ; extra == 'io' + - scipy>=1.15 ; extra == 'io' + - zarr>=3.0 ; extra == 'io' + - fsspec ; extra == 'io' + - cftime ; extra == 'io' + - pooch ; extra == 'io' + - sparse>=0.15 ; extra == 'etc' + - dask[complete] ; extra == 'parallel' + - cartopy>=0.24 ; extra == 'viz' + - matplotlib>=3.10 ; extra == 'viz' + - nc-time-axis ; extra == 'viz' + - seaborn ; extra == 'viz' + - pandas-stubs ; extra == 'types' + - scipy-stubs ; extra == 'types' + - types-colorama ; extra == 'types' + - types-decorator ; extra == 'types' + - types-defusedxml ; extra == 'types' + - types-docutils ; extra == 'types' + - types-networkx ; extra == 'types' + - types-openpyxl ; extra == 'types' + - types-pexpect ; extra == 'types' + - types-psutil ; extra == 'types' + - types-pycurl ; extra == 'types' + - types-pygments ; extra == 'types' + - types-python-dateutil ; extra == 'types' + - types-pytz ; extra == 'types' + - types-pyyaml ; extra == 'types' + - types-requests ; extra == 'types' + - types-setuptools ; extra == 'types' + - types-xlrd ; extra == 'types' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/dc/b7/901d837999a9350a7773289f7760cb473d4ba01fdc6ebae0ff2553d269ac/ncrystal_python-4.4.2-py3-none-any.whl + name: ncrystal-python + version: 4.4.2 + sha256: f419318d088fade6bcff1e39e15baf6fe69fcf5306dd681fca1106d1f63a89ce requires_dist: - - importlib-metadata>=3.6 ; python_full_version < '3.10' - - typing-extensions>=4.14.0 + - numpy>=1.22 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl + name: multidict + version: 6.7.1 + sha256: f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855 + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl - name: typer - version: 0.25.1 - sha256: 75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89 +- pypi: https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl + name: email-validator + version: 2.3.0 + sha256: 80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 requires_dist: - - click>=8.2.1 - - shellingham>=1.3.0 - - rich>=13.8.0 - - annotated-doc>=0.0.2 + - dnspython>=2.0.0 + - idna>=2.0.0 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl + name: markdown + version: 3.10.2 + sha256: e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36 + requires_dist: + - coverage ; extra == 'testing' + - pyyaml ; extra == 'testing' + - mkdocs>=1.6 ; extra == 'docs' + - mkdocs-nature>=0.6 ; extra == 'docs' + - mdx-gh-links>=0.2 ; extra == 'docs' + - mkdocstrings[python]>=0.28.3 ; extra == 'docs' + - mkdocs-gen-files ; extra == 'docs' + - mkdocs-section-index ; extra == 'docs' + - mkdocs-literate-nav ; extra == 'docs' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c - md5: edd329d7d3a4ab45dcf905899a7a6115 - depends: - - typing_extensions ==4.15.0 pyhcf101f3_0 - license: PSF-2.0 - license_family: PSF - purls: [] - size: 91383 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a - md5: 53f5409c5cfd6c5a66417d68e3f0a864 - depends: - - python >=3.10 - - typing_extensions >=4.12.0 - - python - license: MIT - license_family: MIT - purls: - - pkg:pypi/typing-inspection?source=compressed-mapping - size: 20935 - timestamp: 1777105465795 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 - md5: 0caa1af407ecff61170c9437a808404d - depends: - - python >=3.10 - - python - license: PSF-2.0 - license_family: PSF - purls: - - pkg:pypi/typing-extensions?source=hash-mapping - size: 51692 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda - sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c - md5: f6d7aa696c67756a650e91e15e88223c - depends: - - python >=3.9 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/typing-utils?source=hash-mapping - size: 15183 - timestamp: 1733331395943 -- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c - md5: ad659d0a2b3e47e38d829aa8cad2d610 - license: LicenseRef-Public-Domain - purls: [] - size: 119135 - timestamp: 1767016325805 -- pypi: https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl - name: tzlocal - version: 5.3.1 - sha256: eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d +- pypi: https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl + name: shellingham + version: 1.5.4 + sha256: 7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: fonttools + version: 4.62.1 + sha256: 6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1 requires_dist: - - tzdata ; sys_platform == 'win32' - - pytest>=4.3 ; extra == 'devenv' - - pytest-mock>=3.3 ; extra == 'devenv' - - pytest-cov ; extra == 'devenv' - - check-manifest ; extra == 'devenv' - - zest-releaser ; extra == 'devenv' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 - md5: 71b24316859acd00bdb8b38f5e2ce328 - constrains: - - vc14_runtime >=14.29.30037 - - vs2015_runtime >=14.29.30037 - license: LicenseRef-MicrosoftWindowsSDK10 - purls: [] - size: 694692 - timestamp: 1756385147981 -- pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - name: uncertainties - version: 3.2.3 - sha256: 313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + name: plopp + version: 26.4.2 + sha256: 5cab99bb0905ce08a1d1d7d82f0f64cee7d594269ec1bd01a8a361bd14ab7bff requires_dist: - - numpy ; extra == 'arrays' + - lazy-loader>=0.4 + - matplotlib>=3.8 + - 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' + - ipywidgets>=8.1.0 ; extra == 'test' + - ipykernel>=6.26,<7 ; extra == 'test' + - mpltoolbox>=24.6.0 ; extra == 'test' + - pandas>=2.2.2 ; extra == 'test' + - plotly>=5.15.0 ; extra == 'test' + - pooch>=1.5 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'test' + - pytest>=8.0 ; extra == 'test' + - pythreejs>=2.4.1 ; extra == 'test' + - 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/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + name: numpy + version: 2.4.4 + sha256: 4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl + name: scipp + version: 26.3.1 + sha256: 37877cf07b4f54f224d5465c265d6a1e591d605d0c23dd350a4b48d95c26ab7b + requires_dist: + - numpy>=2 - pytest ; extra == 'test' - - pytest-codspeed ; extra == 'test' - - pytest-cov ; extra == 'test' - - scipy ; extra == 'test' - - sphinx ; extra == 'doc' - - sphinx-copybutton ; extra == 'doc' - - python-docs-theme ; extra == 'doc' - - uncertainties[arrays,doc,test] ; extra == 'all' + - matplotlib ; extra == 'test' + - beautifulsoup4 ; extra == 'test' + - ipython ; extra == 'test' + - h5py ; extra == 'extra' + - scipy>=1.7.0 ; extra == 'extra' + - graphviz ; extra == 'extra' + - pooch ; extra == 'extra' + - plopp ; extra == 'extra' + - matplotlib ; extra == 'extra' + - scipp[extra] ; extra == 'all' + - ipympl ; extra == 'all' + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - jupyterlab ; extra == 'all' + - jupyterlab-widgets ; extra == 'all' + - jupyter-nbextensions-configurator ; extra == 'all' + - nodejs ; extra == 'all' + - pythreejs ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + name: cycler + version: 0.12.1 + sha256: 85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 + requires_dist: + - ipython ; extra == 'docs' + - matplotlib ; extra == 'docs' + - numpydoc ; extra == 'docs' + - sphinx ; extra == 'docs' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-xdist ; extra == 'tests' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda - sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 - md5: e7cb0f5745e4c5035a460248334af7eb - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/uri-template?source=hash-mapping - size: 23990 - timestamp: 1733323714454 -- pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - name: uritools - version: 6.1.0 - sha256: f1b699052459711f1dd03721ca79341dc345054e6286f47313f8bce777782415 +- pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: pillow + version: 12.2.0 + sha256: e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - check-manifest ; extra == 'tests' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pyroma>=5 ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda - sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a - md5: 9272daa869e03efe68833e3dc7a02130 - depends: - - backports.zstd >=1.0.0 - - brotli-python >=1.2.0 - - h2 >=4,<5 - - pysocks >=1.5.6,<2.0,!=1.5.7 - - python >=3.10 - license: MIT - license_family: MIT - purls: - - pkg:pypi/urllib3?source=hash-mapping - size: 103172 - timestamp: 1767817860341 -- pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl - name: validate-pyproject - version: '0.25' - sha256: f9d05e2686beff82f9ea954f582306b036ced3d3feb258c1110f2c2a495b1981 +- pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl + name: nbmake + version: 1.5.5 + sha256: c6fbe6e48b60cacac14af40b38bf338a3b88f47f085c54ac5b8639ff0babaf4b requires_dist: - - fastjsonschema>=2.16.2,<=3 - - packaging>=24.2 ; extra == 'all' - - trove-classifiers>=2021.10.20 ; extra == 'all' - - tomli>=1.2.1 ; python_full_version < '3.11' and extra == 'all' - - validate-pyproject-schema-store ; extra == 'store' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/a4/39/3a0ae5b0edb66e61bb0e8bc53a503495cba5892297ae21faf6ba0525e681/varname-1.0.0-py3-none-any.whl - name: varname - version: 1.0.0 - sha256: 1125bfe981c3bbbe56988f5cb85fdcd7cad923b153283c2d464aea8b4c833d51 + - ipykernel>=5.4.0 + - nbclient>=0.6.6 + - nbformat>=5.0.4 + - pygments>=2.7.3 + - pytest>=6.1.0 + requires_python: '>=3.8.0' +- pypi: https://files.pythonhosted.org/packages/ed/01/e1da2f721c3a8feec311a16924c42fb28c37445528df0747f47dec5232d5/crysfml-0.6.2-cp313-cp313-win_amd64.whl + name: crysfml + version: 0.6.2 + sha256: b32ddbf4010ce61a535182d58c12c410ed4a0f5de2259e7ef605e7fbcce40be3 requires_dist: - - executing>=2.1 - - typing-extensions>=4.13 ; python_full_version < '3.10' - - asttokens==3.* ; extra == 'all' - - pure-eval==0.* ; extra == 'all' + - numpy + requires_python: '>=3.11,<3.15' +- pypi: https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl + name: prettytable + version: 3.17.0 + sha256: aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287 + requires_dist: + - wcwidth + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-lazy-fixtures ; extra == 'tests' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ee/ab/7d7463cda94f8b68b969ea97aaad679655a0e436efd6a643e528a8de114e/gemmi-0.7.5-cp313-cp313-win_amd64.whl + name: gemmi + version: 0.7.5 + sha256: ad1f72ffa24adbfaf259e11471f6f071a668667f6ca846051f3bfea024fd337d requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda - sha256: 9dc40c2610a6e6727d635c62cced5ef30b7b30123f5ef67d6139e23d21744b3a - md5: 1e610f2416b6acdd231c5f573d754a0f - depends: - - vc14_runtime >=14.44.35208 - track_features: - - vc14 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 19356 - timestamp: 1767320221521 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - sha256: 02732f953292cce179de9b633e74928037fa3741eb5ef91c3f8bae4f761d32a5 - md5: 37eb311485d2d8b2c419449582046a42 - depends: - - ucrt >=10.0.20348.0 - - vcomp14 14.44.35208 h818238b_34 - constrains: - - vs2015_runtime 14.44.35208.* *_34 - license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime - license_family: Proprietary - purls: [] - size: 683233 - timestamp: 1767320219644 -- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - sha256: 878d5d10318b119bd98ed3ed874bd467acbe21996e1d81597a1dbf8030ea0ce6 - md5: 242d9f25d2ae60c76b38a5e42858e51d - depends: - - ucrt >=10.0.20348.0 - constrains: - - vs2015_runtime 14.44.35208.* *_34 - license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime - license_family: Proprietary - purls: [] - size: 115235 - timestamp: 1767320173250 -- pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - name: versioningit - version: 3.3.0 - sha256: 23b1db3c4756cded9bd6b0ddec6643c261e3d0c471707da3e0b230b81ce53e4b +- pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + name: lxml + version: 6.1.0 + sha256: e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2 requires_dist: - - importlib-metadata>=3.6 ; python_full_version < '3.10' - - packaging>=17.1 - - tomli>=1.2,<3.0 ; python_full_version < '3.11' + - cssselect>=0.7 ; extra == 'cssselect' + - html5lib ; extra == 'html5' + - beautifulsoup4 ; extra == 'htmlsoup' + - lxml-html-clean ; extra == 'html-clean' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/a4/ce/3b6fee91c85626eaf769d617f1be9d2e15c1cca027bbdeb2e0d751469355/verspec-0.1.0-py3-none-any.whl - name: verspec - version: 0.1.0 - sha256: 741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31 - requires_dist: - - coverage ; extra == 'test' - - flake8>=3.7 ; extra == 'test' - - mypy ; extra == 'test' - - pretend ; extra == 'test' - - pytest ; extra == 'test' -- pypi: https://files.pythonhosted.org/packages/b1/4f/f71e641e504111a5a74e3a20bc52d01bd86788b22699dd3fee1c63253cf6/virtualenv-21.3.1-py3-none-any.whl - name: virtualenv - version: 21.3.1 - sha256: d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35 +- pypi: https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: aiohttp + version: 3.13.5 + sha256: 3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665 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.2.2 - - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f1/2c/3850985d4c64048dec7b826f8a803e135b52b11b4c81c9cd4326b1ca15ab/ncrystal_core-4.4.2-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: ncrystal-core + version: 4.4.2 + sha256: d0d9c47cd017b7cefc52dde50546d7c151bfdd75d345e42e2b3e74ab5fe83c62 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl - name: watchdog - version: 6.0.0 - sha256: 20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 +- pypi: https://files.pythonhosted.org/packages/f1/5b/e63c877c4c94382b66de5045e08ec8cd960e8a4d22f0d62a4dfb1f9e5ac6/ipydatawidgets-4.3.5-py2.py3-none-any.whl + name: ipydatawidgets + version: 4.3.5 + sha256: d590cdb7c364f2f6ab346f20b9d2dd661d27a834ef7845bc9d7113118f05ec87 requires_dist: - - pyyaml>=3.10 ; extra == 'watchmedo' + - ipywidgets>=7.0.0 + - numpy + - traittypes>=0.2.0 + - sphinx ; extra == 'docs' + - recommonmark ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - pytest>=4 ; extra == 'test' + - pytest-cov ; extra == 'test' + - nbval>=0.9.2 ; extra == 'test' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: propcache + version: 0.4.1 + sha256: d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl - name: watchdog - version: 6.0.0 - sha256: afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c +- pypi: https://files.pythonhosted.org/packages/f2/f2/728f041460f1b9739b85ee23b45fa5a505962ea11fd85bdbe2a02b021373/darkdetect-0.8.0-py3-none-any.whl + name: darkdetect + version: 0.8.0 + sha256: a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85 + requires_dist: + - pyobjc-framework-cocoa ; sys_platform == 'darwin' and extra == 'macos-listener' + requires_python: '>=3.6' +- pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl + name: propcache + version: 0.4.1 + sha256: 364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl + name: pyhanko-certvalidator + version: 0.31.1 + sha256: 0aabe25b536ed555f3b18e5eb2c62ff46adeff9d15e27f0c9d5d81952d956f45 requires_dist: - - pyyaml>=3.10 ; extra == 'watchmedo' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl - name: watchdog - version: 6.0.0 - sha256: cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 + - asn1crypto>=1.5.1 + - oscrypto>=1.1.0 + - cryptography>=48.0.0 + - uritools>=3.0.1 + - requests>=2.31.0 + - aiohttp>=3.9,<3.14 ; extra == 'async-http' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/f4/a4/61adb19f3c74b0dc0e411de4f06ebef564b1f179928f9dffcbd4b378f2ef/jupyter_notebook_parser-0.1.4-py2.py3-none-any.whl + name: jupyter-notebook-parser + version: 0.1.4 + sha256: 27b3b67cf898684e646d569f017cb27046774ad23866cb0bdf51d5f76a46476b + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/f5/57/2a154a69d6642860300bf8eb205d13131104991f2b1065bbb9075ac5c32e/tof-26.3.0-py3-none-any.whl + name: tof + version: 26.3.0 + sha256: e89783a072b05fdb53d9e76fbf919dc8935e75e118fdaf17ca5cc33727ef002b requires_dist: - - pyyaml>=3.10 ; extra == 'watchmedo' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl - name: watchdog - version: 6.0.0 - sha256: a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b + - plopp>=23.10.0 + - pooch>=1.5.0 + - scipp>=25.1.0 + - lazy-loader>=0.3 + - pytest>=8.0 ; extra == 'test' + - scippneutron>=24.12.0 ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipy + version: 1.17.1 + sha256: 581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464 requires_dist: - - pyyaml>=3.10 ; extra == 'watchmedo' + - numpy>=1.26.4,<2.7 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.10.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl + name: propcache + version: 0.4.1 + sha256: 381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e requires_python: '>=3.9' -- 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=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 - depends: - - python >=3.10 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/webcolors?source=hash-mapping - size: 18987 - timestamp: 1761899393153 -- conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 - md5: 2841eb5bfc75ce15e9a0054b98dcd64d - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/webencodings?source=hash-mapping - size: 15496 - timestamp: 1733236131358 -- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - sha256: 42a2b61e393e61cdf75ced1f5f324a64af25f347d16c60b14117393a98656397 - md5: 2f1ed718fcd829c184a6d4f0f2e07409 - depends: - - python >=3.10 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/websocket-client?source=hash-mapping - size: 61391 - timestamp: 1759928175142 -- pypi: https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl - name: widgetsnbextension - version: 4.0.15 - sha256: 8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366 - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f - md5: 46e441ba871f524e2b067929da3051c2 - depends: - - __win - - python >=3.9 - license: LicenseRef-Public-Domain - purls: - - pkg:pypi/win-inet-pton?source=hash-mapping - size: 9555 - timestamp: 1733130678956 -- conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 - sha256: 9df10c5b607dd30e05ba08cbd940009305c75db242476f4e845ea06008b0a283 - md5: 1cee351bf20b830d991dbe0bc8cd7dfe - license: MIT - license_family: MIT - purls: [] - size: 1176306 -- pypi: https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl - name: wsproto - version: 1.3.2 - sha256: 61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584 +- pypi: https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl + name: py + version: 1.11.0 + sha256: 607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl + name: ncrystal + version: 4.4.2 + sha256: e02fa7d743addc3fbea23287737a88b8d01192450fdca51554d3f9032fe4617c requires_dist: - - h11>=0.16.0,<1 + - ncrystal-core==4.4.2 + - ncrystal-python==4.4.2 + - spglib>=2.1.0 ; extra == 'composer' + - ase>=3.23.0 ; extra == 'cif' + - gemmi>=0.6.1 ; extra == 'cif' + - spglib>=2.1.0 ; extra == 'cif' + - endf-parserpy>=0.14.3 ; extra == 'endf' + - matplotlib>=3.6.0 ; extra == 'plot' + - ase>=3.23.0 ; extra == 'all' + - endf-parserpy>=0.14.3 ; extra == 'all' + - gemmi>=0.6.1 ; extra == 'all' + - matplotlib>=3.6.0 ; extra == 'all' + - spglib>=2.1.0 ; extra == 'all' + - pyyaml>=6.0.0 ; extra == 'devel' + - ase>=3.23.0 ; extra == 'devel' + - cppcheck ; extra == 'devel' + - endf-parserpy>=0.14.3 ; extra == 'devel' + - gemmi>=0.6.1 ; extra == 'devel' + - matplotlib>=3.6.0 ; extra == 'devel' + - mpmath>=1.3.0 ; extra == 'devel' + - numpy>=1.22 ; extra == 'devel' + - pybind11>=2.11.0 ; extra == 'devel' + - ruff>=0.8.1 ; extra == 'devel' + - simple-build-system>=1.6.0 ; extra == 'devel' + - spglib>=2.1.0 ; extra == 'devel' + - tomli>=2.0.0 ; python_full_version < '3.11' and extra == 'devel' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl + name: h5py + version: 3.16.0 + sha256: 17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999 + requires_dist: + - numpy>=1.21.2 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/93/ca/d53764f0534ff857239595f090f4cb83b599d226cc326c7de5eb3d802715/xhtml2pdf-0.2.17-py3-none-any.whl - name: xhtml2pdf - version: 0.2.17 - sha256: 61a7ecac829fed518f7dbcb916e9d56bea6e521e02e54644b3d0ca33f0658315 +- pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl + name: pymdown-extensions + version: 10.21.2 + sha256: 5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638 requires_dist: - - arabic-reshaper>=3.0.0 - - html5lib>=1.1 - - pillow>=8.1.1 - - pyhanko>=0.12.1 - - pyhanko-certvalidator>=0.19.5 - - pypdf>=3.1.0 - - python-bidi>=0.5.0 - - reportlab>=4.0.4,<5 - - svglib>=1.2.1 - - reportlab[pycairo]>=4.0.4,<5 ; extra == 'pycairo' - - reportlab[renderpm]>=4.0.4,<5 ; extra == 'renderpm' - - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'test' - - tox ; extra == 'test' - - coverage>=5.3 ; extra == 'test' - - sphinx>=6 ; extra == 'docs' - - sphinx-rtd-theme>=0.5.0 ; extra == 'docs' - - sphinx-reredirects>=0.1.3 ; extra == 'docs' - - build ; extra == 'release' - - twine ; extra == 'release' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/38/8b/7ec325b4e9e78beefc2d025b01ee8a2fde771ef7c957c3bff99b9e1fbffa/xraydb-4.5.8-py3-none-any.whl - name: xraydb - version: 4.5.8 - sha256: 2215baafa6a03d00d0254a94525aafc6493c8c285e4ac4477fbd6271b25e6a51 + - markdown>=3.6 + - pyyaml + - pygments>=2.19.1 ; extra == 'extra' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl + name: ghp-import + version: 2.1.0 + sha256: 8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 requires_dist: - - numpy>=1.19 - - scipy>=1.6 - - sqlalchemy>=2.0.1 - - platformdirs - - build ; extra == 'dev' + - python-dateutil>=2.8.1 - twine ; extra == 'dev' - - sphinx ; extra == 'doc' + - markdown ; extra == 'dev' + - flake8 ; extra == 'dev' + - wheel ; extra == 'dev' +- pypi: https://files.pythonhosted.org/packages/f7/ee/d0348d8dd27ac7f001e9583c004f1b245826b04cc23b5ce6b7867c4a1cc6/refnx-0.1.63-cp311-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: refnx + version: 0.1.63 + sha256: 4f397b0d6e18ac1bb793372ec92c9f4a122383387a706dea456df5a2c90dff5e + requires_dist: + - numpy>=2.0 + - scipy + - orsopy + - ipython ; extra == 'all' + - ipywidgets ; extra == 'all' + - traitlets ; extra == 'all' + - matplotlib ; extra == 'all' + - xlrd ; extra == 'all' + - h5py ; extra == 'all' + - jupyter ; extra == 'all' + - tqdm ; extra == 'all' + - pymc ; extra == 'all' + - pytensor ; extra == 'all' + - attrs ; extra == 'all' + - pandas ; extra == 'all' + - pyparsing ; extra == 'all' + - periodictable ; extra == 'all' + - pyqt6 ; extra == 'all' + - qtpy ; extra == 'all' + - corner ; extra == 'all' + - numba ; extra == 'all' - pytest ; extra == 'test' - - pytest-cov ; extra == 'test' - - coverage ; extra == 'test' - - xraydb[dev,doc,test] ; extra == 'all' + - uncertainties ; extra == 'test' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl + name: toolz + version: 1.1.0 + sha256: 15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8 requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad - md5: a77f85f77be52ff59391544bfe73390a - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - license: MIT - license_family: MIT - purls: [] - size: 85189 - timestamp: 1753484064210 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda - sha256: b03433b13d89f5567e828ea9f1a7d5c5d697bf374c28a4168d71e9464f5dafac - md5: 78a0fe9e9c50d2c381e8ee47e3ea437d - depends: - - __osx >=11.0 - license: MIT - license_family: MIT - purls: [] - size: 83386 - timestamp: 1753484079473 -- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda - sha256: 80ee68c1e7683a35295232ea79bcc87279d31ffeda04a1665efdb43cbd50a309 - md5: 433699cba6602098ae8957a323da2664 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - license: MIT - license_family: MIT - purls: [] - size: 63944 - timestamp: 1753484092156 -- pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: yarl - version: 1.23.0 - sha256: 34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4 +- pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + name: aiosignal + version: 1.4.0 + sha256: 053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e requires_dist: - - idna>=2.0 - - multidict>=4.0 - - propcache>=0.2.1 - requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl - name: yarl - version: 1.23.0 - sha256: baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3 + - frozenlist>=1.1.0 + - typing-extensions>=4.2 ; python_full_version < '3.13' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl + name: mdit-py-plugins + version: 0.5.0 + sha256: 07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f requires_dist: - - idna>=2.0 - - multidict>=4.0 - - propcache>=0.2.1 + - markdown-it-py>=2.0.0,<5.0.0 + - pre-commit ; extra == 'code-style' + - myst-parser ; extra == 'rtd' + - sphinx-book-theme ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/93/22/b85eca6fa2ad9491af48c973e4c8cf6b103a73dbb271fe3346949449fca0/yarl-1.23.0-cp311-cp311-win_amd64.whl - name: yarl - version: 1.23.0 - sha256: bf49a3ae946a87083ef3a34c8f677ae4243f5b824bfc4c69672e72b3d6719d46 +- pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl + name: refl1d + version: 1.0.1 + sha256: 8cc52121790df465e3a659d0fb0298f2501256c6f2d7fca26085927297112eb1 requires_dist: - - idna>=2.0 - - multidict>=4.0 - - propcache>=0.2.1 + - bumps==1.0.4 + - matplotlib + - numba + - numpy + - periodictable + - scipy + - orsopy + - ipython ; extra == 'dev' + - matplotlib ; extra == 'dev' + - nbsphinx ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydantic ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - ruff ; extra == 'dev' + - sphinx<8.2 ; extra == 'dev' + - versioningit ; extra == 'dev' + - wxpython ; extra == 'full' + - ipython ; extra == 'full' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/9a/64/c53487d9f4968045b8afa51aed7ca44f58b2589e772f32745f3744476c82/yarl-1.23.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - name: yarl - version: 1.23.0 - sha256: 99c8a9ed30f4164bc4c14b37a90208836cbf50d4ce2a57c71d0f52c7fb4f7598 - requires_dist: - - idna>=2.0 - - multidict>=4.0 - - propcache>=0.2.1 +- pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + name: uritools + version: 6.1.0 + sha256: f1b699052459711f1dd03721ca79341dc345054e6286f47313f8bce777782415 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl - name: yarl - version: 1.23.0 - sha256: 7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b +- pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl + name: numba + version: 0.65.1 + sha256: 85be74c0d036842699a30058f82fb88fc5ffdc59f7615cab5792ea92914c9b62 requires_dist: - - idna>=2.0 - - multidict>=4.0 - - propcache>=0.2.1 + - llvmlite>=0.47.0.dev0,<0.48 + - numpy>=1.22 + - numpy>=1.22,<2.5 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/b2/0d/71ceabc14c146ba8ee3804ca7b3d42b1664c8440439de5214d366fec7d3a/yarl-1.23.0-cp311-cp311-macosx_11_0_arm64.whl - name: yarl - version: 1.23.0 - sha256: dc52310451fc7c629e13c4e061cbe2dd01684d91f2f8ee2821b083c58bd72432 +- pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl + name: watchdog + version: 6.0.0 + sha256: a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b requires_dist: - - idna>=2.0 - - multidict>=4.0 - - propcache>=0.2.1 - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h41580af_10.conda - sha256: 325d370b28e2b9cc1f765c5b4cdb394c91a5d958fbd15da1a14607a28fee09f6 - md5: 755b096086851e1193f3b10347415d7c - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - krb5 >=1.22.2,<1.23.0a0 - - libsodium >=1.0.21,<1.0.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 311150 - timestamp: 1772476812121 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - sha256: 2705360c72d4db8de34291493379ffd13b09fd594d0af20c9eefa8a3f060d868 - md5: e85dcd3bde2b10081cdcaeae15797506 - depends: - - __osx >=11.0 - - libcxx >=19 - - krb5 >=1.22.2,<1.23.0a0 - - libsodium >=1.0.21,<1.0.22.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 245246 - timestamp: 1772476886668 -- conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - sha256: b8568dfde46edf3455458912ea6ffb760e4456db8230a0cf34ecbc557d3c275f - md5: 1ab0237036bfb14e923d6107473b0021 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - libsodium >=1.0.21,<1.0.22.0a0 - - krb5 >=1.22.2,<1.23.0a0 - license: MPL-2.0 - license_family: MOZILLA - purls: [] - size: 265665 - timestamp: 1772476832995 -- 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=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 - depends: - - __glibc >=2.17,<3.0.a0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 601375 - timestamp: 1764777111296 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - sha256: 9485ba49e8f47d2b597dd399e88f4802e100851b27c21d7525625b0b4025a5d9 - md5: ab136e4c34e97f34fb621d2592a393d8 - depends: - - __osx >=11.0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 433413 - timestamp: 1764777166076 -- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - sha256: 368d8628424966fd8f9c8018326a9c779e06913dd39e646cf331226acc90e5b2 - md5: 053b84beec00b71ea8ff7a4f84b55207 - depends: - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 388453 - timestamp: 1764777142545 + - pyyaml>=3.10 ; extra == 'watchmedo' + requires_python: '>=3.9' diff --git a/pyproject.toml b/pyproject.toml index 681c5562..e9a7c354 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,8 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'easyscience', + 'easyscience @ git+https://github.com/easyscience/corelib.git@develop', + # 'easyscience', 'scipp', 'refnx', 'refl1d>=1.0.0', @@ -68,6 +69,8 @@ dev = [ 'mkdocstrings-python', # MkDocs: Python docstring support 'pyyaml', # YAML parser 'spdx-headers', # SPDX license header validation + 'arviz', # Bayesian analysis and visualization + 'plotly', # Interactive plotting ] [project.urls] diff --git a/src/easyreflectometry/__init__.py b/src/easyreflectometry/__init__.py index 8ff945d0..a18fee06 100644 --- a/src/easyreflectometry/__init__.py +++ b/src/easyreflectometry/__init__.py @@ -5,6 +5,7 @@ from importlib import metadata +from .analysis.bayesian import PosteriorResults from .project import Project try: @@ -14,5 +15,6 @@ __all__ = [ 'Project', + 'PosteriorResults', '__version__', ] diff --git a/src/easyreflectometry/analysis/__init__.py b/src/easyreflectometry/analysis/__init__.py new file mode 100644 index 00000000..9f086835 --- /dev/null +++ b/src/easyreflectometry/analysis/__init__.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2026 EasyReflectometry contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Post-hoc analysis utilities for reflectometry fitting results.""" + +from easyreflectometry.analysis.bayesian import PosteriorResults +from easyreflectometry.analysis.bayesian import credible_intervals +from easyreflectometry.analysis.bayesian import plot_corner +from easyreflectometry.analysis.bayesian import plot_distribution +from easyreflectometry.analysis.bayesian import plot_trace +from easyreflectometry.analysis.bayesian import posterior_predictive_reflectivity +from easyreflectometry.analysis.bayesian import posterior_predictive_sld_profile +from easyreflectometry.analysis.bayesian import posterior_summary + +__all__ = [ + 'PosteriorResults', + 'plot_corner', + 'plot_distribution', + 'plot_trace', + 'posterior_summary', + 'credible_intervals', + 'posterior_predictive_reflectivity', + 'posterior_predictive_sld_profile', +] diff --git a/src/easyreflectometry/analysis/bayesian.py b/src/easyreflectometry/analysis/bayesian.py new file mode 100644 index 00000000..39389a84 --- /dev/null +++ b/src/easyreflectometry/analysis/bayesian.py @@ -0,0 +1,1376 @@ +# SPDX-FileCopyrightText: 2026 EasyReflectometry contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Bayesian posterior analysis for reflectometry fitting results.""" + +from __future__ import annotations + +import hashlib +import json +import warnings +from typing import Any + +import numpy as np +from easyscience.fitting.minimizers.minimizer_base import MINIMIZER_PARAMETER_PREFIX + +try: + import arviz as _arviz + + _HAS_ARVIZ = True +except ImportError: + _HAS_ARVIZ = False + + +def _require_arviz(): + if not _HAS_ARVIZ: + raise ImportError('The ``arviz`` library is required for trace plots and R-hat. Install it with ``pip install arviz``.') + + +def _require_plotly(): + try: + import plotly # noqa: F401 + except ImportError as exc: + raise ImportError( + 'The ``plotly`` library is required for posterior plots. Install it with ``pip install plotly``.' + ) from exc + + +def _wrap_pair_label(name: str, max_len: int = 16) -> str: + """Insert ``
`` line breaks so long parameter labels don't overlap. + + Dotted names (e.g. ``layer1.thickness``) break on dots; plain names + word-wrap on spaces at roughly *max_len* characters per line. + """ + name = name.strip() + if not name: + return name + if '.' in name: + parts = [p.strip() for p in name.split('.') if p.strip()] + if parts: + return '
'.join([*(f'{p}.' for p in parts[:-1]), parts[-1]]) + if len(name) <= max_len: + return name + words = name.split() + if len(words) == 1: + return name + lines: list[str] = [] + current = '' + for word in words: + if current and len(current) + 1 + len(word) > max_len: + lines.append(current) + current = word + else: + current = f'{current} {word}' if current else word + if current: + lines.append(current) + return '
'.join(lines) + + +def _to_arviz_data(draws: np.ndarray, param_names: list[str]): + """Convert posterior draws to an arviz InferenceData object. + + :param draws: Posterior samples, shape ``(n_samples, n_params)`` or + ``(n_chains, n_draws, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names (one per column). + :type param_names: list[str] + :return: arviz InferenceData object. + """ + draws = np.asarray(draws, dtype=np.float64) + if draws.ndim == 2: + draws = draws[np.newaxis, ...] # (1, n_samples, n_params) + + # Build a dict of {param_name: (chain, draw) array} + posterior_dict = {} + for i, name in enumerate(param_names): + posterior_dict[name] = draws[:, :, i] + + return _arviz.from_dict({'posterior': posterior_dict}) + + +class PosteriorResults: + """Container for Bayesian posterior samples with analysis methods. + + :param draws: Posterior samples, shape ``(n_samples, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names (one per column of ``draws``). + :type param_names: list[str] + :param logp: Log-posterior values, shape ``(n_samples,)``, or ``None``. + :type logp: np.ndarray | None + :param sampler_state: Raw sampler state object (e.g. BUMPS ``DreamState``), or ``None``. + :type sampler_state: Any | None + """ + + def __init__( + self, + draws: np.ndarray, + param_names: list[str], + logp: np.ndarray | None = None, + sampler_state: Any | None = None, + ): + self.draws = np.asarray(draws) + self.param_names = list(param_names) + self.logp = np.asarray(logp) if logp is not None else None + self.sampler_state = sampler_state + + def __repr__(self) -> str: + n_samples, n_params = self.draws.shape + return f'PosteriorResults(n_samples={n_samples}, n_params={n_params}, param_names={self.param_names})' + + def summary(self) -> str: + """Return a formatted summary table with mean, sd, and equal-tailed 95% credible interval for each parameter. + + :return: Formatted summary table as a string. + :rtype: str + """ + return posterior_summary(self.draws, self.param_names) + + def corner(self) -> Any: + """Return the parameter-correlation corner plot as a Plotly Figure. + + Requires the ``plotly`` library. + + :return: Plotly Figure. + """ + return plot_corner(self.draws, self.param_names) + + def trace(self, **kwargs) -> None: + """Plot MCMC trace plot. + + Requires the ``arviz`` library. + + :param kwargs: Additional keyword arguments passed to ``arviz.plot_trace``. + """ + plot_trace(self.draws, self.param_names, **kwargs) + + def distribution(self) -> Any: + """Return the per-parameter marginal posterior distributions as a Plotly Figure. + + Each panel overlays the posterior histogram, a smooth KDE marginal, the + 95% credible interval, the median, and the best posterior sample (when + ``logp`` is available). Requires the ``plotly`` library. + + :return: Plotly Figure. + """ + return plot_distribution(self.draws, self.param_names, logp=self.logp, return_figure=True) + + def credible_interval(self, alpha: float = 0.95) -> dict: + """Compute equal-tailed credible intervals for each parameter. + + :param alpha: Credible interval width (e.g. 0.95 for 95%). + :type alpha: float + :return: Dictionary mapping parameter name to ``(lower, upper)``. + :rtype: dict + """ + return credible_intervals(self.draws, self.param_names, alpha=alpha) + + def save(self, path: str) -> None: + """Persist this posterior trace to disk. + + Convenience wrapper around :func:`save_posterior`. + + :param path: File path prefix (see :func:`save_posterior`). + :type path: str + """ + save_posterior(self, path) + + def gelman_rubin(self) -> dict | None: + """Compute the Gelman-Rubin R-hat convergence diagnostic. + + Requires the ``arviz`` library and posterior draws with at least + two chains, i.e. shape ``(n_chains, n_draws, n_params)`` with + ``n_chains >= 2``. R-hat is undefined for a single chain. + + :return: Dictionary mapping parameter name to R-hat value, or ``None`` + if ``arviz`` is not available. + :rtype: dict | None + :raises ValueError: If ``self.draws`` does not contain at least two + chains. + """ + if not _HAS_ARVIZ: + warnings.warn( + 'The ``arviz`` library is required for Gelman-Rubin R-hat. Install it with ``pip install arviz``.', + UserWarning, + ) + return None + if self.draws.ndim < 3 or self.draws.shape[0] < 2: + raise ValueError( + 'Gelman-Rubin R-hat requires posterior draws with at least 2 chains ' + '(shape ``(n_chains, n_draws, n_params)`` with ``n_chains >= 2``).' + ) + data = _to_arviz_data(self.draws, self.param_names) + rhat = _arviz.rhat(data) + return {name: float(rhat[name].values) for name in self.param_names} + + +def posterior_summary(draws: np.ndarray, param_names: list[str]) -> str: + """Return a formatted summary table with mean, sd, and the equal-tailed + 2.5%/97.5% posterior quantiles for each parameter. + + The reported interval is the equal-tailed 95% credible interval; it is + not a highest-density interval (HDI) and the two coincide only for + symmetric unimodal posteriors. + + :param draws: Posterior samples, shape ``(n_samples, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names (one per column). + :type param_names: list[str] + :return: Formatted summary table as a string. + :rtype: str + """ + draws = np.asarray(draws) + lines = [f'{"parameter":<30s} {"mean":>10s} {"sd":>10s} {"q2.5%":>10s} {"q97.5%":>10s}'] + for i, name in enumerate(param_names): + col = draws[:, i] + lo, hi = np.percentile(col, [2.5, 97.5]) + lines.append(f'{name:<30s} {col.mean():>10.4f} {col.std():>10.4f} {lo:>10.4f} {hi:>10.4f}') + return '\n'.join(lines) + + +# Posterior pair-plot palette and styling, mirroring easydiffraction so the +# corner plots match across the two libraries. +_POSTERIOR_PAIR_MIN_SAMPLE_COUNT = 2 +_POSTERIOR_PAIR_COVARIANCE_RANK = 2 +_POSTERIOR_PAIR_SCATTER_MAX_POINTS = 1500 +_POSTERIOR_PAIR_CONTOUR_GRID_SIZE = 80 +_POSTERIOR_PAIR_DENSITY_GRID_SIZE = 256 + +_POSTERIOR_PAIR_MARGINAL_LINE_COLOR = 'rgb(44, 160, 44)' +_POSTERIOR_PAIR_MARGINAL_FILL_COLOR = 'rgba(44, 160, 44, 0.22)' +_POSTERIOR_PAIR_SCATTER_COLOR = 'rgba(140, 140, 140, 0.22)' +_POSTERIOR_HISTOGRAM_FILL_COLOR = 'rgba(120, 120, 120, 0.38)' +_POSTERIOR_HISTOGRAM_LINE_COLOR = 'rgba(120, 120, 120, 0.24)' +_POSTERIOR_INTERVAL_95_FILL_COLOR = 'rgba(214, 39, 40, 0.14)' +_POSTERIOR_MEDIAN_LINE_COLOR = 'rgb(80, 80, 80)' +_POSTERIOR_POINT_ESTIMATE_LINE_COLOR = 'rgb(214, 39, 40)' +_POSTERIOR_CONTOUR_FILL_COLORSCALE = [ + [0.0, 'rgba(224, 233, 255, 0.62)'], + [0.35, 'rgba(183, 203, 255, 0.70)'], + [0.60, 'rgba(138, 169, 252, 0.78)'], + [0.82, 'rgba(96, 131, 242, 0.84)'], + [1.0, 'rgba(58, 86, 224, 0.90)'], +] +_POSTERIOR_NEGATIVE_CONTOUR_FILL_COLORSCALE = [ + [0.0, 'rgba(255, 224, 224, 0.62)'], + [0.35, 'rgba(250, 188, 188, 0.70)'], + [0.60, 'rgba(245, 148, 148, 0.78)'], + [0.82, 'rgba(237, 104, 104, 0.84)'], + [1.0, 'rgba(215, 48, 39, 0.90)'], +] +_POSTERIOR_CONTOUR_LINE_COLORSCALE = [ + [0.0, 'rgba(183, 203, 255, 0.94)'], + [0.35, 'rgba(183, 203, 255, 0.94)'], + [0.35, 'rgba(138, 169, 252, 0.95)'], + [0.60, 'rgba(138, 169, 252, 0.95)'], + [0.60, 'rgba(96, 131, 242, 0.96)'], + [0.82, 'rgba(96, 131, 242, 0.96)'], + [0.82, 'rgba(58, 86, 224, 0.98)'], + [1.0, 'rgba(58, 86, 224, 0.98)'], +] +_POSTERIOR_NEGATIVE_CONTOUR_LINE_COLORSCALE = [ + [0.0, 'rgba(250, 188, 188, 0.94)'], + [0.35, 'rgba(250, 188, 188, 0.94)'], + [0.35, 'rgba(245, 148, 148, 0.95)'], + [0.60, 'rgba(245, 148, 148, 0.95)'], + [0.60, 'rgba(237, 104, 104, 0.96)'], + [0.82, 'rgba(237, 104, 104, 0.96)'], + [0.82, 'rgba(215, 48, 39, 0.98)'], + [1.0, 'rgba(215, 48, 39, 0.98)'], +] + + +def _posterior_axis_bounds(values: np.ndarray) -> tuple[float, float] | None: + """Return padded ``(lower, upper)`` plotting bounds for one posterior axis. + + :param values: Posterior samples for a single parameter. + :type values: np.ndarray + :return: Padded bounds, or ``None`` if there are no finite samples. + :rtype: tuple[float, float] | None + """ + data = np.asarray(values, dtype=float) + data = data[np.isfinite(data)] + if data.size == 0: + return None + data_min = float(np.min(data)) + data_max = float(np.max(data)) + data_range = data_max - data_min + padding = 0.05 * data_range if data_range > 0 else max(abs(data_min), 1.0) * 0.05 + if padding == 0: + padding = 1e-6 + return data_min - padding, data_max + padding + + +def _posterior_density_curve( + values: np.ndarray, + grid_size: int = _POSTERIOR_PAIR_DENSITY_GRID_SIZE, +) -> tuple[np.ndarray, np.ndarray] | None: + """Estimate a 1-D Gaussian-KDE marginal density normalised to unit area. + + :param values: Posterior samples for a single parameter. + :type values: np.ndarray + :param grid_size: Number of grid points at which to evaluate the density. + :type grid_size: int + :return: ``(grid, density)`` arrays, or ``None`` if a smooth density could + not be estimated (e.g. ``scipy`` missing or degenerate samples). + :rtype: tuple[np.ndarray, np.ndarray] | None + """ + try: + from scipy.stats import gaussian_kde + except ImportError: + return None + + data = np.asarray(values, dtype=float) + data = data[np.isfinite(data)] + if data.size < _POSTERIOR_PAIR_MIN_SAMPLE_COUNT: + return None + + bounds = _posterior_axis_bounds(data) + if bounds is None: + return None + grid = np.linspace(bounds[0], bounds[1], num=grid_size) + + if np.allclose(data, data[0]): + bandwidth = max(abs(data[0]) * 0.01, 1e-6) + density = np.exp(-0.5 * ((grid - data[0]) / bandwidth) ** 2) + density /= bandwidth * np.sqrt(2.0 * np.pi) + else: + try: + density = np.asarray(gaussian_kde(data)(grid), dtype=float) + except (np.linalg.LinAlgError, ValueError): + return None + + area = np.trapezoid(density, grid) + if area <= 0: + return None + return grid, density / area + + +def _posterior_density_surface( + x_values: np.ndarray, + y_values: np.ndarray, + grid_size: int = _POSTERIOR_PAIR_CONTOUR_GRID_SIZE, +) -> tuple[np.ndarray, np.ndarray, np.ndarray] | None: + """Estimate a 2-D Gaussian-KDE density surface for one pair panel. + + :param x_values: Posterior samples for the x-axis parameter. + :type x_values: np.ndarray + :param y_values: Posterior samples for the y-axis parameter. + :type y_values: np.ndarray + :param grid_size: Number of grid points per axis. + :type grid_size: int + :return: ``(x_grid, y_grid, density)``, or ``None`` if a smooth surface + could not be estimated (e.g. ``scipy`` missing or degenerate samples). + :rtype: tuple[np.ndarray, np.ndarray, np.ndarray] | None + """ + try: + from scipy.stats import gaussian_kde + except ImportError: + return None + + x_data = np.asarray(x_values, dtype=float) + y_data = np.asarray(y_values, dtype=float) + mask = np.isfinite(x_data) & np.isfinite(y_data) + x_data = x_data[mask] + y_data = y_data[mask] + if x_data.size < _POSTERIOR_PAIR_MIN_SAMPLE_COUNT or y_data.size < _POSTERIOR_PAIR_MIN_SAMPLE_COUNT: + return None + if np.allclose(x_data, x_data[0]) and np.allclose(y_data, y_data[0]): + return None + + pair_data = np.vstack([x_data, y_data]) + covariance = np.cov(pair_data) + if np.linalg.matrix_rank(covariance) < _POSTERIOR_PAIR_COVARIANCE_RANK: + return None + + x_bounds = _posterior_axis_bounds(x_data) + y_bounds = _posterior_axis_bounds(y_data) + if x_bounds is None or y_bounds is None: + return None + x_grid = np.linspace(x_bounds[0], x_bounds[1], num=grid_size) + y_grid = np.linspace(y_bounds[0], y_bounds[1], num=grid_size) + mesh_x, mesh_y = np.meshgrid(x_grid, y_grid) + try: + kde = gaussian_kde(pair_data) + density = np.asarray(kde(np.vstack([mesh_x.ravel(), mesh_y.ravel()])), dtype=float) + except (np.linalg.LinAlgError, ValueError): + return None + density = density.reshape(mesh_x.shape) + if not np.any(np.isfinite(density)): + return None + return x_grid, y_grid, density + + +def _posterior_contour_colorscales( + x_values: np.ndarray, + y_values: np.ndarray, +) -> tuple[list, list]: + """Return sign-aware fill and line contour palettes for one pair panel. + + Negatively correlated parameter pairs use a red palette; everything else + uses blue. + + :param x_values: Posterior samples for the x-axis parameter. + :type x_values: np.ndarray + :param y_values: Posterior samples for the y-axis parameter. + :type y_values: np.ndarray + :return: ``(fill_colorscale, line_colorscale)``. + :rtype: tuple[list, list] + """ + x_data = np.asarray(x_values, dtype=float) + y_data = np.asarray(y_values, dtype=float) + mask = np.isfinite(x_data) & np.isfinite(y_data) + if np.count_nonzero(mask) >= _POSTERIOR_PAIR_MIN_SAMPLE_COUNT: + correlation = float(np.corrcoef(x_data[mask], y_data[mask])[0, 1]) + if np.isfinite(correlation) and correlation < 0: + return _POSTERIOR_NEGATIVE_CONTOUR_FILL_COLORSCALE, _POSTERIOR_NEGATIVE_CONTOUR_LINE_COLORSCALE + return _POSTERIOR_CONTOUR_FILL_COLORSCALE, _POSTERIOR_CONTOUR_LINE_COLORSCALE + + +def _add_corner_marginal( + fig: Any, + go: Any, + *, + values: np.ndarray, + row: int, + col: int, + show_legend: bool, +) -> None: + """Add a diagonal marginal-density panel (smooth KDE, histogram fallback). + + :param fig: The Plotly Figure being built. + :param go: The ``plotly.graph_objects`` module. + :param values: Posterior samples for the diagonal parameter. + :type values: np.ndarray + :param row: 1-based subplot row. + :type row: int + :param col: 1-based subplot column. + :type col: int + :param show_legend: Whether this trace should add the legend entry. + :type show_legend: bool + """ + curve = _posterior_density_curve(values) + if curve is not None: + grid, density = curve + fig.add_trace( + go.Scatter( + x=grid, + y=density, + mode='lines', + line=dict(color=_POSTERIOR_PAIR_MARGINAL_LINE_COLOR, width=1), + fill='tozeroy', + fillcolor=_POSTERIOR_PAIR_MARGINAL_FILL_COLOR, + name='Marginal density', + legendgroup='marginal', + showlegend=show_legend, + hoverinfo='skip', + ), + row=row, + col=col, + ) + return + # scipy unavailable or degenerate samples: fall back to a histogram. + fig.add_trace( + go.Histogram( + x=values, + nbinsx=40, + histnorm='probability density', + marker=dict( + color=_POSTERIOR_PAIR_MARGINAL_FILL_COLOR, + line=dict(color=_POSTERIOR_PAIR_MARGINAL_LINE_COLOR, width=1), + ), + name='Marginal density', + legendgroup='marginal', + showlegend=show_legend, + hoverinfo='skip', + ), + row=row, + col=col, + ) + + +def _corner_contour_traces( + go: Any, + x_values: np.ndarray, + y_values: np.ndarray, +) -> tuple[Any, Any] | None: + """Build filled and line 2-D KDE contour traces for one pair panel. + + :param go: The ``plotly.graph_objects`` module. + :param x_values: Posterior samples for the x-axis parameter. + :type x_values: np.ndarray + :param y_values: Posterior samples for the y-axis parameter. + :type y_values: np.ndarray + :return: ``(fill_trace, line_trace)``, or ``None`` if no smooth surface + could be estimated. + :rtype: tuple[Any, Any] | None + """ + surface = _posterior_density_surface(x_values, y_values) + if surface is None: + return None + x_grid, y_grid, density = surface + + fill_colorscale, line_colorscale = _posterior_contour_colorscales(x_values, y_values) + density_max = float(np.max(density)) + contour_start = density_max * 0.20 + contour_end = density_max * 0.95 + contour_size = density_max * 0.15 + + fill_density = np.array(density, copy=True) + fill_density[fill_density < contour_start] = np.nan + fill_trace = go.Contour( + x=x_grid, + y=y_grid, + z=fill_density, + contours=dict( + coloring='fill', + showlabels=False, + showlines=False, + start=contour_start, + end=contour_end, + size=contour_size, + ), + colorscale=fill_colorscale, + zmin=contour_start, + zmax=contour_end, + connectgaps=False, + hoverinfo='skip', + showscale=False, + zorder=1, + ) + line_trace = go.Contour( + x=x_grid, + y=y_grid, + z=density, + contours=dict( + coloring='lines', + showlabels=False, + start=contour_start, + end=contour_end, + size=contour_size, + ), + colorscale=line_colorscale, + zmin=contour_start, + zmax=contour_end, + line=dict(width=0.9), + hoverinfo='skip', + showscale=False, + zorder=2, + ) + return fill_trace, line_trace + + +def plot_corner(draws: np.ndarray, param_names: list[str]) -> Any: + """Build a parameter-correlation corner plot as a Plotly Figure. + + Smooth Gaussian-KDE marginal densities on the diagonal, a posterior + scatter overlay with filled 2-D KDE contours on the lower triangle, and a + hidden upper triangle. Contours are coloured blue for positively + correlated pairs and red for negatively correlated ones. This mirrors the + posterior pair plot in ``easydiffraction``. Requires ``plotly`` (and + ``scipy`` for the KDE smoothing; without it the diagonal falls back to a + histogram and the contours are omitted). + + :param draws: Posterior samples, shape ``(n_samples, n_params)`` or + ``(n_chains, n_draws, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names (one per column). + :type param_names: list[str] + :return: Plotly Figure. + """ + _require_plotly() + import plotly.graph_objects as go + from plotly.subplots import make_subplots + + draws = np.asarray(draws) + if draws.ndim == 3: + draws = draws.reshape(-1, draws.shape[-1]) + n_params = len(param_names) + + wrapped_labels = [_wrap_pair_label(name) for name in param_names] + + # Full draws drive the smooth KDE surfaces; the scatter overlay is thinned + # so large posteriors stay responsive to render and pan. + n_samples = draws.shape[0] + if n_samples > _POSTERIOR_PAIR_SCATTER_MAX_POINTS: + stride = max(1, n_samples // _POSTERIOR_PAIR_SCATTER_MAX_POINTS) + scatter_draws = draws[::stride] + else: + scatter_draws = draws + + fig = make_subplots( + rows=n_params, + cols=n_params, + horizontal_spacing=0.02, + vertical_spacing=0.02, + ) + + # Track which trace types have already been added to the legend. + legend_shown = {'marginal': False, 'scatter': False, 'contour': False} + + for row in range(n_params): + for col in range(n_params): + r, c = row + 1, col + 1 + if col > row: + fig.update_xaxes(visible=False, row=r, col=c) + fig.update_yaxes(visible=False, row=r, col=c) + continue + if col == row: + _add_corner_marginal( + fig, + go, + values=draws[:, row], + row=r, + col=c, + show_legend=not legend_shown['marginal'], + ) + legend_shown['marginal'] = True + else: + fig.add_trace( + go.Scatter( + x=scatter_draws[:, col], + y=scatter_draws[:, row], + mode='markers', + marker=dict(size=4, color=_POSTERIOR_PAIR_SCATTER_COLOR), + name='Posterior samples', + legendgroup='scatter', + showlegend=not legend_shown['scatter'], + hoverinfo='skip', + zorder=0, + ), + row=r, + col=c, + ) + legend_shown['scatter'] = True + contour_traces = _corner_contour_traces(go, draws[:, col], draws[:, row]) + if contour_traces is not None: + fill_trace, line_trace = contour_traces + fill_trace.name = 'Posterior contours' + fill_trace.legendgroup = 'contour' + fill_trace.showlegend = not legend_shown['contour'] + line_trace.legendgroup = 'contour' + line_trace.showlegend = False + fig.add_trace(fill_trace, row=r, col=c) + fig.add_trace(line_trace, row=r, col=c) + legend_shown['contour'] = True + + # Axis labels: outer edges only (bottom row x-axes, leftmost column y-axes, + # including the top-left diagonal cell so its parameter is identifiable). + for i, label in enumerate(wrapped_labels): + fig.update_xaxes(title_text=label, title_font=dict(size=10), row=n_params, col=i + 1) + fig.update_yaxes(title_text=label, title_font=dict(size=10), row=i + 1, col=1) + # Diagonal y-axes are probability density — hide their tick labels (except the + # top-left, where ticks would be the only cue about the density scale). + for i in range(1, n_params): + fig.update_yaxes(showticklabels=False, row=i + 1, col=i + 1) + + fig.update_layout( + height=max(450, 180 * n_params), + width=max(550, 180 * n_params + 140), + showlegend=True, + legend=dict( + orientation='v', + yanchor='top', + y=1.0, + xanchor='left', + x=1.02, + font=dict(size=11), + itemsizing='constant', + ), + plot_bgcolor='white', + margin=dict(l=80, r=160, t=30, b=60), + ) + fig.update_xaxes(showgrid=False, zeroline=False, ticks='outside') + fig.update_yaxes(showgrid=False, zeroline=False, ticks='outside') + return fig + + +def plot_trace(draws: np.ndarray, param_names: list[str], return_figure: bool = False, **kwargs) -> Any: + """Plot MCMC trace plot. + + When *return_figure* is ``True`` a Plotly ``Figure`` is returned instead of + being displayed inline; the caller is responsible for rendering it. This + requires the ``plotly`` package. + + :param draws: Posterior samples, shape ``(n_chains, n_draws, n_params)`` or + ``(n_draws, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names (one per column). + :type param_names: list[str] + :param return_figure: Return a Plotly Figure instead of rendering inline. + :type return_figure: bool + :param kwargs: Additional keyword arguments passed to ``arviz.plot_trace`` + when *return_figure* is ``False``. + :return: Plotly Figure when *return_figure* is ``True``, otherwise ``None``. + """ + draws = np.asarray(draws) + if draws.ndim == 2: + draws = draws[np.newaxis, ...] # (1, n_draws, n_params) + # draws shape: (n_chains, n_draws, n_params) + + if return_figure: + try: + import plotly.graph_objects as go + from plotly.subplots import make_subplots + except ImportError: + warnings.warn( + 'The ``plotly`` library is required to build the trace figure. Install it with ``pip install plotly``.', + UserWarning, + stacklevel=2, + ) + return None + + n_params = len(param_names) + n_chains = draws.shape[0] + fig = make_subplots( + rows=n_params, + cols=2, + column_widths=[0.6, 0.4], + ) + colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'] + for i, name in enumerate(param_names): + row = i + 1 + show_legend = i == 0 + for c in range(n_chains): + chain_draws = draws[c, :, i] + color = colors[c % len(colors)] + fig.add_trace( + go.Scatter( + y=chain_draws, + mode='lines', + line=dict(color=color, width=1), + name=f'chain {c}', + legendgroup=f'chain {c}', + showlegend=show_legend, + ), + row=row, + col=1, + ) + fig.add_trace( + go.Histogram( + x=chain_draws, + marker_color=color, + opacity=0.6, + name=f'chain {c}', + legendgroup=f'chain {c}', + showlegend=show_legend, + nbinsx=40, + ), + row=row, + col=2, + ) + fig.update_yaxes(title_text=name, title_font=dict(size=10), row=row, col=1) + fig.update_xaxes(title_text=name, title_font=dict(size=10), row=row, col=2) + fig.update_yaxes(title_text='Count', title_font=dict(size=10), row=row, col=2) + fig.update_xaxes(title_text='Draw index', title_font=dict(size=10), row=n_params, col=1) + fig.update_layout( + height=max(300, 200 * n_params), + barmode='overlay', + legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1, font=dict(size=10)), + ) + return fig + + _require_arviz() + idata = _to_arviz_data(draws, param_names) + _arviz.plot_trace(idata, var_names=param_names, **kwargs) + return None + + +def _posterior_marginal_y_range( + values: np.ndarray, + density_curve: tuple[np.ndarray, np.ndarray] | None, +) -> tuple[float, float] | None: + """Return a ``(0, max)`` y-axis range covering histogram and KDE density. + + The range spans the larger of the histogram (probability-density normalised) + and smooth-KDE peaks so credible-interval bands and reference lines, which + are drawn as full-height traces, reach the top of the panel. + + :param values: Posterior samples for a single parameter. + :type values: np.ndarray + :param density_curve: ``(grid, density)`` from :func:`_posterior_density_curve`, or ``None``. + :type density_curve: tuple[np.ndarray, np.ndarray] | None + :return: ``(0.0, padded_max)`` range, or ``None`` if no density is available. + :rtype: tuple[float, float] | None + """ + data = np.asarray(values, dtype=float) + data = data[np.isfinite(data)] + maxima: list[float] = [] + if data.size: + hist, _ = np.histogram(data, bins=40, density=True) + if hist.size: + maxima.append(float(np.max(hist))) + if density_curve is not None: + maxima.append(float(np.max(density_curve[1]))) + if not maxima: + return None + y_max = max(maxima) + if y_max <= 0: + return None + return 0.0, y_max * 1.08 + + +def _posterior_interval_band_trace( + go: Any, + *, + x0: float, + x1: float, + y_range: tuple[float, float], + name: str, + color: str, + show_legend: bool, +) -> Any: + """Return a filled rectangle marking a credible interval. + + :param go: The ``plotly.graph_objects`` module. + :param x0: Lower interval bound. + :type x0: float + :param x1: Upper interval bound. + :type x1: float + :param y_range: Panel y-axis range the band should span. + :type y_range: tuple[float, float] + :param name: Legend/trace name. + :type name: str + :param color: Fill colour. + :type color: str + :param show_legend: Whether this trace adds the legend entry. + :type show_legend: bool + :return: A Plotly Scatter trace. + """ + return go.Scatter( + x=[x0, x1, x1, x0, x0], + y=[y_range[0], y_range[0], y_range[1], y_range[1], y_range[0]], + mode='lines', + fill='toself', + fillcolor=color, + line=dict(color=color, width=0), + name=name, + legendgroup=name, + showlegend=show_legend, + hoverinfo='skip', + ) + + +def _posterior_reference_line_trace( + go: Any, + *, + x_value: float, + y_range: tuple[float, float], + name: str, + color: str, + dash: str, + show_legend: bool, +) -> Any: + """Return a vertical reference line for a posterior marginal panel. + + :param go: The ``plotly.graph_objects`` module. + :param x_value: Parameter value at which to draw the line. + :type x_value: float + :param y_range: Panel y-axis range the line should span. + :type y_range: tuple[float, float] + :param name: Legend/trace name. + :type name: str + :param color: Line colour. + :type color: str + :param dash: Plotly dash style (e.g. ``'dash'``, ``'dot'``). + :type dash: str + :param show_legend: Whether this trace adds the legend entry. + :type show_legend: bool + :return: A Plotly Scatter trace. + """ + return go.Scatter( + x=[x_value, x_value], + y=[y_range[0], y_range[1]], + mode='lines', + line=dict(color=color, width=2, dash=dash), + name=name, + legendgroup=name, + showlegend=show_legend, + hovertemplate=f'{name}: %{{x:.4f}}', + ) + + +def plot_distribution( + draws: np.ndarray, + param_names: list[str], + logp: np.ndarray | None = None, + return_figure: bool = False, + **kwargs, +) -> Any: + """Plot marginal posterior distributions for each parameter. + + Each panel overlays, mirroring ``easydiffraction``'s + ``project.display.posterior.distribution()``: + + * the posterior **histogram** (probability-density normalised), + * a smooth **Gaussian-KDE marginal** density curve (requires ``scipy``; + omitted if unavailable or the samples are degenerate), + * a shaded **95% credible interval** (equal-tailed 2.5/97.5 percentiles), + * a dashed **median** line, and + * a dotted **best posterior sample** line when *logp* is supplied. + + When *return_figure* is ``True`` a Plotly ``Figure`` is returned. + + :param draws: Posterior samples, shape ``(n_samples, n_params)`` or + ``(n_chains, n_draws, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names (one per column). + :type param_names: list[str] + :param logp: Log-posterior values, shape ``(n_samples,)``. When given, the + draw with the largest value marks the best posterior sample. + :type logp: np.ndarray | None + :param return_figure: Return a Plotly Figure instead of rendering inline. + :type return_figure: bool + :param kwargs: Additional keyword arguments (currently unused). + :return: Plotly Figure when *return_figure* is ``True``, otherwise ``None``. + """ + draws = np.asarray(draws) + if draws.ndim == 3: + draws = draws.reshape(-1, draws.shape[-1]) + # draws shape: (n_samples, n_params) + + best_index: int | None = None + if logp is not None: + logp = np.asarray(logp).reshape(-1) + if logp.size == draws.shape[0] and np.any(np.isfinite(logp)): + best_index = int(np.nanargmax(logp)) + + if not return_figure: + return None + + try: + import plotly.graph_objects as go + from plotly.subplots import make_subplots + except ImportError: + warnings.warn( + 'The ``plotly`` library is required to build the distribution figure. Install it with ``pip install plotly``.', + UserWarning, + stacklevel=2, + ) + return None + + n_params = len(param_names) + n_cols = min(3, n_params) + n_rows = (n_params + n_cols - 1) // n_cols + fig = make_subplots(rows=n_rows, cols=n_cols) + + # Each trace type contributes a single shared legend entry. + legend_shown = { + 'histogram': False, + 'marginal': False, + 'interval': False, + 'median': False, + 'best': False, + } + + for i, name in enumerate(param_names): + row = i // n_cols + 1 + col = i % n_cols + 1 + values = draws[:, i] + + density_curve = _posterior_density_curve(values) + y_range = _posterior_marginal_y_range(values, density_curve) + lower, upper = (float(v) for v in np.percentile(values, [2.5, 97.5])) + + # Credible-interval band first so it sits behind the density traces. + if y_range is not None: + fig.add_trace( + _posterior_interval_band_trace( + go, + x0=lower, + x1=upper, + y_range=y_range, + name='95% credible interval', + color=_POSTERIOR_INTERVAL_95_FILL_COLOR, + show_legend=not legend_shown['interval'], + ), + row=row, + col=col, + ) + legend_shown['interval'] = True + + fig.add_trace( + go.Histogram( + x=values, + histnorm='probability density', + marker=dict( + color=_POSTERIOR_HISTOGRAM_FILL_COLOR, + line=dict(color=_POSTERIOR_HISTOGRAM_LINE_COLOR, width=1), + ), + opacity=0.82, + nbinsx=40, + name='Posterior histogram', + legendgroup='histogram', + showlegend=not legend_shown['histogram'], + hovertemplate='sample=%{x:.4f}
density: %{y:.2f}', + ), + row=row, + col=col, + ) + legend_shown['histogram'] = True + + if density_curve is not None: + grid, density = density_curve + fig.add_trace( + go.Scatter( + x=grid, + y=density, + mode='lines', + line=dict(color=_POSTERIOR_PAIR_MARGINAL_LINE_COLOR, width=2), + fill='tozeroy', + fillcolor=_POSTERIOR_PAIR_MARGINAL_FILL_COLOR, + name='Marginal density', + legendgroup='marginal', + showlegend=not legend_shown['marginal'], + hovertemplate=f'{name}: %{{x:.4f}}
density: %{{y:.4f}}', + ), + row=row, + col=col, + ) + legend_shown['marginal'] = True + + if y_range is not None: + fig.add_trace( + _posterior_reference_line_trace( + go, + x_value=float(np.median(values)), + y_range=y_range, + name='Median', + color=_POSTERIOR_MEDIAN_LINE_COLOR, + dash='dash', + show_legend=not legend_shown['median'], + ), + row=row, + col=col, + ) + legend_shown['median'] = True + if best_index is not None: + fig.add_trace( + _posterior_reference_line_trace( + go, + x_value=float(values[best_index]), + y_range=y_range, + name='Best posterior sample', + color=_POSTERIOR_POINT_ESTIMATE_LINE_COLOR, + dash='dot', + show_legend=not legend_shown['best'], + ), + row=row, + col=col, + ) + legend_shown['best'] = True + fig.update_yaxes(range=list(y_range), row=row, col=col) + + if density_curve is not None: + fig.update_xaxes( + range=[float(density_curve[0][0]), float(density_curve[0][-1])], + row=row, + col=col, + ) + fig.update_xaxes(title_text=name, title_font=dict(size=11), row=row, col=col) + fig.update_yaxes(title_text='Probability density', title_font=dict(size=11), row=row, col=col) + + fig.update_layout( + height=max(300, 250 * n_rows), + barmode='overlay', + showlegend=True, + legend=dict(font=dict(size=10)), + plot_bgcolor='white', + ) + fig.update_xaxes(showgrid=False, zeroline=False, ticks='outside') + fig.update_yaxes(showgrid=False, zeroline=False, ticks='outside') + return fig + + +def credible_intervals( + draws: np.ndarray, + param_names: list[str], + alpha: float = 0.95, +) -> dict: + """Compute equal-tailed credible intervals for each parameter. + + :param draws: Posterior samples, shape ``(n_samples, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names (one per column). + :type param_names: list[str] + :param alpha: Credible interval width (e.g. 0.95 for 95%). + :type alpha: float + :return: Dictionary mapping parameter name to ``(lower, upper)``. + :rtype: dict + """ + draws = np.asarray(draws) + tail = (1.0 - alpha) / 2.0 + lo_pct = tail * 100 + hi_pct = (1.0 - tail) * 100 + result = {} + for i, name in enumerate(param_names): + col = draws[:, i] + lo, hi = np.percentile(col, [lo_pct, hi_pct]) + result[name] = (float(lo), float(hi)) + return result + + +def _save_parameter_state(model) -> dict: + """Save the current values and errors of all free parameters in a model. + + :param model: A reflectometry model with ``get_parameters()``. + :return: Dictionary mapping ``unique_name`` to ``(value, error)``. + :rtype: dict + """ + state = {} + for param in model.get_parameters(): + state[param.unique_name] = (param.value, param.error) + return state + + +def _restore_parameter_state(model, state: dict) -> None: + """Restore parameter values and errors from a saved state. + + :param model: A reflectometry model with ``get_parameters()``. + :param state: Dictionary mapping ``unique_name`` to ``(value, error)``. + """ + for param in model.get_parameters(): + if param.unique_name in state: + param.value = state[param.unique_name][0] + param.error = state[param.unique_name][1] + + +def _apply_draw(model, draws: np.ndarray, param_names: list[str], row: int) -> None: + """Apply a single posterior draw to the model parameters. + + Parameter lookup uses ``unique_name``, matching the BUMPS names after + removing the minimizer prefix, which avoids collisions when repeated models + or multi-contrast fits contain similarly named parameters. + + :param model: A reflectometry model with ``get_parameters()``. + :param draws: Posterior samples array. + :param param_names: Parameter names matching the columns of ``draws``. + :param row: Index of the draw to apply. + """ + param_lookup = {p.unique_name: p for p in model.get_parameters()} + for j, name in enumerate(param_names): + if name in param_lookup: + param_lookup[name].value = float(draws[row, j]) + + +def posterior_predictive_reflectivity( + draws: np.ndarray, + param_names: list[str], + model, + q_values: np.ndarray, + n_samples: int = 200, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Compute the posterior predictive reflectivity with credible intervals. + + Parameter values and errors are saved before applying any posterior draw + and restored in a ``finally`` block, so the model is not left mutated. + + :param draws: Posterior samples, shape ``(n_samples_posterior, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names matching the columns of ``draws``. + :type param_names: list[str] + :param model: A reflectometry model with ``interface.fit_func``. + :param q_values: Q values at which to evaluate reflectivity. + :type q_values: np.ndarray + :param n_samples: Number of posterior draws to use (last ``n_samples``). + :type n_samples: int + :return: Tuple of ``(median, lower_95, upper_95)`` reflectivity arrays. + :rtype: tuple[np.ndarray, np.ndarray, np.ndarray] + """ + draws = np.asarray(draws) + q_values = np.asarray(q_values) + + n_total = draws.shape[0] + n_use = min(n_samples, n_total) + sample_indices = range(n_total - n_use, n_total) + + saved_state = _save_parameter_state(model) + try: + reflectivity_samples = [] + for i in sample_indices: + _apply_draw(model, draws, param_names, i) + r_calc = model.interface.fit_func(q_values, model.unique_name) + reflectivity_samples.append(np.asarray(r_calc)) + finally: + _restore_parameter_state(model, saved_state) + + reflectivity_samples = np.array(reflectivity_samples) + median = np.median(reflectivity_samples, axis=0) + lower = np.percentile(reflectivity_samples, 2.5, axis=0) + upper = np.percentile(reflectivity_samples, 97.5, axis=0) + return median, lower, upper + + +def posterior_predictive_sld_profile( + draws: np.ndarray, + param_names: list[str], + model, + n_samples: int = 200, +) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """Compute the posterior predictive SLD profile with credible intervals. + + Parameter values and errors are saved before applying any posterior draw + and restored in a ``finally`` block, so the model is not left mutated. + + :param draws: Posterior samples, shape ``(n_samples_posterior, n_params)``. + :type draws: np.ndarray + :param param_names: Parameter names matching the columns of ``draws``. + :type param_names: list[str] + :param model: A reflectometry model with ``interface.sld_profile``. + :param n_samples: Number of posterior draws to use (last ``n_samples``). + :type n_samples: int + :return: Tuple of ``(z, median, lower_95, upper_95)`` SLD profile arrays. + :rtype: tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray] + """ + draws = np.asarray(draws) + + n_total = draws.shape[0] + n_use = min(n_samples, n_total) + sample_indices = range(n_total - n_use, n_total) + + saved_state = _save_parameter_state(model) + try: + sld_samples = [] + z_shared = None + for i in sample_indices: + _apply_draw(model, draws, param_names, i) + z, sld = model.interface.sld_profile(model.unique_name) + if z_shared is None: + z_shared = np.asarray(z) + sld_samples.append(np.asarray(sld)) + finally: + _restore_parameter_state(model, saved_state) + + sld_samples = np.array(sld_samples) + median = np.median(sld_samples, axis=0) + lower = np.percentile(sld_samples, 2.5, axis=0) + upper = np.percentile(sld_samples, 97.5, axis=0) + return z_shared, median, lower, upper + + +# =================================================================== +# Persistence helpers — save / load a posterior trace to / from disk +# =================================================================== + +_SIDECAR_SCHEMA_VERSION = 1 + + +def _easyreflectometry_version() -> str: + """Return the installed easyreflectometry version string.""" + try: + from importlib.metadata import version as _v + + return _v('easyreflectometry') + except Exception: + return 'unknown' + + +def _data_fingerprint( + x_list: list[np.ndarray], + y_list: list[np.ndarray], + w_list: list[np.ndarray], +) -> str | None: + """Return a SHA-256 hex digest of concatenated (x|y|weights), or None.""" + try: + h = hashlib.sha256() + for arr in list(x_list) + list(y_list) + list(w_list): + h.update(np.ascontiguousarray(arr, dtype=np.float64).tobytes()) + return h.hexdigest() + except Exception: + return None + + +def save_posterior(results: 'PosteriorResults', path: str) -> None: + """Persist a sampling trace to disk using BUMPS' native state files. + + Writes ``-*.mc`` (BUMPS ``save_state`` output) plus a sidecar + ``.params.json`` holding parameter names and metadata so that + :func:`load_posterior` can reconstruct a fully populated + :class:`PosteriorResults` without re-deriving names from the model. + + Note that ``save_state`` writes **multiple** files (one per DREAM + component: chain, point, and stats). The ``path`` argument is a + prefix; the actual files will be ``-chain.mc``, + ``-point.mc``, and ``-stats.mc``. + + :param results: The posterior results to persist. Must have a + non-``None`` ``sampler_state``. + :type results: PosteriorResults + :param path: File path prefix. BUMPS appends its own suffixes. + :type path: str + :raises ValueError: If ``results.sampler_state`` is ``None``. + :raises TypeError: If ``results.sampler_state`` is not a BUMPS + ``MCMCDraw`` object. + """ + from bumps.dream.state import MCMCDraw + from bumps.dream.state import save_state + + if results.sampler_state is None: + raise ValueError( + 'This PosteriorResults has no sampler_state, so the chain ' + 'cannot be saved or resumed. Re-run sample() and wrap the ' + "returned dict's 'state' value into PosteriorResults." + ) + if not isinstance(results.sampler_state, MCMCDraw): + raise TypeError( + f'sampler_state must be a BUMPS MCMCDraw object, got ' + f'{type(results.sampler_state).__name__}. Only BUMPS DREAM ' + 'traces can be persisted with save_posterior.' + ) + + save_state(results.sampler_state, path) + + # Write the sidecar JSON + sidecar = { + 'schema_version': _SIDECAR_SCHEMA_VERSION, + 'param_names': results.param_names, + 'easyreflectometry_version': _easyreflectometry_version(), + } + with open(f'{path}.params.json', 'w') as f: + json.dump(sidecar, f, indent=2) + + +def load_posterior(path: str, skip: int = 0) -> 'PosteriorResults': + """Reload a trace saved by :func:`save_posterior` into a + :class:`PosteriorResults`. + + The returned object's ``sampler_state`` can be fed back into + ``MultiFitter.mcmc_sample(..., resume_state=...)`` to extend the chain. + + :param path: File path prefix used in :func:`save_posterior`. + :type path: str + :param skip: Discard the first ``skip`` saved generations on load, + forwarded to ``bumps.dream.state.load_state(path, skip=skip)``. + Useful for trimming additional burn-in without re-sampling. + :type skip: int + :return: A fully populated :class:`PosteriorResults`. + :rtype: PosteriorResults + """ + from bumps.dream.state import load_state + + state = load_state(path, skip=skip) + _draw = state.draw() + draws = _draw.points + logp = _draw.logp # .logp is on the Draw object, NOT state.logp + + # Restore param_names: prefer the sidecar; fall back to state.labels + param_names: list[str] | None = None + try: + with open(f'{path}.params.json', 'r') as f: + sidecar = json.load(f) + if sidecar.get('schema_version') == _SIDECAR_SCHEMA_VERSION: + param_names = sidecar.get('param_names') + except (FileNotFoundError, json.JSONDecodeError, KeyError): + pass + + if param_names is None: + # Fallback: strip BUMPS 'p' prefix from state.labels + param_names = [ + lbl[len(MINIMIZER_PARAMETER_PREFIX) :] if lbl.startswith(MINIMIZER_PARAMETER_PREFIX) else lbl + for lbl in state.labels + ] + + return PosteriorResults( + draws=draws, + param_names=param_names, + logp=logp, + sampler_state=state, + ) diff --git a/src/easyreflectometry/fitting.py b/src/easyreflectometry/fitting.py index 78f74e68..83a3f6eb 100644 --- a/src/easyreflectometry/fitting.py +++ b/src/easyreflectometry/fitting.py @@ -3,6 +3,8 @@ import warnings +from typing import Any +from typing import Callable import numpy as np import scipp as sc @@ -353,6 +355,87 @@ def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) ] return result + def mcmc_sample( + self, + data: sc.DataGroup, + samples: int = 10000, + burn: int = 2000, + thin: int = 10, + population: int | None = None, + objective: str | None = None, + initializer: str | None = None, + progress_callback: Callable[..., Any] | None = None, + abort_test: Callable[[], bool] | None = None, + ) -> dict: + """Run Bayesian MCMC sampling on reflectometry data using the DREAM sampler. + + Requires that the minimizer is a BUMPS instance (i.e. the minimizer was + switched to ``AvailableMinimizers.Bumps``). + + :param data: DataGroup with reflectivity data. + :param samples: Number of retained DREAM samples requested from BUMPS. + :param burn: Burn-in steps. + :param thin: Thinning interval. + :param population: BUMPS DREAM population count for advanced users. + :param objective: Zero-variance handling strategy. + :param initializer: DREAM population initializer. One of ``'eps'``, + ``'cov'``, ``'lhs'``, or ``'random'``. By default, None (BUMPS + uses ``'eps'``). + — the population already exists in the saved state. + :param progress_callback: Optional callback for progress updates during + sampling. Forwarded to the core MultiFitter. + :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, + and ``'logp'``. + :raises RuntimeError: If the current minimizer is not a BUMPS instance. + """ + minimizer = self.easy_science_multi_fitter.minimizer + if not (hasattr(minimizer, 'package') and minimizer.package == 'bumps'): + raise RuntimeError( + 'Bayesian sampling requires a BUMPS minimizer. ' + 'Use ``fitter.switch_minimizer(AvailableMinimizers.Bumps)`` first.' + ) + + obj = _validate_objective(objective) if objective is not None else self._objective + + refl_nums = [k[3:] for k in data['coords'].keys() if k.startswith('Qz_')] + x = [] + y = [] + dy = [] + + # Process each reflectivity dataset + for i in refl_nums: + x_vals = data['coords'][f'Qz_{i}'].values + y_vals = data['data'][f'R_{i}'].values + variances = data['data'][f'R_{i}'].variances + + x_out, y_eff, weights, stats = _prepare_fit_arrays(x_vals, y_vals, variances, obj) + + if stats['masked'] > 0: + warnings.warn( + f'Masked {stats["masked"]} data point(s) in reflectivity {i} due to zero variance during sampling.', + UserWarning, + ) + x.append(x_out) + y.append(y_eff) + dy.append(weights) + + # Delegate the actual BUMPS/DREAM sampling to the core MultiFitter + sampler_kwargs = {} + if initializer is not None: + sampler_kwargs['init'] = initializer + return self.easy_science_multi_fitter.mcmc_sample( + x=x, + y=y, + weights=dy, + samples=samples, + burn=burn, + thin=thin, + population=population, + sampler_kwargs=sampler_kwargs or None, + progress_callback=progress_callback, + abort_test=abort_test, + ) + @property def chi2(self) -> float | None: """Total chi-squared across all fitted datasets, or None if no fit has been performed.""" diff --git a/tests/_static/amor_reduced_iofq.ort b/tests/_static/amor_reduced_iofq.ort new file mode 100644 index 00000000..534097d2 --- /dev/null +++ b/tests/_static/amor_reduced_iofq.ort @@ -0,0 +1,236 @@ +# # ORSO reflectivity data file | 1.1 standard | YAML encoding | https://www.reflectometry.org/ +# data_source: +# owner: +# name: A. Luchini +# affiliation: null +# contact: '' +# experiment: +# title: ESS solid-liquid cell test +# instrument: Amor +# start_date: 2024-09-12T03:32:57 +# probe: neutron +# facility: SINQ +# sample: +# name: ESS004 Si|dmpc+ZipAdiscs|D2O +# model: +# stack: Si | SiO2 | pc_head | d54dm_tail | d54dm_tail | pc_head | D2O +# measurement: +# instrument_settings: +# incident_angle: {min: 0.006719518471565493, max: 0.013002693060859875, unit: rad} +# wavelength: {min: 3.0031411323142234, max: 12.499958254247156, unit: angstrom} +# polarization: null +# data_files: +# - file: amor2024n004079.hdf +# - file: amor2024n004080.hdf +# - file: amor2024n004081.hdf +# additional_files: +# - file: amor2024n004152.hdf +# comment: supermirror +# reduction: +# software: {name: ess.reflectometry, version: 0.1.dev1+g180c20f, platform: Linux} +# timestamp: 2025-06-16T03:13:22.094956+00:00 +# creator: +# name: Max Mustermann +# affiliation: European Spallation Source ERIC +# contact: max.mustermann@ess.eu +# corrections: +# - chopper ToF correction +# - footprint correction +# - supermirror calibration +# data_set: 0 +# columns: +# - {name: Qz, unit: 1/angstrom, physical_quantity: wavevector transfer} +# - {name: R, physical_quantity: reflectivity} +# - {name: sR, physical_quantity: standard deviation of reflectivity} +# - {name: sQz, unit: 1/angstrom, physical_quantity: standard deviation of wavevector +# transfer resolution} +# # Qz (1/angstrom) R sR sQz (1/angstrom) +1.0044995237394449e-02 9.8603449665594300e-01 7.8179479714162482e-03 4.1363725159788978e-04 +1.0135390626460987e-02 9.8245104333478350e-01 7.5570347164487421e-03 4.1670106126586831e-04 +1.0226599487925606e-02 9.7696417593495455e-01 7.3448109829999759e-03 4.1828901426624527e-04 +1.0318629142265039e-02 9.8564334802005227e-01 7.3906865714743432e-03 4.1962414384512657e-04 +1.0411486975833342e-02 9.5169127761079275e-01 6.9599717030842763e-03 4.2310566259624898e-04 +1.0505180441454711e-02 9.7407382190188407e-01 6.8907576938003132e-03 4.2645004390068738e-04 +1.0599717059021668e-02 9.8426912609602146e-01 6.7562375631018429e-03 4.2928423579183424e-04 +1.0695104416098604e-02 9.7450248676141815e-01 6.5034923168526652e-03 4.3253452179161566e-04 +1.0791350168530762e-02 9.8118888188357278e-01 6.3716833404347311e-03 4.3480071560078745e-04 +1.0888462041058696e-02 9.8887990521784652e-01 6.2638375299793724e-03 4.3927220128707435e-04 +1.0986447827938273e-02 1.0106569599007325e+00 6.2384780198697382e-03 4.4223603304064865e-04 +1.1085315393566239e-02 1.0015005572426214e+00 6.0240780687788762e-03 4.4527232322051240e-04 +1.1185072673111411e-02 1.0177740726436633e+00 5.9882664258192365e-03 4.4820858865534128e-04 +1.1285727673151575e-02 9.8921638764095243e-01 5.6963317802609156e-03 4.5265854416738833e-04 +1.1387288472316090e-02 1.0201869446242222e+00 5.7196396022441455e-03 4.5581306060731361e-04 +1.1489763221934280e-02 1.0008249942362757e+00 5.4809677965727164e-03 4.5871481927092051e-04 +1.1593160146689669e-02 1.0025584609428606e+00 5.4013279001621436e-03 4.6292087220993082e-04 +1.1697487545280105e-02 1.0263729247237372e+00 5.3895056224825362e-03 4.6600726482663279e-04 +1.1802753791083805e-02 1.0193693802271575e+00 5.2267157416687896e-03 4.6970776586345347e-04 +1.1908967332831416e-02 1.0103354333924730e+00 5.0871137990220350e-03 4.7319692527195552e-04 +1.2016136695284119e-02 1.0094491046259855e+00 4.9950652800967200e-03 4.7648929966986469e-04 +1.2124270479917813e-02 1.0409814363689365e+00 5.0293613302694624e-03 4.7972371645923123e-04 +1.2233377365613496e-02 1.0223059605490794e+00 4.8607306388079325e-03 4.8407536387068818e-04 +1.2343466109353831e-02 1.0252532931453100e+00 4.7993351510761256e-03 4.8695863655647632e-04 +1.2454545546925976e-02 1.0128499829761819e+00 4.6788897952293471e-03 4.9180902124788242e-04 +1.2566624593630759e-02 9.9854962639967948e-01 4.5570537317499698e-03 4.9464614477104059e-04 +1.2679712244998228e-02 1.0054021277720633e+00 4.4881193266977145e-03 4.9880191982438712e-04 +1.2793817577509629e-02 1.0093717851660171e+00 4.4560096498985390e-03 5.0251488214466983e-04 +1.2908949749325898e-02 1.0010082832213376e+00 4.3674046766402144e-03 5.0611872242520696e-04 +1.3025118001022684e-02 9.9997502865166965e-01 4.3246987974745249e-03 5.1087145503655442e-04 +1.3142331656332034e-02 1.0100273480052027e+00 4.2936439281344493e-03 5.1496622270002569e-04 +1.3260600122890685e-02 9.9862071869848812e-01 4.2029742286579241e-03 5.1907625237436288e-04 +1.3379932892995151e-02 1.0164823930731817e+00 4.2357430748032766e-03 5.2294295070626966e-04 +1.3500339544363577e-02 9.7996155603147039e-01 4.0449708337456014e-03 5.2793717559712617e-04 +1.3621829740904442e-02 9.9247214969196795e-01 4.0645114477996080e-03 5.3257311502360278e-04 +1.3744413233492195e-02 1.0037016435356869e+00 4.0682004028257997e-03 5.3633920455711120e-04 +1.3868099860749876e-02 9.8685070242340178e-01 3.9754464443643002e-03 5.4020499389089403e-04 +1.3992899549838754e-02 9.7696514386858824e-01 3.9134752355823154e-03 5.4470166910886920e-04 +1.4118822317255090e-02 9.7178241076301930e-01 3.8866934076358353e-03 5.4976238836404348e-04 +1.4245878269634081e-02 9.2250010832578311e-01 3.7445142123583479e-03 5.5305864996756332e-04 +1.4374077604561006e-02 8.2214390672146609e-01 3.4677481882741967e-03 5.5843124972314361e-04 +1.4503430611389697e-02 7.1353253457907029e-01 3.2166390980214502e-03 5.6155042910128778e-04 +1.4633947672068381e-02 6.0030958518931332e-01 2.9309926173895426e-03 5.6437947346971672e-04 +1.4765639261972916e-02 4.9166627882772374e-01 2.6392357491950885e-03 5.6950953682093216e-04 +1.4898515950747572e-02 3.9563596998583606e-01 2.3544433153957511e-03 5.7345285137804425e-04 +1.5032588403153347e-02 3.3008462019887669e-01 2.1288969526833288e-03 5.7872465889241900e-04 +1.5167867379923934e-02 2.8348628624360400e-01 1.9645569907311548e-03 5.8367885330035823e-04 +1.5304363738629378e-02 2.4971863206251596e-01 1.8334842472432855e-03 5.8770342136830621e-04 +1.5442088434547505e-02 2.2246216998804652e-01 1.7261069959473119e-03 5.9304201275462809e-04 +1.5581052521543216e-02 1.9665563555021481e-01 1.6004802471234396e-03 5.9843253042245518e-04 +1.5721267152955666e-02 1.7930778808508194e-01 1.5280211872099028e-03 6.0297178899375997e-04 +1.5862743582493424e-02 1.6235463616506585e-01 1.4414618539511184e-03 6.0854894037747011e-04 +1.6005493165137739e-02 1.4508570827061523e-01 1.3449372866973191e-03 6.1344256859332798e-04 +1.6149527358053868e-02 1.3326095171286856e-01 1.2771658081253238e-03 6.1789422621403027e-04 +1.6294857721510625e-02 1.2721780987752765e-01 1.2567160206851790e-03 6.2282221685751999e-04 +1.6441495919808258e-02 1.1360640589351807e-01 1.1729308180692003e-03 6.2841066232835951e-04 +1.6589453722214591e-02 1.0356366919550507e-01 1.1152902778779911e-03 6.3321849045516547e-04 +1.6738743003909644e-02 9.6586762699090237e-02 1.0726325643774977e-03 6.3892800485394416e-04 +1.6889375746938759e-02 8.9077992838027684e-02 1.0185548898229684e-03 6.4389516784577148e-04 +1.7041364041174276e-02 8.2719027886160049e-02 9.8358774221977280e-04 6.4844692107349829e-04 +1.7194720085285855e-02 7.6563380615004603e-02 9.4034688430919878e-04 6.5481558273970013e-04 +1.7349456187719568e-02 7.2490220764900221e-02 9.1415942677435956e-04 6.5926814059131156e-04 +1.7505584767685781e-02 6.4251252027250003e-02 8.4532581653434444e-04 6.6511620008951754e-04 +1.7663118356155916e-02 6.1368551284949335e-02 8.3045196505704150e-04 6.7038946412950238e-04 +1.7822069596868211e-02 5.5794193605098193e-02 7.9049687607934710e-04 6.7542025368706586e-04 +1.7982451247342511e-02 5.3244900196045324e-02 7.6950986679186933e-04 6.8159065116678449e-04 +1.8144276179904172e-02 4.9895039079214679e-02 7.4183354352460121e-04 6.8673563730519630e-04 +1.8307557382717225e-02 4.6369745695198222e-02 7.0676877881829467e-04 6.9215160152456118e-04 +1.8472307960826800e-02 4.2676736819516385e-02 6.7850307559999860e-04 6.9707095735638193e-04 +1.8638541137210956e-02 4.0374548477077950e-02 6.6411546231271486e-04 7.0383181466090615e-04 +1.8806270253841958e-02 3.6999978648876108e-02 6.3332951956318324e-04 7.0895990372178978e-04 +1.8975508772757121e-02 3.4568332978177671e-02 6.0650056249067518e-04 7.1427816321714709e-04 +1.9146270277139256e-02 3.4072934265176318e-02 6.0590467866145038e-04 7.1957920659550430e-04 +1.9318568472406893e-02 3.0046042729325925e-02 5.6472505072440728e-04 7.2611474421825758e-04 +1.9492417187314270e-02 2.8365919147004797e-02 5.4500253028234424e-04 7.3184171000717979e-04 +1.9667830375061243e-02 2.6058834561471629e-02 5.2179425869031267e-04 7.3775340044820266e-04 +1.9844822114413171e-02 2.4746345854375979e-02 5.1107964466043712e-04 7.4155194956120908e-04 +2.0023406610830900e-02 2.3851195066905306e-02 4.9916964513846960e-04 7.4830622458566356e-04 +2.0203598197610889e-02 2.1253241483291857e-02 4.6980003523848648e-04 7.5524727677442024e-04 +2.0385411337035607e-02 2.0999497560799319e-02 4.6455494741044496e-04 7.6137631518134383e-04 +2.0568860621534294e-02 1.9438222062193970e-02 4.5148435621545892e-04 7.6715006979192818e-04 +2.0753960774854155e-02 1.7911668785073356e-02 4.3347108302645740e-04 7.7250543628190973e-04 +2.0940726653242085e-02 1.7063681872190845e-02 4.2249940053998666e-04 7.7959601825903526e-04 +2.1129173246637063e-02 1.5751821495561726e-02 4.0609429536334887e-04 7.8544440633751956e-04 +2.1319315679873243e-02 1.5256503430763645e-02 4.0096295989391071e-04 7.9077472093173310e-04 +2.1511169213893868e-02 1.5247551172600692e-02 3.9817006524973524e-04 7.9725169368577713e-04 +2.1704749246976133e-02 1.4738797155940621e-02 3.9569829441703268e-04 8.0363974559300474e-04 +2.1900071315967076e-02 1.2265720162206365e-02 3.6170484268039949e-04 8.1043237181891976e-04 +2.2097151097530542e-02 1.1435145048888439e-02 3.4884949178788980e-04 8.1613315421321807e-04 +2.2296004409405420e-02 1.1758574408129640e-02 3.5407895511419332e-04 8.2250218837096848e-04 +2.2496647211675196e-02 1.1148262991366597e-02 3.4624925624656392e-04 8.2946886150455192e-04 +2.2699095608048902e-02 1.1134293056936589e-02 3.4897470337361412e-04 8.3654975749666795e-04 +2.2903365847153599e-02 8.9691322016261406e-03 3.0681202709152125e-04 8.4252256109452312e-04 +2.3109474323838523e-02 9.4329068912666313e-03 3.1750543304164013e-04 8.4935410242671769e-04 +2.3317437580490936e-02 8.2774928771431680e-03 2.9965588979169108e-04 8.5682822910394178e-04 +2.3527272308363826e-02 7.8557806942254529e-03 2.9447717006224908e-04 8.6341605251930736e-04 +2.3738995348915562e-02 8.0295695579126886e-03 3.0015229767897395e-04 8.6942632367731127e-04 +2.3952623695161605e-02 6.5062795212043603e-03 2.6699673709128122e-04 8.7638665668172511e-04 +2.4168174493038350e-02 6.5390651424043775e-03 2.7155371818985959e-04 8.8312396312905305e-04 +2.4385665042779302e-02 6.4617205382751352e-03 2.7014672755448684e-04 8.8970775253373595e-04 +2.4605112800303586e-02 6.1242708291963897e-03 2.6232612988914415e-04 8.9720927091643278e-04 +2.4826535378616954e-02 5.6793862370787991e-03 2.5669323342458771e-04 9.0347421490645648e-04 +2.5049950549225465e-02 5.1137524674573992e-03 2.4744685919525034e-04 9.1013595608878698e-04 +2.5275376243561798e-02 5.0715281507510640e-03 2.4661133778676296e-04 9.1864323525257569e-04 +2.5502830554424420e-02 5.0378112186348443e-03 2.4704513388133591e-04 9.2548657511316824e-04 +2.5732331737429767e-02 4.5523100036128760e-03 2.3919340850292169e-04 9.3173438829395562e-04 +2.5963898212477448e-02 4.0075887730517063e-03 2.2259605356545533e-04 9.4002091588991140e-04 +2.6197548565228601e-02 4.3538688398406869e-03 2.3396091018105050e-04 9.4666848305364950e-04 +2.6433301548597618e-02 4.5772645541576765e-03 2.4115707990622463e-04 9.5422910073401305e-04 +2.6671176084257259e-02 3.7595255345007038e-03 2.1931146799267310e-04 9.6341084984366619e-04 +2.6911191264157329e-02 3.4687751333548331e-03 2.1130706788376251e-04 9.7025365591636725e-04 +2.7153366352056972e-02 3.4280294251800374e-03 2.1117047636182585e-04 9.7603991937129345e-04 +2.7397720785070828e-02 3.0988098018301588e-03 2.0353253305151864e-04 9.8405212963986570e-04 +2.7644274175229053e-02 3.0631097653504601e-03 2.0025279738740751e-04 9.9071870063870832e-04 +2.7893046311051387e-02 2.6189507808044949e-03 1.8972012884168663e-04 9.9848441599317676e-04 +2.8144057159135413e-02 2.7670387551085546e-03 1.9431921443520219e-04 1.0072117386581893e-03 +2.8397326865759069e-02 2.5745698827701236e-03 1.8831868298433725e-04 1.0106453741284542e-03 +2.8652875758497592e-02 2.7905681789483642e-03 1.9735568291361536e-04 1.0206108783355101e-03 +2.8910724347855053e-02 2.2579498616464998e-03 1.8144168545649193e-04 1.0247286452242205e-03 +2.9170893328910506e-02 2.2718397646608662e-03 1.8507254277329277e-04 1.0284513133486230e-03 +2.9433403582979002e-02 2.0913335375318648e-03 1.7730493232883838e-04 1.0347765664821713e-03 +2.9698276179287553e-02 2.0037579736302677e-03 1.7397641847780328e-04 1.0407227158837906e-03 +2.9965532376666153e-02 2.0053082414414943e-03 1.8148425060825760e-04 1.0471416121277033e-03 +3.0235193625253991e-02 1.8892278671415810e-03 1.7691505421149438e-04 1.0538028705339444e-03 +3.0507281568221079e-02 1.6455571663803511e-03 1.6438306782146591e-04 1.0586732890518317e-03 +3.0781818043505368e-02 2.0152345002365500e-03 1.8668060953165212e-04 1.0651681484471396e-03 +3.1058825085565428e-02 1.4399014212171631e-03 1.6141275114956683e-04 1.0740831689914773e-03 +3.1338324927148969e-02 1.7036160263742333e-03 1.7395184489677181e-04 1.0788999220164093e-03 +3.1620340001077260e-02 1.1217298551618442e-03 1.4542794260526752e-04 1.0852190174050710e-03 +3.1904892942045598e-02 1.6760876765263965e-03 1.8329924377565878e-04 1.0936302747601016e-03 +3.2192006588439973e-02 1.4541359774942839e-03 1.7112578883105698e-04 1.1006572678515826e-03 +3.2481703984170079e-02 1.2875872140922125e-03 1.5948329784556213e-04 1.1074818246421499e-03 +3.2774008380518888e-02 1.0770163414102337e-03 1.4904188037172727e-04 1.1115157833241759e-03 +3.3068943238008706e-02 1.0239128298757724e-03 1.4722523545267687e-04 1.1210080748372785e-03 +3.3366532228284257e-02 1.3048175654721734e-03 1.7133632525039643e-04 1.1281079743235417e-03 +3.3666799236012505e-02 7.8081362277807726e-04 1.3505100592578191e-04 1.1355989313283469e-03 +3.3969768360799638e-02 8.8962543191568346e-04 1.4347746193817939e-04 1.1448622805880508e-03 +3.4275463919125379e-02 5.8492135802313194e-04 1.1805992819084637e-04 1.1518871164758397e-03 +3.4583910446294566e-02 1.0037626448930780e-03 1.6184361791870929e-04 1.1590405080415585e-03 +3.4895132698406453e-02 9.7543464716695215e-04 1.5700708673043159e-04 1.1668900430769433e-03 +3.5209155654341573e-02 8.2897549415443853e-04 1.4987388458057805e-04 1.1754731914395710e-03 +3.5526004517766607e-02 5.7239932166676844e-04 1.2530670507033206e-04 1.1835062045421057e-03 +3.5845704719157254e-02 6.0909326959586723e-04 1.3776867520875599e-04 1.1899729621591166e-03 +3.6168281917839239e-02 8.8693445320398674e-04 1.6875462952929237e-04 1.2003910698861150e-03 +3.6493762004047754e-02 6.2866063571478806e-04 1.3770801746981355e-04 1.2069214028710526e-03 +3.6822171101005483e-02 7.2420676686636156e-04 1.5522563136825894e-04 1.2167722868909683e-03 +3.7153535567019239e-02 6.0486200606272840e-04 1.4335868693002909e-04 1.2240064922834871e-03 +3.7487881997595465e-02 4.9446798541633616e-04 1.3838616735761618e-04 1.2336892832317771e-03 +3.7825237227574859e-02 5.8215581368609181e-04 1.4606820653814031e-04 1.2427557293899095e-03 +3.8165628333286086e-02 4.8799461496087536e-04 1.4205352618575168e-04 1.2515238172612596e-03 +3.8509082634719000e-02 6.7630231365165602e-04 1.6475492676418794e-04 1.2596638429171289e-03 +3.8855627697717332e-02 3.8561873170820743e-04 1.2952940536673275e-04 1.2691388444112553e-03 +3.9205291336191161e-02 4.1438409638717576e-04 1.3132903286872680e-04 1.2796078996929498e-03 +3.9558101614349250e-02 3.1830759029369065e-04 1.2110370479995612e-04 1.2920115664439001e-03 +3.9914086848951529e-02 5.6771195393483416e-04 1.6471361196844844e-04 1.3001318282913159e-03 +4.0273275611581782e-02 2.1375692518747785e-04 1.0705467399039935e-04 1.3082872057334817e-03 +4.0635696730940848e-02 4.5930344831629293e-04 1.5326244166342880e-04 1.3154555503861233e-03 +4.1001379295160359e-02 4.2122812388616657e-04 1.4899195442700177e-04 1.3261958685364048e-03 +4.1370352654137479e-02 5.0335754311600276e-04 1.6788562790991915e-04 1.3332333052413590e-03 +4.1742646421890482e-02 2.3558363811391621e-04 1.1804725653138095e-04 1.3435013294740446e-03 +4.2118290478935588e-02 6.0136228364515883e-04 2.0124632978611175e-04 1.3516355545853918e-03 +4.2497314974685209e-02 3.4973579458410191e-04 1.5666145956338512e-04 1.3607785943180531e-03 +4.2879750329867759e-02 3.8041576943784828e-04 1.7040036063109117e-04 1.3704800464320716e-03 +4.3265627238969193e-02 1.4956829106560899e-04 1.0577627788060906e-04 1.3811467445739348e-03 +4.3654976672696626e-02 5.2457236348972601e-04 2.1438329194891435e-04 1.3893380463826904e-03 +4.4047829880464046e-02 3.5772420454280963e-04 1.7907266100853451e-04 1.3986637127843060e-03 +4.4444218392900400e-02 1.9663401121448377e-04 1.3931442465193813e-04 1.4087204853440356e-03 +4.4844174024380276e-02 6.3375339418640501e-04 2.5910150759841130e-04 1.4171719936303924e-03 +4.5247728875577285e-02 1.9911713936503233e-04 1.4079740927647961e-04 1.4297107977552753e-03 +4.5654915336040547e-02 3.5358306852108451e-04 2.0460404177313448e-04 1.4388379325034240e-03 +4.6065766086794288e-02 3.5432656021929464e-04 2.0464934542952718e-04 1.4487444072325503e-03 +4.6480314102960781e-02 2.8770257297080278e-04 2.0345856667102854e-04 1.4587365148404951e-03 +4.6898592656407043e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.4689492392730790e-03 +4.7320635318415155e-02 1.7033604000696743e-04 1.7033604000696740e-04 1.4785079554828448e-03 +4.7746475962376814e-02 5.6604272028629654e-04 3.2689578616413662e-04 1.4892584671520415e-03 +4.8176148766511939e-02 1.9739099846253179e-04 1.9739099846253182e-04 1.4994645342843837e-03 +4.8609688216611859e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.5106514637331777e-03 +4.9047129108807180e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.5225005846333374e-03 +4.9488506552360575e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.5342463017077742e-03 +4.9933855972484620e-02 4.0777098120311518e-04 4.0777098120311508e-04 1.5465102660743115e-03 +5.0383213113185062e-02 9.6712777624285962e-04 6.8386581951956297e-04 1.5578312155554435e-03 +5.0836614040129652e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.5687794057255237e-03 +5.1294095143542787e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.5766818520382530e-03 +5.1755693141126241e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.5865994314325019e-03 +5.2221445081006082e-02 1.6425834058987397e-03 1.6425834058987399e-03 1.5961999224356393e-03 +5.2691388344706297e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.6063526892327405e-03 +5.3165560650148949e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.6164111412755033e-03 +5.3644000054681451e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.6284421571881489e-03 +5.4126744958131107e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.6364407191857197e-03 diff --git a/tests/test_bayesian.py b/tests/test_bayesian.py new file mode 100644 index 00000000..15404645 --- /dev/null +++ b/tests/test_bayesian.py @@ -0,0 +1,476 @@ +# SPDX-FileCopyrightText: 2026 EasyReflectometry contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Tests for the Bayesian analysis module.""" + +import numpy as np +import pytest + + +@pytest.fixture +def sample_draws(): + """Generate synthetic posterior draws for testing.""" + rng = np.random.default_rng(42) + n_samples = 100 + # Two parameters: 'thickness' and 'sld' + thickness = rng.normal(loc=250, scale=10, size=n_samples) + sld = rng.normal(loc=2.0, scale=0.2, size=n_samples) + draws = np.column_stack([thickness, sld]) + param_names = ['Film_thickness', 'Film_sld'] + return draws, param_names + + +class TestPosteriorSummary: + def test_returns_string(self, sample_draws): + from easyreflectometry.analysis.bayesian import posterior_summary + + draws, param_names = sample_draws + result = posterior_summary(draws, param_names) + assert isinstance(result, str) + assert 'parameter' in result + assert 'mean' in result + assert 'sd' in result + + def test_header_uses_quantile_labels(self, sample_draws): + """The header should label the columns as equal-tailed quantiles, not HDI.""" + from easyreflectometry.analysis.bayesian import posterior_summary + + draws, param_names = sample_draws + result = posterior_summary(draws, param_names) + header = result.splitlines()[0] + assert 'q2.5%' in header + assert 'q97.5%' in header + assert 'hdi' not in header.lower() + + def test_contains_param_names(self, sample_draws): + from easyreflectometry.analysis.bayesian import posterior_summary + + draws, param_names = sample_draws + result = posterior_summary(draws, param_names) + for name in param_names: + assert name in result + + +class TestCredibleIntervals: + def test_returns_dict(self, sample_draws): + from easyreflectometry.analysis.bayesian import credible_intervals + + draws, param_names = sample_draws + result = credible_intervals(draws, param_names) + assert isinstance(result, dict) + for name in param_names: + assert name in result + lo, hi = result[name] + assert lo < hi + + def test_alpha_95_coverage(self, sample_draws): + from easyreflectometry.analysis.bayesian import credible_intervals + + draws, param_names = sample_draws + result = credible_intervals(draws, param_names, alpha=0.95) + for i, name in enumerate(param_names): + lo, hi = result[name] + # 95% interval should contain at least 90% of samples + col = draws[:, i] + inside = np.sum((col >= lo) & (col <= hi)) + assert inside / len(col) >= 0.90 + + def test_alpha_50_narrower(self, sample_draws): + from easyreflectometry.analysis.bayesian import credible_intervals + + draws, param_names = sample_draws + ci_95 = credible_intervals(draws, param_names, alpha=0.95) + ci_50 = credible_intervals(draws, param_names, alpha=0.50) + for name in param_names: + assert (ci_95[name][1] - ci_95[name][0]) > (ci_50[name][1] - ci_50[name][0]) + + +class TestPosteriorResults: + def test_repr(self, sample_draws): + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + pr = PosteriorResults(draws, param_names) + rep = repr(pr) + assert 'PosteriorResults' in rep + assert str(draws.shape[0]) in rep + + def test_summary_delegates(self, sample_draws): + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + pr = PosteriorResults(draws, param_names) + summary_str = pr.summary() + assert isinstance(summary_str, str) + assert 'parameter' in summary_str + + def test_credible_interval_delegates(self, sample_draws): + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + pr = PosteriorResults(draws, param_names) + ci = pr.credible_interval(alpha=0.95) + assert isinstance(ci, dict) + for name in param_names: + assert name in ci + + +class TestPosteriorPredictiveReflectivity: + def test_returns_tuples(self, sample_draws): + """Test with a mock model that returns a constant array.""" + from unittest.mock import MagicMock + + from easyreflectometry.analysis.bayesian import posterior_predictive_reflectivity + + draws, param_names = sample_draws + mock_model = MagicMock() + mock_model.unique_name = 'test_model' + mock_model.interface = MagicMock() + mock_model.interface.fit_func = MagicMock(return_value=np.ones(50)) + mock_model.get_parameters = MagicMock(return_value=[]) + + q_values = np.linspace(0.01, 0.3, 50) + median, lower, upper = posterior_predictive_reflectivity( + draws, + param_names, + mock_model, + q_values, + n_samples=20, + ) + assert median.shape == (50,) + assert lower.shape == (50,) + assert upper.shape == (50,) + + +class TestPosteriorPredictiveSLDProfile: + def test_returns_tuples(self, sample_draws): + """Test with a mock model that returns constant z and sld.""" + from unittest.mock import MagicMock + + from easyreflectometry.analysis.bayesian import posterior_predictive_sld_profile + + draws, param_names = sample_draws + mock_model = MagicMock() + mock_model.unique_name = 'test_model' + mock_model.interface = MagicMock() + mock_model.interface.sld_profile = MagicMock(return_value=(np.linspace(0, 500, 100), np.ones(100) * 2.0)) + mock_model.get_parameters = MagicMock(return_value=[]) + + z, median, lower, upper = posterior_predictive_sld_profile( + draws, + param_names, + mock_model, + n_samples=20, + ) + assert z.shape == (100,) + assert median.shape == (100,) + assert lower.shape == (100,) + assert upper.shape == (100,) + + +class TestCornerPlot: + def test_plot_corner_returns_plotly_figure(self, sample_draws): + """plot_corner returns a Plotly Figure built from posterior draws.""" + try: + from plotly.graph_objects import Figure + + from easyreflectometry.analysis.bayesian import plot_corner + except ImportError: + pytest.skip('plotly not installed') + + draws, param_names = sample_draws + fig = plot_corner(draws, param_names) + assert isinstance(fig, Figure) + assert len(fig.data) > 0 + + +class TestSaveRestoreParameterState: + def test_save_and_restore(self): + """Test that parameter state save/restore works correctly.""" + from easyreflectometry.analysis.bayesian import _restore_parameter_state + from easyreflectometry.analysis.bayesian import _save_parameter_state + + # Use simple objects that support attribute assignment + class MockParam: + def __init__(self, unique_name, raw_value, error): + self.unique_name = unique_name + self.value = raw_value + self.error = error + + param1 = MockParam('param_a', 1.5, 0.1) + param2 = MockParam('param_b', 3.0, 0.2) + + class MockModel: + def get_parameters(self): + return [param1, param2] + + model = MockModel() + + state = _save_parameter_state(model) + assert state['param_a'] == (1.5, 0.1) + assert state['param_b'] == (3.0, 0.2) + + # Modify values + param1.raw_value = 99.0 + param1.value = 99.0 + param2.raw_value = 99.0 + param2.value = 99.0 + + _restore_parameter_state(model, state) + assert param1.value == 1.5 + assert param1.error == 0.1 + assert param2.value == 3.0 + assert param2.error == 0.2 + + +class TestApplyDraw: + def test_apply_draw_updates_parameters(self): + """Test that _apply_draw sets parameter values correctly.""" + from easyreflectometry.analysis.bayesian import _apply_draw + + class MockParam: + def __init__(self, unique_name): + self.unique_name = unique_name + self.value = None + + param_a = MockParam('thickness') + param_b = MockParam('sld') + + class MockModel: + def get_parameters(self): + return [param_a, param_b] + + model = MockModel() + draws = np.array([[250.0, 2.0], [260.0, 2.1]]) + param_names = ['thickness', 'sld'] + + _apply_draw(model, draws, param_names, row=0) + assert param_a.value == 250.0 + assert param_b.value == 2.0 + + _apply_draw(model, draws, param_names, row=1) + assert param_a.value == 260.0 + assert param_b.value == 2.1 + + +class TestGelmanRubinRequiresMultipleChains: + def test_raises_on_2d_draws(self, sample_draws): + """R-hat is undefined for a single chain; ``gelman_rubin`` must reject 2-D input.""" + pytest.importorskip('arviz') + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws # shape (n_samples, n_params) + pr = PosteriorResults(draws, param_names) + with pytest.raises(ValueError, match='at least 2 chains'): + pr.gelman_rubin() + + def test_raises_on_single_chain_3d(self, sample_draws): + """Even with an explicit chain axis, n_chains == 1 must raise.""" + pytest.importorskip('arviz') + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + single_chain = draws[np.newaxis, ...] # (1, n_draws, n_params) + pr = PosteriorResults(single_chain, param_names) + with pytest.raises(ValueError, match='at least 2 chains'): + pr.gelman_rubin() + + def test_accepts_multi_chain(self, sample_draws, monkeypatch): + """With n_chains >= 2 the diagnostic should forward to arviz and return its values.""" + pytest.importorskip('arviz') + from easyreflectometry.analysis import bayesian as bayesian_mod + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + rng = np.random.default_rng(7) + second = np.column_stack([ + rng.normal(loc=250, scale=10, size=draws.shape[0]), + rng.normal(loc=2.0, scale=0.2, size=draws.shape[0]), + ]) + multi = np.stack([draws, second], axis=0) # (2, n_draws, n_params) + + # Stub arviz.rhat so the test verifies the wrapper's contract (≥2 chains + # accepted, results unpacked per parameter) without depending on arviz's + # small-sample numerics, which can raise platform-specific TypeErrors. + class _FakeRhatVar: + def __init__(self, value: float) -> None: + self.values = np.array(value) + + fake_rhat = {name: _FakeRhatVar(1.01 + 0.001 * i) for i, name in enumerate(param_names)} + monkeypatch.setattr(bayesian_mod._arviz, 'rhat', lambda _data: fake_rhat) + + pr = PosteriorResults(multi, param_names) + result = pr.gelman_rubin() + assert isinstance(result, dict) + for i, name in enumerate(param_names): + assert name in result + assert result[name] == pytest.approx(1.01 + 0.001 * i) + + +class TestPlotFigureFallbackWarnings: + """When ``return_figure=True`` and plotly is missing, the helpers must warn.""" + + def test_plot_trace_warns_without_plotly(self, sample_draws, monkeypatch): + import builtins + + from easyreflectometry.analysis.bayesian import plot_trace + + real_import = builtins.__import__ + + def _fake_import(name, *args, **kwargs): + if name.startswith('plotly'): + raise ImportError('plotly disabled for test') + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, '__import__', _fake_import) + + draws, param_names = sample_draws + with pytest.warns(UserWarning, match='plotly'): + result = plot_trace(draws, param_names, return_figure=True) + assert result is None + + def test_plot_distribution_warns_without_plotly(self, sample_draws, monkeypatch): + import builtins + + from easyreflectometry.analysis.bayesian import plot_distribution + + real_import = builtins.__import__ + + def _fake_import(name, *args, **kwargs): + if name.startswith('plotly'): + raise ImportError('plotly disabled for test') + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, '__import__', _fake_import) + + draws, param_names = sample_draws + with pytest.warns(UserWarning, match='plotly'): + result = plot_distribution(draws, param_names, return_figure=True) + assert result is None + + +# =================================================================== +# Persistence helpers — save_posterior / load_posterior +# =================================================================== + + +class TestSaveLoadPosterior: + """Tests for ``save_posterior`` and ``load_posterior``.""" + + @pytest.fixture + def mock_posterior_results(self, sample_draws): + """Build a PosteriorResults with a real-looking mocked sampler_state.""" + from unittest.mock import MagicMock + + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + + # Build a mock that passes isinstance(obj, MCMCDraw) for the + # type guard in save_posterior. We use a non-spec MagicMock and + # reassign its __class__ so isinstance succeeds. + import bumps.dream.state as _bds + + mock_state = MagicMock() + mock_state.__class__ = _bds.MCMCDraw + + mock_state.Nvar = draws.shape[1] + mock_state.Npop = 5 + mock_state.labels = [f'p{name}' for name in param_names] + mock_draw = MagicMock() + mock_draw.points = draws + mock_draw.logp = np.zeros(draws.shape[0]) + mock_state.draw.return_value = mock_draw + + pr = PosteriorResults( + draws=draws, + param_names=param_names, + logp=np.zeros(draws.shape[0]), + sampler_state=mock_state, + ) + return pr + + def test_save_posterior_no_state_raises(self, sample_draws): + """PosteriorResults without sampler_state raises ValueError.""" + from easyreflectometry.analysis.bayesian import PosteriorResults + from easyreflectometry.analysis.bayesian import save_posterior + + draws, param_names = sample_draws + pr = PosteriorResults(draws, param_names) + with pytest.raises(ValueError, match='no sampler_state'): + save_posterior(pr, 'dummy') + + def test_save_posterior_wrong_state_type_raises(self, sample_draws): + """Non-MCMCDraw sampler_state raises TypeError.""" + from unittest.mock import MagicMock + + from easyreflectometry.analysis.bayesian import PosteriorResults + from easyreflectometry.analysis.bayesian import save_posterior + + draws, param_names = sample_draws + pr = PosteriorResults(draws, param_names, sampler_state=MagicMock()) + with pytest.raises(TypeError, match='MCMCDraw'): + save_posterior(pr, 'dummy') + + def test_save_and_load_roundtrip(self, mock_posterior_results, monkeypatch, tmp_path): + """Save then load, verify draws, param_names, logp, and state.""" + from unittest.mock import MagicMock + + # Mock save_state and load_state + import bumps.dream.state as _bds + + from easyreflectometry.analysis.bayesian import load_posterior + from easyreflectometry.analysis.bayesian import save_posterior + + saved_state_ref = mock_posterior_results.sampler_state + monkeypatch.setattr(_bds, 'save_state', MagicMock()) + monkeypatch.setattr(_bds, 'load_state', MagicMock(return_value=saved_state_ref)) + + prefix = str(tmp_path / 'test_run') + save_posterior(mock_posterior_results, prefix) + + # Verify save_state was called + _bds.save_state.assert_called_once_with(saved_state_ref, prefix) + + loaded = load_posterior(prefix) + + assert np.allclose(loaded.draws, mock_posterior_results.draws) + assert loaded.param_names == mock_posterior_results.param_names + assert loaded.sampler_state is saved_state_ref + + def test_save_convenience_method(self, mock_posterior_results, monkeypatch, tmp_path): + """PosteriorResults.save() delegates to save_posterior.""" + from unittest.mock import MagicMock + + import bumps.dream.state as _bds + + monkeypatch.setattr(_bds, 'save_state', MagicMock()) + + prefix = str(tmp_path / 'test_convenience') + mock_posterior_results.save(prefix) + + _bds.save_state.assert_called_once_with(mock_posterior_results.sampler_state, prefix) + + def test_load_posterior_skip(self, mock_posterior_results, monkeypatch, tmp_path): + """load_posterior with skip>0 forwards skip to load_state.""" + from unittest.mock import MagicMock + + import bumps.dream.state as _bds + + from easyreflectometry.analysis.bayesian import load_posterior + + monkeypatch.setattr(_bds, 'load_state', MagicMock(return_value=mock_posterior_results.sampler_state)) + monkeypatch.setattr(_bds, 'save_state', MagicMock()) + + prefix = str(tmp_path / 'test_skip') + load_posterior(prefix, skip=5) + + _bds.load_state.assert_called_once_with(prefix, skip=5) + + +class TestPlotDistributionExported: + def test_in_analysis_namespace(self): + """``plot_distribution`` should be importable from the analysis package.""" + from easyreflectometry import analysis + + assert hasattr(analysis, 'plot_distribution') + assert 'plot_distribution' in analysis.__all__ diff --git a/tests/test_fitting.py b/tests/test_fitting.py index 28dfe0fa..e4f938c8 100644 --- a/tests/test_fitting.py +++ b/tests/test_fitting.py @@ -801,3 +801,266 @@ def _fake_fit(*, x, y, weights): fitter.fit_single_data_set_1d(data, objective='legacy_mask') assert len(captured['x'][0]) == 2 # one point dropped + + +# --------------------------------------------------------------------------- +# Tests for MultiFitter.mcmc_sample (Bayesian MCMC) +# --------------------------------------------------------------------------- + + +class TestMCMCSampleRequiresBumpsEngine: + """mcmc_sample() must raise when the core engine is not a BUMPS instance.""" + + def test_raises_runtime_error_when_not_bumps(self): + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, + }) + + with pytest.raises(RuntimeError, match='Bayesian sampling requires a BUMPS minimizer'): + fitter.mcmc_sample(data) + + def test_wrapper_check_runs_before_core_mcmc_sample(self): + """The wrapper-level guard must fire before delegating to the core sampler. + + Replace the core ``mcmc_sample`` with a sentinel that would record any call; + the guard should raise without invoking it. + """ + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) # default minimizer is LMFit, not BUMPS + + core_called = {'count': 0} + + def _should_not_be_called(**_kwargs): + core_called['count'] += 1 + return {'draws': np.empty((0, 0)), 'param_names': [], 'state': None, 'logp': None} + + fitter.easy_science_multi_fitter.mcmc_sample = _should_not_be_called + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, + }) + + with pytest.raises(RuntimeError, match='Bayesian sampling requires a BUMPS minimizer'): + fitter.mcmc_sample(data) + assert core_called['count'] == 0 + + +class TestMCMCSampleBasic: + """Basic mcmc_sample() dispatch and return-value forwarding.""" + + def test_returns_core_result_dict(self): + """mcmc_sample() returns whatever the core MultiFitter.mcmc_sample() returns.""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + # Mock the core MultiFitter.mcmc_sample to return a known dict + fake_result = {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(return_value=fake_result) + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, + }) + + result = fitter.mcmc_sample(data, samples=100, burn=20, thin=2, population=5) + assert result is fake_result + + def test_forwards_hyperparams_to_core(self): + """Samples, burn, thin, population, chains are forwarded to core.""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + captured = {} + + def _fake_mcmc_sample(*, x, y, weights, samples, burn, thin, population, **kwargs): + captured['samples'] = samples + captured['burn'] = burn + captured['thin'] = thin + captured['population'] = population + return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, + }) + + fitter.mcmc_sample(data, samples=500, burn=100, thin=5, population=8) + assert captured['samples'] == 500 + assert captured['burn'] == 100 + assert captured['thin'] == 5 + assert captured['population'] == 8 + + def test_forwards_population_to_core(self): + """'population' argument is forwarded to core.""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + captured = {} + + def _fake_mcmc_sample(*, x, y, weights, population, **kwargs): + captured['population'] = population + return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, + }) + + fitter.mcmc_sample(data, samples=100, burn=20, thin=2, population=6) + assert captured['population'] == 6 + + +class TestMCMCSampleInitializer: + """initializer parameter is forwarded via sampler_kwargs.""" + + def test_initializer_passed_as_sampler_kwargs_init(self): + """initializer='lhs' should be passed as sampler_kwargs={'init': 'lhs'} to core.""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + captured = {} + + def _fake_mcmc_sample(*, sampler_kwargs, **kwargs): + captured['sampler_kwargs'] = sampler_kwargs + return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, + }) + + fitter.mcmc_sample(data, samples=100, burn=20, thin=2, initializer='lhs') + assert captured['sampler_kwargs'] == {'init': 'lhs'} + + def test_initializer_none_omits_sampler_kwargs(self): + """When initializer is None, sampler_kwargs should be None, not an empty dict.""" + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + captured = {} + + def _fake_mcmc_sample(*, sampler_kwargs, **kwargs): + captured['sampler_kwargs'] = sampler_kwargs + return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, + }) + + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + assert captured['sampler_kwargs'] is None + + +class TestMCMCSampleZeroVariance: + """Zero-variance handling in the mcmc_sample() data-preparation path.""" + + def test_hybrid_transforms_zero_variance_points(self): + """mcmc_sample() uses the objective from constructor to prepare data arrays.""" + import warnings + + model = Model() + model.interface = CalculatorFactory() + # Use legacy_mask so zero-variance points are dropped + fitter = MultiFitter(model, objective='legacy_mask') + + captured = {} + + def _fake_mcmc_sample(*, x, y, weights, **kwargs): + captured['x'] = x + captured['y'] = y + captured['weights'] = weights + return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + qz = np.linspace(0.01, 0.3, 10) + r = np.exp(-qz * 50) + var = np.ones(10) * 0.01 + var[3:5] = 0.0 # 2 zero-variance points + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=qz)}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=r, variances=var)}, + }) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + + # legacy_mask should drop the 2 zero-variance points + assert len(captured['x'][0]) == 8 + assert len(captured['y'][0]) == 8 + assert len(captured['weights'][0]) == 8 + + mask_warnings = [str(ww.message) for ww in w if 'Masked' in str(ww.message)] + assert len(mask_warnings) == 1 + assert '2 data point(s)' in mask_warnings[0] + + def test_per_call_objective_override(self): + """mcmc_sample() respects per-call objective override.""" + import warnings + + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model, objective='legacy_mask') # default + + captured = {} + + def _fake_mcmc_sample(*, x, y, weights, **kwargs): + captured['x'] = x + captured['y'] = y + return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + qz = np.linspace(0.01, 0.3, 10) + r = np.exp(-qz * 50) + var = np.ones(10) * 0.01 + var[3:5] = 0.0 + + data = sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=qz)}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=r, variances=var)}, + }) + + # Override to hybrid — should keep all 10 points + with warnings.catch_warnings(record=True): + warnings.simplefilter('always') + fitter.mcmc_sample(data, samples=100, burn=20, thin=2, objective='hybrid') + + assert len(captured['x'][0]) == 10 # all points kept (Mighell-substituted) diff --git a/tests/test_ort_file.py b/tests/test_ort_file.py index b1277033..ed8180df 100644 --- a/tests/test_ort_file.py +++ b/tests/test_ort_file.py @@ -2,14 +2,13 @@ # SPDX-License-Identifier: BSD-3-Clause import logging +import os import numpy as np - -# from dmsc_nightly.data import make_pooch -import pooch import pytest from easyscience.fitting import AvailableMinimizers +import easyreflectometry from easyreflectometry.calculators import CalculatorFactory from easyreflectometry.data import load from easyreflectometry.fitting import MultiFitter @@ -20,30 +19,12 @@ from easyreflectometry.sample import Multilayer from easyreflectometry.sample import Sample - -def make_pooch(base_url: str, registry: dict[str, str | None]) -> pooch.Pooch: - """Make a Pooch object to download test data.""" - return pooch.create( - path=pooch.os_cache('data'), - env='POOCH_DIR', - base_url=base_url, - registry=registry, - ) - - -@pytest.fixture(scope='module') -def data_registry(): - return make_pooch( - base_url='https://pub-6c25ef91903d4301a3338bd53b370098.r2.dev', - registry={ - 'amor_reduced_iofq.ort': None, - }, - ) +PATH_STATIC = os.path.join(os.path.dirname(easyreflectometry.__file__), '..', '..', 'tests', '_static') @pytest.fixture(scope='module') -def load_data(data_registry): - path = data_registry.fetch('amor_reduced_iofq.ort') +def load_data(): + path = os.path.join(PATH_STATIC, 'amor_reduced_iofq.ort') logging.info('Loading data from %s', path) data = load(path) return data From bfae1449cc8ee2598d76809bd679a50916306bd7 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 5 Jun 2026 16:44:46 +0200 Subject: [PATCH 15/38] HTML Summary now shows plots with plotly (#363) --- pyproject.toml | 1 + .../summary/html_templates.py | 10 ++ src/easyreflectometry/summary/summary.py | 167 ++++++++++++++++-- tests/summary/test_summary.py | 21 ++- 4 files changed, 185 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e9a7c354..ac183b21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ dependencies = [ 'xhtml2pdf', 'bumps', 'pooch', + 'plotly', ] [project.optional-dependencies] diff --git a/src/easyreflectometry/summary/html_templates.py b/src/easyreflectometry/summary/html_templates.py index 66012d40..9df780aa 100644 --- a/src/easyreflectometry/summary/html_templates.py +++ b/src/easyreflectometry/summary/html_templates.py @@ -150,3 +150,13 @@
Fit experiment plot """ + +# Interactive figures for HTML reports. The plotly ``
``s carry their own +# JavaScript so the report stays interactive (zoom, pan, hover) when opened in a +# browser. The plotly.js library is embedded inline in the first figure, which +# keeps the saved report self-contained and working offline. +HTML_INTERACTIVE_FIGURES_TEMPLATE = """ +sld_plot_div +
+fit_experiment_plot_div +""" diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index 2fc2846f..f40f23ad 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: 2024 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +import contextlib +import io +import logging from html import escape from importlib.metadata import PackageNotFoundError from importlib.metadata import version @@ -15,12 +18,35 @@ from .html_templates import HTML_DATA_COLLECTION_TEMPLATE from .html_templates import HTML_FIGURES_TEMPLATE +from .html_templates import HTML_INTERACTIVE_FIGURES_TEMPLATE from .html_templates import HTML_PARAMETER_HEADER_TEMPLATE from .html_templates import HTML_PARAMETER_TEMPLATE from .html_templates import HTML_PROJECT_INFORMATION_TEMPLATE from .html_templates import HTML_REFINEMENT_TEMPLATE from .html_templates import HTML_TEMPLATE + +@contextlib.contextmanager +def _silence_pdf_converter(): + """Silence xhtml2pdf's verbose output during PDF conversion. + + xhtml2pdf emits a large amount of ``log.debug`` output (image tags, file + objects, column widths, parsed schemes, ...) and a few stray ``print`` + statements while rendering. When the host application has configured logging + at DEBUG level this floods stdout. For the duration of the conversion we + raise the ``xhtml2pdf`` logger level so its debug records are dropped, and + redirect stdout to swallow the stray prints. Both are restored afterwards. + """ + xhtml2pdf_logger = logging.getLogger('xhtml2pdf') + previous_level = xhtml2pdf_logger.level + xhtml2pdf_logger.setLevel(logging.WARNING) + try: + with contextlib.redirect_stdout(io.StringIO()): + yield + finally: + xhtml2pdf_logger.setLevel(previous_level) + + _NAME_MAX_LEN = 20 # Custom href scheme used to pass the full name to QML via TextEdit.hoveredLink. _TOOLTIP_SCHEME = 'nametooltip' @@ -89,8 +115,19 @@ def __init__(self, project: Project): """Init function.""" self._project = project - def compile_html_summary(self, figures: bool = False) -> str: - """Compile html summary.""" + def compile_html_summary(self, figures: bool = False, interactive: bool = True) -> str: + """Compile html summary. + + Parameters + ---------- + figures + Whether to render the figures section. + interactive + When ``True`` (the default) the figures are rendered as interactive + plotly charts suitable for an HTML report. Set to ``False`` to fall + back to static images, e.g. when the html is handed to the PDF + converter, which cannot run the embedded JavaScript. + """ html = HTML_TEMPLATE html = html.replace('project_information_section', self._project_information_section()) @@ -105,7 +142,7 @@ def compile_html_summary(self, figures: bool = False) -> str: html = html.replace('refinement_section', self._refinement_section()) if figures: - html = html.replace('figures_section', self._figures_section()) + html = html.replace('figures_section', self._figures_section(interactive=interactive)) else: html = html.replace('figures_section', '') @@ -113,19 +150,22 @@ def compile_html_summary(self, figures: bool = False) -> str: def save_html_summary(self, filename: str) -> None: """Save html summary.""" - html = self.compile_html_summary(figures=True) - with open(filename, 'w') as f: + html = self.compile_html_summary(figures=True, interactive=True) + with open(filename, 'w', encoding='utf-8') as f: f.write(html) def save_pdf_summary(self, filename: str) -> None: """Save pdf summary.""" - html = self.compile_html_summary(figures=True) + # The PDF converter (xhtml2pdf) cannot execute the JavaScript that powers + # the interactive plotly charts, so embed static images instead. + html = self.compile_html_summary(figures=True, interactive=False) with open(filename, 'w+b') as result_file: - pisa_status = pisa.CreatePDF( - html, - dest=result_file, - ) + with _silence_pdf_converter(): + pisa_status = pisa.CreatePDF( + html, + dest=result_file, + ) if pisa_status.err: print('An error occured when generating PDF summary!') @@ -290,8 +330,16 @@ def _compute_goodness_of_fit(self) -> str: except (AttributeError, TypeError, ValueError, ZeroDivisionError): return 'N/A' - def _figures_section(self) -> None: - """Figures section.""" + def _figures_section(self, interactive: bool = True) -> str: + """Figures section. + + When *interactive* is ``True`` the figures are rendered as interactive + plotly charts embedded directly in the html. Otherwise static images are + written to disk and referenced (used for the PDF report). + """ + if interactive: + return self._interactive_figures_section() + html_figures = HTML_FIGURES_TEMPLATE path_sld = self._project.path / 'sld_plot.jpg' path_fit_experiment = self._project.path / 'fit_experiment_plot.jpg' @@ -302,3 +350,98 @@ def _figures_section(self) -> None: html_figures = html_figures.replace('path_sld_plot', str(path_sld)) html_figures = html_figures.replace('path_fit_experiment_plot', str(path_fit_experiment)) return html_figures + + def _interactive_figures_section(self) -> str: + """Build the interactive (plotly) figures section for the html report.""" + import plotly.io as pio + + fig_sld = self._sld_plotly_figure() + fig_fit_experiment = self._fit_experiment_plotly_figure() + + # Embed plotly.js inline once (with the first figure) so the saved report + # is self-contained and renders without an internet connection. + sld_div = pio.to_html( + fig_sld, + include_plotlyjs=True, + full_html=False, + default_width='640px', + default_height='480px', + ) + fit_experiment_div = pio.to_html( + fig_fit_experiment, + include_plotlyjs=False, + full_html=False, + default_width='640px', + default_height='480px', + ) + + html_figures = HTML_INTERACTIVE_FIGURES_TEMPLATE + html_figures = html_figures.replace('sld_plot_div', sld_div) + html_figures = html_figures.replace('fit_experiment_plot_div', fit_experiment_div) + return html_figures + + def _sld_plotly_figure(self): + """Interactive SLD profile figure.""" + import plotly.graph_objects as go + + sld = self._project.sld_data_for_model_at_index(0) + + fig = go.Figure() + fig.add_trace( + go.Scatter( + x=np.asarray(sld.x), + y=np.asarray(sld.y), + mode='lines', + name='SLD', + line={'color': 'blue'}, + ) + ) + fig.update_layout( + xaxis_title='z (Å)', + yaxis_title='SLD (Å⁻²)', + template='simple_white', + margin={'l': 70, 'r': 20, 't': 30, 'b': 50}, + legend={'x': 0.99, 'xanchor': 'right', 'y': 0.99, 'yanchor': 'top'}, + ) + return fig + + def _fit_experiment_plotly_figure(self): + """Interactive reflectivity (model vs. experiment) figure.""" + import plotly.graph_objects as go + + fig = go.Figure() + + model = self._project.model_data_for_model_at_index(0) + fig.add_trace( + go.Scatter( + x=np.asarray(model.x), + y=np.asarray(model.y), + mode='lines', + name='Model', + line={'color': 'blue'}, + ) + ) + + try: + experiment = self._project.experimental_data_for_model_at_index(0) + fig.add_trace( + go.Scatter( + x=np.asarray(experiment.x), + y=np.asarray(experiment.y), + mode='markers', + name='Experiment', + marker={'color': 'red', 'size': 4}, + ) + ) + except IndexError: + pass + + fig.update_layout( + xaxis_title='Q (Å⁻¹)', + yaxis_title='Reflectivity', + yaxis_type='log', + template='simple_white', + margin={'l': 70, 'r': 20, 't': 30, 'b': 50}, + legend={'x': 0.99, 'xanchor': 'right', 'y': 0.99, 'yanchor': 'top'}, + ) + return fig diff --git a/tests/summary/test_summary.py b/tests/summary/test_summary.py index 8a8f581f..5f135fb0 100644 --- a/tests/summary/test_summary.py +++ b/tests/summary/test_summary.py @@ -195,17 +195,34 @@ def test_save_fit_experiment_plot(self, project: Project, tmp_path) -> None: # Expect assert os.path.exists(file_path) - def test_figures_section(self, project: Project) -> None: + def test_figures_section_static(self, project: Project) -> None: # When summary = Summary(project) summary.save_sld_plot = MagicMock() summary.save_fit_experiment_plot = MagicMock() # Then - html = summary._figures_section() + html = summary._figures_section(interactive=False) # Expect summary.save_sld_plot.assert_called_once() summary.save_fit_experiment_plot.assert_called_once() assert 'sld_plot' in html assert 'fit_experiment_plot' in html + + def test_figures_section_interactive(self, project: Project) -> None: + # When + summary = Summary(project) + summary.save_sld_plot = MagicMock() + summary.save_fit_experiment_plot = MagicMock() + + # Then + html = summary._figures_section(interactive=True) + + # Expect + # Interactive figures must not fall back to the static image plots. + summary.save_sld_plot.assert_not_called() + summary.save_fit_experiment_plot.assert_not_called() + # Two interactive plotly charts with the library embedded inline once. + assert html.count('class="plotly-graph-div"') == 2 + assert 'Plotly.newPlot' in html From a47241189d172f9197575e52133b634b84c27d6b Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 5 Jun 2026 17:20:04 +0200 Subject: [PATCH 16/38] Pointwise update (#362) * initial commit with examples * ruff format * refactor core modifications to live in reflectometry lib only * modified the notebook a bit * refactored MCMC sampling to easyscience module * added pass-through for cancellation and total number of calculations * silly typo * yet another linting * moved the notebook to the correct location and fixed the formatting * added more initial arguments to sample. Added unit tests * prettify charts. Updated notebook * fixed issue with the notebook * move file fetch to reflectometry/data * PR review fixes * be careful with small chains * ruff * try to use explicit dtype for Ubuntu * another attempt at fixing failing test * better corner and distribution plots * updated poitwise logic/notebook --- .../simulation/resolution_functions.ipynb | 11 ++- .../model/resolution_functions.py | 80 +++++++++---------- tests/model/test_resolution_functions.py | 15 +++- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/docs/docs/tutorials/simulation/resolution_functions.ipynb b/docs/docs/tutorials/simulation/resolution_functions.ipynb index b5bd017e..d46a76cb 100644 --- a/docs/docs/tutorials/simulation/resolution_functions.ipynb +++ b/docs/docs/tutorials/simulation/resolution_functions.ipynb @@ -340,7 +340,10 @@ "## Afterthoughts\n", "As a last task we will compare the reflectivity determined using a percentage resolution function and a point-wise function.\n", "We should recall that the \"experimental\" data was generated using `Refnx`.\n", - "By comparing the reflectivities determined using a resolution function with a FWHM of 1.0% and the point-wise FHWN constructed from data in a `.ort` file it is apparent that this reference data also was constructed using a resolution function of 1.0%." + "\n", + "The `Pointwise` resolution function derives a per-point resolution width directly from the data: it takes the `[Qz, R, sQz]` triple, where `sQz` is the variance of `Qz` (`Qz_0.variances`), and uses `sqrt(sQz)` as the resolution width at each measured point, linearly interpolating onto the requested `q` (exactly as `LinearSpline` does for explicitly provided widths).\n", + "\n", + "By comparing the reflectivities determined using a resolution function with a FWHM of 1.0% and the point-wise width constructed from the data in a `.ort` file, it is apparent that this reference data also was constructed using a resolution function of 1.0%." ] }, { @@ -404,16 +407,20 @@ " model.unique_name,\n", ")\n", "plt.plot(model_coords, model_data, 'k-', label='Variable', linewidth=5)\n", + "\n", + "# The Pointwise resolution is built from the data triple [Qz, R, sQz],\n", + "# where sQz is the variance of Qz. The width sqrt(sQz) is interpolated onto q.\n", "data_points = []\n", "data_points.append(reference_coords) # Qz\n", "data_points.append(reference_data) # R\n", - "data_points.append(reference_variances) # sQz\n", + "data_points.append(reference_variances) # sQz (variance of Qz)\n", "model.resolution_function = Pointwise(q_data_points=data_points)\n", "model_data = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", "plt.plot(model_coords, model_data, 'r-', label='Pointwise')\n", + "plt.plot(reference_coords, reference_data, 'bx', label='Reference', markersize=4)\n", "\n", "ax = plt.gca()\n", "ax.set_xlim([-0.01, 0.45])\n", diff --git a/src/easyreflectometry/model/resolution_functions.py b/src/easyreflectometry/model/resolution_functions.py index ee0933e2..fe5d2254 100644 --- a/src/easyreflectometry/model/resolution_functions.py +++ b/src/easyreflectometry/model/resolution_functions.py @@ -82,27 +82,45 @@ def as_dict( } -# add pointwise smearing funtion class Pointwise(ResolutionFunction): - def __init__(self, q_data_points: list[np.ndarray]): - """Init function.""" + """Pointwise resolution defined by a per-point resolution provided with the data. + + The resolution is supplied as the variance of the Qz values (``sQz``) at the + measured Qz data points, which is the form produced by data reduction (e.g. + ``Qz_0.variances``). The resolution width at each point is ``sqrt(sQz)``. + For a requested ``q`` the width is obtained by linearly interpolating onto + ``q``, exactly as :class:`LinearSpline` does for explicitly provided widths. + + This is a convenience wrapper around :class:`LinearSpline` that derives the + widths from the ``[Qz, R, sQz]`` triple loaded from a data file; the returned + widths are consumed by the calculators (refnx ``x_err`` / refl1d ``dq``), + which perform the actual convolution against the model. + """ + + def __init__(self, q_data_points: List[np.ndarray]): + """Init function. + + Parameters + ---------- + q_data_points : List[np.ndarray] + ``[Qz, R, sQz]`` where ``Qz`` are the measured Qz values, ``R`` the + measured reflectivity (kept only for serialization round-trips) and + ``sQz`` the variance of ``Qz`` at each point. + """ self.q_data_points = q_data_points - self.q = None - def smearing(self, q: Union[np.ndarray, float] = None) -> np.ndarray: - """Smearing function.""" - Qz = self.q_data_points[0] - R = self.q_data_points[1] - sQz = self.q_data_points[2] - if q is None: - q = self.q_data_points[0] - self.q = q - sQzs = np.sqrt(sQz) - if isinstance(Qz, float): - Qz = np.array(Qz) - - smeared = self.apply_smooth_smearing(Qz, R, sQzs) - return smeared + def smearing(self, q: Optional[Union[np.ndarray, float]] = None) -> np.ndarray: + """Return the resolution width interpolated onto ``q``. + + The width at each data point is ``sqrt(sQz)``; values are linearly + interpolated onto the requested ``q``. When ``q`` is ``None`` the widths + are returned at the stored data points. + """ + Qz = np.asarray(self.q_data_points[0], dtype=float) + sQz = np.asarray(self.q_data_points[2], dtype=float) + q_eval = Qz if q is None else np.asarray(q, dtype=float) + widths = np.sqrt(sQz) + return np.asarray(np.interp(q_eval, Qz, widths)) def as_dict( self, skip: Optional[List[str]] = None @@ -114,29 +132,3 @@ def as_dict( 'R_data_points': list(self.q_data_points[1]), 'sQz_data_points': list(self.q_data_points[2]), } - - def gaussian_smearing(self, qt, Qz, R, sQz): - """Gaussian smearing.""" - weights = np.exp(-0.5 * ((qt - Qz) / sQz) ** 2) - if np.sum(weights) == 0 or not np.isfinite(np.sum(weights)): - return np.sum(R) - weights /= sQz * np.sqrt(2 * np.pi) - return np.sum(R * weights) / np.sum(weights) - - def apply_smooth_smearing(self, Qz, R, sQzs): - """Apply smooth resolution smearing using convolution with Gaussian kernel.""" - if self.q is None: - R_smeared = np.zeros_like(Qz) - else: - R_smeared = np.zeros_like(self.q) - - if not isinstance(Qz, np.ndarray): - Qz = np.array(Qz) - if not isinstance(R, np.ndarray): - R = np.array(R) - R_smeared = np.zeros_like(self.q) - - for i, qt in enumerate(self.q): - R_smeared[i] = self.gaussian_smearing(qt, Qz, R, sQzs) - - return R_smeared diff --git a/tests/model/test_resolution_functions.py b/tests/model/test_resolution_functions.py index e28a99f2..b8a4ea18 100644 --- a/tests/model/test_resolution_functions.py +++ b/tests/model/test_resolution_functions.py @@ -95,12 +95,23 @@ def test_constructor(self): # When resolution_function = Pointwise(q_data_points=self.data_points) - # Then Expect + # Then Expect: smearing returns the resolution width sqrt(sQz) at the data + # points, since sQz holds the variance of Qz (consistent with LinearSpline). + expected_widths = np.sqrt(self.data_points[2]) assert np.allclose( np.array(resolution_function.smearing()), - np.array([2.51664683, 2.84038734, 3.2460762, 3.6796519, 4.07869271]), + expected_widths, ) + def test_smearing_interpolates_onto_q(self): + # When + resolution_function = Pointwise(q_data_points=self.data_points) + + # Then Expect: requesting points between data points linearly interpolates the width. + widths = np.sqrt(self.data_points[2]) + expected = np.interp([0.15, 0.25], self.data_points[0], widths) + assert np.allclose(resolution_function.smearing([0.15, 0.25]), expected) + def test_as_dict(self): # When resolution_function = Pointwise(q_data_points=self.data_points) From 5e14e7fe9d2af7b3669ede3ab5fc58184f2efe6e Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 24 Jul 2026 10:14:06 +0200 Subject: [PATCH 17/38] 381 apitest hygiene (#384) * addressing #381 * minor docstring update --- CONTRIBUTING.md | 3 +- docs/docs/tutorials/simulation/bilayer.ipynb | 10 +-- .../docs/tutorials/simulation/magnetism.ipynb | 16 ++--- .../simulation/resolution_functions.ipynb | 10 +-- docs/mkdocs.yml | 3 +- pixi.lock | 66 +++++++++++-------- .../calculators/calculator_base.py | 2 +- src/easyreflectometry/calculators/factory.py | 2 +- src/easyreflectometry/fitting.py | 20 +----- src/easyreflectometry/limits.py | 11 +++- src/easyreflectometry/model/model.py | 4 +- .../model/resolution_functions.py | 3 +- src/easyreflectometry/project.py | 44 ++++++------- .../elements/materials/material_mixture.py | 17 +++-- src/easyreflectometry/special/calculations.py | 10 +-- .../refl1d/test_refl1d_calculator.py | 8 +-- .../refnx/test_refnx_calculator.py | 6 +- tests/model/test_model.py | 4 +- tests/model/test_resolution_functions.py | 11 +++- .../layers/test_layer_area_per_molecule.py | 18 ++--- .../materials/test_material_mixture.py | 58 ++++++++-------- tests/test_project.py | 2 +- tests/test_topmost_nesting.py | 4 +- 23 files changed, 171 insertions(+), 161 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4ac1fbc..a6ceeebd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,8 +42,7 @@ Please make sure you follow the EasyScience organization-wide If you are not planning to contribute code, you may want to: - 🐞 Report a bug — see [Reporting Issues](#11-reporting-issues) -- 🛡 Report a security issue — see - [Security Issues](#12-security-issues) +- 🛡 Report a security issue — see [Security Issues](#12-security-issues) - 💬 Ask a question or start a discussion at [Project Discussions](https://github.com/easyscience/reflectometry-lib/discussions) diff --git a/docs/docs/tutorials/simulation/bilayer.ipynb b/docs/docs/tutorials/simulation/bilayer.ipynb index 5f37b010..987cd8e1 100644 --- a/docs/docs/tutorials/simulation/bilayer.ipynb +++ b/docs/docs/tutorials/simulation/bilayer.ipynb @@ -331,7 +331,7 @@ "q = np.linspace(0.005, 0.3, 500)\n", "\n", "# Calculate reflectometry\n", - "reflectivity = model.interface().reflectity_profile(q, model.unique_name)\n", + "reflectivity = model.interface().reflectivity_profile(q, model.unique_name)\n", "\n", "# Plot\n", "plt.figure(figsize=(10, 6))\n", @@ -391,7 +391,7 @@ "outputs": [], "source": [ "# First, compute reflectivity with current conformal roughness (3.0 Å)\n", - "reflectivity_conformal = model.interface().reflectity_profile(q, model.unique_name)\n", + "reflectivity_conformal = model.interface().reflectivity_profile(q, model.unique_name)\n", "\n", "# Disable conformal roughness to allow independent roughness per layer\n", "bilayer.conformal_roughness = False\n", @@ -406,7 +406,7 @@ "bilayer.back_head_layer.roughness.value = 4.0\n", "\n", "# Compute reflectivity with variable roughness\n", - "reflectivity_variable_roughness = model.interface().reflectity_profile(q, model.unique_name)\n", + "reflectivity_variable_roughness = model.interface().reflectivity_profile(q, model.unique_name)\n", "\n", "# Plot comparison\n", "plt.figure(figsize=(10, 6))\n", @@ -629,8 +629,8 @@ "outputs": [], "source": [ "# Calculate reflectivity for both contrasts\n", - "reflectivity_d2o = model.interface().reflectity_profile(q, model.unique_name)\n", - "reflectivity_h2o = model_h2o.interface().reflectity_profile(q, model_h2o.unique_name)\n", + "reflectivity_d2o = model.interface().reflectivity_profile(q, model.unique_name)\n", + "reflectivity_h2o = model_h2o.interface().reflectivity_profile(q, model_h2o.unique_name)\n", "\n", "plt.figure(figsize=(10, 6))\n", "plt.semilogy(q, reflectivity_d2o, 'b-', linewidth=2, label='D₂O contrast')\n", diff --git a/docs/docs/tutorials/simulation/magnetism.ipynb b/docs/docs/tutorials/simulation/magnetism.ipynb index 8efdb9e4..ef226c41 100644 --- a/docs/docs/tutorials/simulation/magnetism.ipynb +++ b/docs/docs/tutorials/simulation/magnetism.ipynb @@ -252,7 +252,7 @@ "model.resolution_function = PercentageFwhm(0)\n", "model_interface = model.interface()\n", "model_interface.magnetism = False\n", - "model_data_no_magnetism_ref1d_easy = model.interface().reflectity_profile(\n", + "model_data_no_magnetism_ref1d_easy = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -284,7 +284,7 @@ "model.interface = interface\n", "model_interface = model.interface()\n", "model_interface.include_magnetism = True\n", - "model_data_magnetism = model.interface().reflectity_profile(\n", + "model_data_magnetism = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -301,7 +301,7 @@ "model_interface._wrapper.update_layer(\n", " list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175\n", ")\n", - "model_data_magnetism_layer_1 = model.interface().reflectity_profile(\n", + "model_data_magnetism_layer_1 = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -365,7 +365,7 @@ "model_interface._wrapper.update_layer(\n", " list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175\n", ")\n", - "model_data_magnetism_easy = model.interface().reflectity_profile(\n", + "model_data_magnetism_easy = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -478,7 +478,7 @@ "interface.switch('refnx')\n", "model.interface = interface\n", "model_interface = model.interface()\n", - "model_data_no_magnetism_refnx = model.interface().reflectity_profile(\n", + "model_data_no_magnetism_refnx = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -488,7 +488,7 @@ "interface.switch('refl1d')\n", "model.interface = interface\n", "model_interface = model.interface()\n", - "model_data_no_magnetism_ref1d = model.interface().reflectity_profile(\n", + "model_data_no_magnetism_ref1d = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -518,7 +518,7 @@ "model.interface = interface\n", "model_interface = model.interface()\n", "model_interface.magnetism = True\n", - "model_data_magnetism = model.interface().reflectity_profile(\n", + "model_data_magnetism = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -529,7 +529,7 @@ "model.interface = interface\n", "model_interface = model.interface()\n", "model_interface.magnetism = False\n", - "model_data_no_magnetism = model.interface().reflectity_profile(\n", + "model_data_no_magnetism = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", diff --git a/docs/docs/tutorials/simulation/resolution_functions.ipynb b/docs/docs/tutorials/simulation/resolution_functions.ipynb index d46a76cb..cc7abe10 100644 --- a/docs/docs/tutorials/simulation/resolution_functions.ipynb +++ b/docs/docs/tutorials/simulation/resolution_functions.ipynb @@ -310,7 +310,7 @@ " num=1000,\n", " )\n", " model.resolution_function = resolution_function_dict[key]\n", - " model_data = model.interface().reflectity_profile(\n", + " model_data = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", " )\n", @@ -363,14 +363,14 @@ ")\n", "\n", "model.resolution_function = resolution_function_dict[key]\n", - "model_data = model.interface().reflectity_profile(\n", + "model_data = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", "plt.plot(model_coords, model_data, 'k-', label='Variable', linewidth=5)\n", "\n", "model.resolution_function = PercentageFwhm(1.0)\n", - "model_data = model.interface().reflectity_profile(\n", + "model_data = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -402,7 +402,7 @@ ")\n", "\n", "model.resolution_function = resolution_function_dict[key]\n", - "model_data = model.interface().reflectity_profile(\n", + "model_data = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -415,7 +415,7 @@ "data_points.append(reference_data) # R\n", "data_points.append(reference_variances) # sQz (variance of Qz)\n", "model.resolution_function = Pointwise(q_data_points=data_points)\n", - "model_data = model.interface().reflectity_profile(\n", + "model_data = model.interface().reflectivity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f75e4657..1cd19cde 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -208,7 +208,8 @@ nav: - Elements: - Layers: - Layer: api-reference/elements/layer.md - - Layer Area Per Molecule: api-reference/elements/layer_area_per_molecule.md + - Layer Area Per Molecule: + api-reference/elements/layer_area_per_molecule.md - Materials: - Material: api-reference/elements/material.md - Material Density: api-reference/elements/material_density.md diff --git a/pixi.lock b/pixi.lock index 6b3a38bc..983bc0bd 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,8 +1,21 @@ version: 7 platforms: - name: linux-64 -- name: osx-arm64 + virtual-packages: + - __unix=0=0 + - __linux=4.18 + - __glibc=2.28 + - __archspec=0=x86_64 +- name: p1 + subdir: osx-arm64 + virtual-packages: + - __osx=14.0 + - __unix=0=0 + - __archspec=0=m1 - name: win-64 + virtual-packages: + - __win=10.0 + - __archspec=0=x86_64 environments: default: channels: @@ -189,8 +202,8 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -326,7 +339,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - osx-arm64: + p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -500,8 +513,8 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -801,8 +814,8 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -1124,8 +1137,8 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1259,7 +1272,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - osx-arm64: + p1: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda @@ -1429,8 +1442,8 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1724,8 +1737,8 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2047,8 +2060,8 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2184,7 +2197,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - osx-arm64: + p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -2358,8 +2371,8 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2659,8 +2672,8 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: ./ + - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -3070,7 +3083,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - osx-arm64: + p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -8297,12 +8310,13 @@ packages: purls: [] size: 388453 timestamp: 1764777142545 -- pypi: . +- pypi: ./ name: easyreflectometry requires_dist: - bumps - - easyscience @ git+https://github.com/easyscience/corelib@develop + - easyscience @ git+https://github.com/easyscience/corelib.git@develop - orsopy + - plotly - pooch - refl1d>=1.0.0 - refnx @@ -8344,7 +8358,7 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.11' -- pypi: git+https://github.com/easyscience/corelib?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 +- pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 name: easyscience version: 2.3.1+dev8 requires_dist: diff --git a/src/easyreflectometry/calculators/calculator_base.py b/src/easyreflectometry/calculators/calculator_base.py index e2a92804..06fa5f07 100644 --- a/src/easyreflectometry/calculators/calculator_base.py +++ b/src/easyreflectometry/calculators/calculator_base.py @@ -189,7 +189,7 @@ def remove_item_from_model(self, item_id: str, model_id: str) -> None: """ self._wrapper.remove_item(item_id, model_id) - def reflectity_profile(self, x_array: np.ndarray, model_id: str) -> np.ndarray: + def reflectivity_profile(self, x_array: np.ndarray, model_id: str) -> np.ndarray: """Determines the reflectivity profile for the given range and model. Parameters diff --git a/src/easyreflectometry/calculators/factory.py b/src/easyreflectometry/calculators/factory.py index c3e1479c..b1c1b170 100644 --- a/src/easyreflectometry/calculators/factory.py +++ b/src/easyreflectometry/calculators/factory.py @@ -40,6 +40,6 @@ def fit_func(self) -> Callable: def __fit_func(*args, **kwargs): """Fit func.""" - return self().reflectity_profile(*args, **kwargs) + return self().reflectivity_profile(*args, **kwargs) return __fit_func diff --git a/src/easyreflectometry/fitting.py b/src/easyreflectometry/fitting.py index 83a3f6eb..3cb0ecb3 100644 --- a/src/easyreflectometry/fitting.py +++ b/src/easyreflectometry/fitting.py @@ -384,8 +384,8 @@ def mcmc_sample( — the population already exists in the saved state. :param progress_callback: Optional callback for progress updates during sampling. Forwarded to the core MultiFitter. - :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, - and ``'logp'``. + :return: Dictionary with keys ``'draws'``, ``'param_names'``, + ``'internal_bumps_object'``, and ``'logp'``. :raises RuntimeError: If the current minimizer is not a BUMPS instance. """ minimizer = self.easy_science_multi_fitter.minimizer @@ -494,19 +494,3 @@ def switch_minimizer(self, minimizer: AvailableMinimizers) -> None: Minimizer to be switched to. """ self.easy_science_multi_fitter.switch_minimizer(minimizer) - - -def _flatten_list(this_list: list) -> list: - """Flatten nested lists. - - Parameters - ---------- - this_list : list - List to be flattened. - - Returns - ------- - list - Flattened list. - """ - return np.array([item for sublist in this_list for item in sublist]) diff --git a/src/easyreflectometry/limits.py b/src/easyreflectometry/limits.py index 001bba64..8962ab9b 100644 --- a/src/easyreflectometry/limits.py +++ b/src/easyreflectometry/limits.py @@ -31,14 +31,19 @@ def apply_default_limits(parameter: Parameter, kind: str) -> None: def _apply_percentage_limits(parameter: Parameter) -> None: - """Set min to 50% and max to 200% of the current value, only if current bounds are inf.""" + """Set bounds to 50%-200% of the current value, only if current bounds are inf. + + For negative values 0.5*value > 2*value, so the candidates are ordered + to keep min <= value <= max. + """ value = parameter.value if value == 0.0: return + low, high = sorted((0.5 * value, 2.0 * value)) if np.isinf(parameter.min): - parameter.min = 0.5 * value + parameter.min = low if np.isinf(parameter.max): - parameter.max = 2.0 * value + parameter.max = high def _apply_fixed_limits(parameter: Parameter, low: float, high: float) -> None: diff --git a/src/easyreflectometry/model/model.py b/src/easyreflectometry/model/model.py index da1396d0..5aa32833 100644 --- a/src/easyreflectometry/model/model.py +++ b/src/easyreflectometry/model/model.py @@ -148,12 +148,12 @@ def background(self, value: float) -> None: # ----- assembly management ----- - def add_assemblies(self, *assemblies: list[BaseAssembly]) -> None: + def add_assemblies(self, *assemblies: BaseAssembly) -> None: """Add assemblies to the model sample. Parameters ---------- - *assemblies : list[BaseAssembly] + *assemblies : BaseAssembly Assemblies to add to model sample. """ if not assemblies: diff --git a/src/easyreflectometry/model/resolution_functions.py b/src/easyreflectometry/model/resolution_functions.py index fe5d2254..c723bf14 100644 --- a/src/easyreflectometry/model/resolution_functions.py +++ b/src/easyreflectometry/model/resolution_functions.py @@ -10,6 +10,7 @@ from __future__ import annotations +from abc import ABC from abc import abstractmethod from typing import List from typing import Optional @@ -20,7 +21,7 @@ DEFAULT_RESOLUTION_FWHM_PERCENTAGE = 5.0 -class ResolutionFunction: +class ResolutionFunction(ABC): @abstractmethod def smearing(self, q: Union[np.array, float]) -> np.array: ... diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 5ae272a6..fe4af1bf 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -346,29 +346,30 @@ def path_json(self): """Path json.""" return self.path / 'project.json' + def _get_or_add_material_index(self, name: str, sld: float, isld: float) -> int: + """Return the index of the named material, adding it to the project + materials first if not present. This mutates ``self._materials``.""" + names = [material.name for material in self._materials] + if name not in names: + self._materials.add_material(Material(name=name, sld=sld, isld=isld)) + names.append(name) + return names.index(name) + def get_index_air(self) -> int: - """Get index air.""" - if 'Air' not in [material.name for material in self._materials]: - self._materials.add_material(Material(name='Air', sld=0.0, isld=0.0)) - return [material.name for material in self._materials].index('Air') + """Index of the Air material, adding it to the project if missing.""" + return self._get_or_add_material_index('Air', sld=0.0, isld=0.0) def get_index_si(self) -> int: - """Get index si.""" - if 'Si' not in [material.name for material in self._materials]: - self._materials.add_material(Material(name='Si', sld=2.07, isld=0.0)) - return [material.name for material in self._materials].index('Si') + """Index of the Si material, adding it to the project if missing.""" + return self._get_or_add_material_index('Si', sld=2.07, isld=0.0) def get_index_sio2(self) -> int: - """Get index sio2.""" - if 'SiO2' not in [material.name for material in self._materials]: - self._materials.add_material(Material(name='SiO2', sld=3.47, isld=0.0)) - return [material.name for material in self._materials].index('SiO2') + """Index of the SiO2 material, adding it to the project if missing.""" + return self._get_or_add_material_index('SiO2', sld=3.47, isld=0.0) def get_index_d2o(self) -> int: - """Get index d2o.""" - if 'D2O' not in [material.name for material in self._materials]: - self._materials.add_material(Material(name='D2O', sld=6.36, isld=0.0)) - return [material.name for material in self._materials].index('D2O') + """Index of the D2O material, adding it to the project if missing.""" + return self._get_or_add_material_index('D2O', sld=6.36, isld=0.0) def load_orso_file(self, path: Union[Path, str]) -> None: """Load an ORSO file and optionally create a model and a data from it.""" @@ -386,7 +387,6 @@ def load_orso_file(self, path: Union[Path, str]) -> None: self._experiments[0].name = 'Experiment from ORSO' self._experiments[0].model = self.models[0] self._with_experiments = True - pass def set_sample_from_orso(self, sample: Sample) -> None: """Replace the current project model collection with a single model built from an ORSO-parsed sample. @@ -665,7 +665,7 @@ def model_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.arr if q_range is None: q_range = np.linspace(self.q_min, self.q_max, self.q_resolution) self.models[index].interface = self._calculator - reflectivity = self.models[index].interface().reflectity_profile(q_range, self._models[index].unique_name) + reflectivity = self.models[index].interface().reflectivity_profile(q_range, self._models[index].unique_name) return DataSet1D( name=f'Reflectivity for Model {index}', x=q_range, @@ -868,10 +868,10 @@ def as_dict(self, include_materials_not_in_model=False): self._as_dict_add_materials_not_in_model_dict(project_dict) if self._with_experiments: self._as_dict_add_experiments(project_dict) - if self.fitter is not None: - project_dict['fitter_minimizer'] = self.fitter.easy_science_multi_fitter.minimizer.name - elif self._minimizer_selection is not None: - project_dict['fitter_minimizer'] = self._minimizer_selection.name + # Read the minimizer without touching the lazy `fitter` property: + # serialization must not construct a MultiFitter as a side effect. + if self.minimizer is not None: + project_dict['fitter_minimizer'] = self.minimizer.name if self._calculator is not None: project_dict['calculator'] = self._calculator.current_interface_name if self._colors is not None: diff --git a/src/easyreflectometry/sample/elements/materials/material_mixture.py b/src/easyreflectometry/sample/elements/materials/material_mixture.py index 1ab76ddc..b5b88e27 100644 --- a/src/easyreflectometry/sample/elements/materials/material_mixture.py +++ b/src/easyreflectometry/sample/elements/materials/material_mixture.py @@ -152,17 +152,16 @@ def fraction(self, value: float) -> None: # ----- derived sld / isld parameters (shared shape with Material) ----- # # These are *derived* via the constraints set up in `_materials_constraints` - # (not constructor arguments) so we expose them as floats to match the - # legacy MaterialMixture API. The underlying Parameter objects remain - # available as `self._sld` / `self._isld`. + # (not constructor arguments), so unlike Material there are no setters: + # their values follow the child materials and the fraction. @property - def sld(self) -> float: - return self._sld.value + def sld(self) -> Parameter: + return self._sld @property - def isld(self) -> float: - return self._isld.value + def isld(self) -> Parameter: + return self._isld # ----- calculator binding ----- @@ -171,8 +170,8 @@ def _get_linkable_attributes(self): Override of the inherited `BaseCore._get_linkable_attributes`, which walks `get_all_variables()` and would otherwise expose the **child** - materials' sld/isld (because our own `sld` / `isld` are floats, not - Parameters). The calculator's `InterfaceFactoryTemplate.generate_bindings` + materials' sld/isld alongside the mixed ones. The calculator's + `InterfaceFactoryTemplate.generate_bindings` matches by parameter `name`; without this override it binds to `material_a.sld` and reflectivity is computed off the wrong SLD. """ diff --git a/src/easyreflectometry/special/calculations.py b/src/easyreflectometry/special/calculations.py index 07844d3c..9c6e43c2 100644 --- a/src/easyreflectometry/special/calculations.py +++ b/src/easyreflectometry/special/calculations.py @@ -44,11 +44,13 @@ def neutron_scattering_length(formula: str) -> complex: scattering_length = 0 + 0j for key, value in formula_as_dict.items(): scattering_length += pt.elements.symbol(key).neutron.b_c * value + # b_c_i is the imaginary (absorption) part of the bound coherent + # scattering length, not the incoherent scattering length. if pt.elements.symbol(key).neutron.b_c_i: - inc = pt.elements.symbol(key).neutron.b_c_i + imag = pt.elements.symbol(key).neutron.b_c_i else: - inc = 0 - scattering_length += inc * 1j * value + imag = 0 + scattering_length += imag * 1j * value return scattering_length * 1e-5 @@ -63,7 +65,7 @@ def molecular_weight(formula: str) -> float: Returns ------- float - Molecular weight of the material in kilograms. + Molecular weight of the material in u (g/mol). """ formula_as_dict = parse_formula(formula) mw = 0 diff --git a/tests/calculators/refl1d/test_refl1d_calculator.py b/tests/calculators/refl1d/test_refl1d_calculator.py index 50de2db4..a27d1f7a 100644 --- a/tests/calculators/refl1d/test_refl1d_calculator.py +++ b/tests/calculators/refl1d/test_refl1d_calculator.py @@ -27,7 +27,7 @@ def test_init(self): assert_equal(p._model_link['background'], 'bkg') assert_equal(p.name, 'refl1d') - def test_reflectity_profile(self): + def test_reflectivity_profile(self): p = Refl1d() p._wrapper.create_material('Material1') p._wrapper.update_material('Material1', rho=0.000, irho=0.000) @@ -63,7 +63,7 @@ def test_reflectity_profile(self): 1.3093e-07, 1.0520e-07, ] - assert_almost_equal(p.reflectity_profile(q, 'MyModel'), expected, decimal=4) + assert_almost_equal(p.reflectivity_profile(q, 'MyModel'), expected, decimal=4) def test_calculate2(self): p = Refl1d() @@ -95,7 +95,7 @@ def test_calculate2(self): p._wrapper.add_item('Item3', 'MyModel') p._wrapper.update_item('Item2', repeat=10) q = np.linspace(0.001, 0.3, 10) - actual = p.reflectity_profile(q, 'MyModel') + actual = p.reflectivity_profile(q, 'MyModel') expected = [ 9.9949e-01, 8.7414e-03, @@ -140,7 +140,7 @@ def test_calculate_magnetic(self): p._wrapper.add_item('Item2', 'MyModel') p._wrapper.add_item('Item3', 'MyModel') q = np.linspace(0.001, 0.3, 10) - actual = p.reflectity_profile(q, 'MyModel') + actual = p.reflectivity_profile(q, 'MyModel') expected = [ 9.99491251e-01, 1.08413641e-02, diff --git a/tests/calculators/refnx/test_refnx_calculator.py b/tests/calculators/refnx/test_refnx_calculator.py index baeb9296..e4efae68 100644 --- a/tests/calculators/refnx/test_refnx_calculator.py +++ b/tests/calculators/refnx/test_refnx_calculator.py @@ -27,7 +27,7 @@ def test_init(self): assert_equal(p._model_link['background'], 'bkg') assert_equal(p.name, 'refnx') - def test_reflectity_profile(self): + def test_reflectivity_profile(self): p = Refnx() p._wrapper.create_material('Material1') p._wrapper.update_material('Material1', real=0.000, imag=0.000) @@ -62,7 +62,7 @@ def test_reflectity_profile(self): 1.26726993e-07, 1.01842852e-07, ] - assert_almost_equal(p.reflectity_profile(q, 'MyModel'), expected) + assert_almost_equal(p.reflectivity_profile(q, 'MyModel'), expected) def test_calculate2(self): p = Refnx() @@ -105,7 +105,7 @@ def test_calculate2(self): 3.4981523e-07, 2.5424356e-07, ] - assert_almost_equal(p.reflectity_profile(q, 'MyModel'), expected) + assert_almost_equal(p.reflectivity_profile(q, 'MyModel'), expected) def test_sld_profile(self): p = Refnx() diff --git a/tests/model/test_model.py b/tests/model/test_model.py index b11149c1..0c83d5dc 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -427,8 +427,8 @@ def test_dict_round_trip(interface): if interface is not None: assert model.interface().name == model_from_dict.interface().name assert_almost_equal( - model.interface().reflectity_profile([0.3], model.unique_name), - model_from_dict.interface().reflectity_profile([0.3], model_from_dict.unique_name), + model.interface().reflectivity_profile([0.3], model.unique_name), + model_from_dict.interface().reflectivity_profile([0.3], model_from_dict.unique_name), ) diff --git a/tests/model/test_resolution_functions.py b/tests/model/test_resolution_functions.py index b8a4ea18..1391949b 100644 --- a/tests/model/test_resolution_functions.py +++ b/tests/model/test_resolution_functions.py @@ -38,7 +38,7 @@ def test_as_dict(self): resolution_function = PercentageFwhm(1.0) # Then Expect - resolution_function.as_dict() == {'smearing': 'PercentageFwhm', 'constant': 1.0} + assert resolution_function.as_dict() == {'smearing': 'PercentageFwhm', 'constant': 1.0} def test_dict_round_trip(self): # When @@ -67,7 +67,7 @@ def test_as_dict(self): resolution_function = LinearSpline(q_data_points=[0, 10], fwhm_values=[5, 10]) # Then Expect - resolution_function.as_dict() == { + assert resolution_function.as_dict() == { 'smearing': 'LinearSpline', 'q_data_points': [0, 10], 'fwhm_values': [5, 10], @@ -117,7 +117,12 @@ def test_as_dict(self): resolution_function = Pointwise(q_data_points=self.data_points) # Then Expect - assert resolution_function.as_dict(), {'smearing': 'Pointwise', 'q_data_points': [0, 10]} + assert resolution_function.as_dict() == { + 'smearing': 'Pointwise', + 'q_data_points': self.data_points[0], + 'R_data_points': self.data_points[1], + 'sQz_data_points': self.data_points[2], + } def test_dict_round_trip(self): # When diff --git a/tests/sample/elements/layers/test_layer_area_per_molecule.py b/tests/sample/elements/layers/test_layer_area_per_molecule.py index 0d466be7..7fe04009 100644 --- a/tests/sample/elements/layers/test_layer_area_per_molecule.py +++ b/tests/sample/elements/layers/test_layer_area_per_molecule.py @@ -27,8 +27,8 @@ def test_default(self): assert p.roughness.value == 3.3 assert str(p.roughness.unit) == 'Å' assert p.roughness.fixed is True - assert_almost_equal(p.material.sld, 2.268770124481328) - assert_almost_equal(p.material.isld, 0) + assert_almost_equal(p.material.sld.value, 2.268770124481328) + assert_almost_equal(p.material.isld.value, 0) assert p.material.name == 'C10H18NO8P in D2O' assert p.solvent.sld.value == 6.36 assert p.solvent.isld.value == 0 @@ -69,7 +69,7 @@ def test_from_pars_constraint(self): ) assert p.molecular_formula == 'C8O10H12P' assert p.area_per_molecule.value == 50 - assert_almost_equal(p.material.sld, 0.31494833333333333) + assert_almost_equal(p.material.sld.value, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 @@ -77,10 +77,10 @@ def test_from_pars_constraint(self): assert p.solvent_fraction.value == 0.5 p.area_per_molecule = 30 assert p.area_per_molecule.value == 30 - assert_almost_equal(p.material.sld, 0.7119138888888887) + assert_almost_equal(p.material.sld.value, 0.7119138888888887) p.thickness.value = 10 assert p.thickness.value == 10 - assert_almost_equal(p.material.sld, 0.9103966666666665) + assert_almost_equal(p.material.sld.value, 0.9103966666666665) @unittest.skip('Instantiation of LayerAreaPerMolecule fails, despite working everywhere else.') def test_solvent_change(self): @@ -97,7 +97,7 @@ def test_solvent_change(self): assert p.molecular_formula == 'C8O10H12P' assert p.area_per_molecule.value == 50 print(p.material) - assert_almost_equal(p.material.sld, 0.31494833333333333) + assert_almost_equal(p.material.sld.value, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 @@ -107,7 +107,7 @@ def test_solvent_change(self): p.solvent = d2o assert p.molecular_formula == 'C8O10H12P' assert p.area_per_molecule.value == 50 - assert_almost_equal(p.material.sld, 3.762948333333333) + assert_almost_equal(p.material.sld.value, 3.762948333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == 6.335 @@ -127,7 +127,7 @@ def test_molecular_formula_change(self): ) assert p.molecular_formula == 'C8O10H12P' assert p.area_per_molecule.value == 50 - assert_almost_equal(p.material.sld, 0.31494833333333333) + assert_almost_equal(p.material.sld.value, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 @@ -138,7 +138,7 @@ def test_molecular_formula_change(self): p.molecular_formula = 'C8O10D12P' assert p.molecular_formula == 'C8O10D12P' assert p.area_per_molecule.value == 50 - assert_almost_equal(p.material.sld, 1.3558483333333333) + assert_almost_equal(p.material.sld.value, 1.3558483333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 diff --git a/tests/sample/elements/materials/test_material_mixture.py b/tests/sample/elements/materials/test_material_mixture.py index 3c2a3f65..d3912b7d 100644 --- a/tests/sample/elements/materials/test_material_mixture.py +++ b/tests/sample/elements/materials/test_material_mixture.py @@ -15,8 +15,8 @@ def test_default(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 4.186) - assert_almost_equal(material_mixture.isld, 0) + assert_almost_equal(material_mixture.sld.value, 4.186) + assert_almost_equal(material_mixture.isld.value, 0) assert str(material_mixture._sld.unit) == '1/Å^2' assert str(material_mixture._isld.unit) == '1/Å^2' @@ -24,12 +24,12 @@ def test_default_constraint(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 4.186) - assert_almost_equal(material_mixture.isld, 0) + assert_almost_equal(material_mixture.sld.value, 4.186) + assert_almost_equal(material_mixture.isld.value, 0) material_mixture.material_a.sld.value = 0 material_mixture.material_b.isld.value = -1 - assert_almost_equal(material_mixture.sld, 2.093) - assert_almost_equal(material_mixture.isld, -0.5) + assert_almost_equal(material_mixture.sld.value, 2.093) + assert_almost_equal(material_mixture.isld.value, -0.5) assert str(material_mixture._sld.unit) == '1/Å^2' assert str(material_mixture._isld.unit) == '1/Å^2' @@ -38,59 +38,59 @@ def test_fraction_constraint(self): q = Material(6.908, -0.278, 'Boron') material_mixture = MaterialMixture(p, q, 0.2) assert material_mixture.fraction.value == 0.2 - assert_almost_equal(material_mixture.sld, 4.7304) - assert_almost_equal(material_mixture.isld, -0.0556) + assert_almost_equal(material_mixture.sld.value, 4.7304) + assert_almost_equal(material_mixture.isld.value, -0.0556) material_mixture._fraction.value = 0.5 assert material_mixture.fraction.value == 0.5 - assert_almost_equal(material_mixture.sld, 5.54700) - assert_almost_equal(material_mixture.isld, -0.1390) + assert_almost_equal(material_mixture.sld.value, 5.54700) + assert_almost_equal(material_mixture.isld.value, -0.1390) def test_material_a_change(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 4.186) - assert_almost_equal(material_mixture.isld, 0) + assert_almost_equal(material_mixture.sld.value, 4.186) + assert_almost_equal(material_mixture.isld.value, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_a = q assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 5.54700) - assert_almost_equal(material_mixture.isld, -0.1390) + assert_almost_equal(material_mixture.sld.value, 5.54700) + assert_almost_equal(material_mixture.isld.value, -0.1390) def test_material_b_change(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 4.186) - assert_almost_equal(material_mixture.isld, 0) + assert_almost_equal(material_mixture.sld.value, 4.186) + assert_almost_equal(material_mixture.isld.value, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_b = q assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 5.54700) - assert_almost_equal(material_mixture.isld, -0.1390) + assert_almost_equal(material_mixture.sld.value, 5.54700) + assert_almost_equal(material_mixture.isld.value, -0.1390) def test_material_b_change_double(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 4.186) - assert_almost_equal(material_mixture.isld, 0) + assert_almost_equal(material_mixture.sld.value, 4.186) + assert_almost_equal(material_mixture.isld.value, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_b = q assert material_mixture.name == 'EasyMaterial/Boron' assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 5.54700) - assert_almost_equal(material_mixture.isld, -0.1390) + assert_almost_equal(material_mixture.sld.value, 5.54700) + assert_almost_equal(material_mixture.isld.value, -0.1390) r = Material(0.00, 0.00, 'ACMW') material_mixture.material_b = r assert material_mixture.name == 'EasyMaterial/ACMW' assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 2.0930) - assert_almost_equal(material_mixture.isld, 0.0000) + assert_almost_equal(material_mixture.sld.value, 2.0930) + assert_almost_equal(material_mixture.isld.value, 0.0000) def test_from_pars(self): p = Material() @@ -98,8 +98,8 @@ def test_from_pars(self): material_mixture = MaterialMixture(p, q, 0.2) assert material_mixture.fraction.value == 0.2 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld, 4.7304) - assert_almost_equal(material_mixture.isld, -0.0556) + assert_almost_equal(material_mixture.sld.value, 4.7304) + assert_almost_equal(material_mixture.isld.value, -0.0556) assert str(material_mixture._sld.unit) == '1/Å^2' assert str(material_mixture._isld.unit) == '1/Å^2' @@ -158,7 +158,7 @@ def test_calculator_binding_uses_mixed_sld(self) -> None: mixture = MaterialMixture(material_a, material_b, fraction=0.25, interface=interface) # 2 * 0.75 + 6 * 0.25 = 1.5 + 1.5 = 3.0 - assert_almost_equal(mixture.sld, 3.0) + assert_almost_equal(mixture.sld.value, 3.0) wrapper_material = interface()._wrapper.storage['material'][mixture.unique_name] assert_almost_equal(wrapper_material.real.value, 3.0) assert_almost_equal(wrapper_material.imag.value, 0.0) @@ -173,8 +173,8 @@ def test_mutation_propagates_after_round_trip(self) -> None: global_object.map._clear() q = MaterialMixture.from_dict(p_dict) - assert_almost_equal(q.sld, 3.0) + assert_almost_equal(q.sld.value, 3.0) q.fraction = 0.8 # 2 * 0.2 + 6 * 0.8 = 0.4 + 4.8 = 5.2 - assert_almost_equal(q.sld, 5.2) + assert_almost_equal(q.sld.value, 5.2) diff --git a/tests/test_project.py b/tests/test_project.py index ec85b3f2..b1ae72eb 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -476,7 +476,7 @@ def test_as_dict_minimizer(self): project = Project() project._fitter = MagicMock() project._fitter.easy_science_multi_fitter = MagicMock() - project._fitter.easy_science_multi_fitter.minimizer = AvailableMinimizers.LMFit + project._fitter.easy_science_multi_fitter.minimizer.enum = AvailableMinimizers.LMFit # Then project_dict = project.as_dict() diff --git a/tests/test_topmost_nesting.py b/tests/test_topmost_nesting.py index 622991b2..1003efe5 100644 --- a/tests/test_topmost_nesting.py +++ b/tests/test_topmost_nesting.py @@ -47,8 +47,8 @@ def test_copy(): assert model._resolution_function.smearing(5.5) == model_copy._resolution_function.smearing(5.5) assert model.interface().name == model_copy.interface().name assert_almost_equal( - model.interface().reflectity_profile([0.3], model.unique_name), - model_copy.interface().reflectity_profile([0.3], model_copy.unique_name), + model.interface().reflectivity_profile([0.3], model.unique_name), + model_copy.interface().reflectivity_profile([0.3], model_copy.unique_name), ) assert model.unique_name != model_copy.unique_name assert model.name == model_copy.name From 90fed4f37b9366f5f9cee1c965a3b0cf97012836 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 27 Jul 2026 13:16:46 +0200 Subject: [PATCH 18/38] fixes for summary printout #375 (#385) --- src/easyreflectometry/fitting.py | 18 ++++++++ .../summary/html_templates.py | 2 +- src/easyreflectometry/summary/summary.py | 28 +++++------ tests/summary/test_summary.py | 46 +++++++++++++++++++ tests/test_fitting.py | 40 ++++++++++++++++ 5 files changed, 120 insertions(+), 14 deletions(-) diff --git a/src/easyreflectometry/fitting.py b/src/easyreflectometry/fitting.py index 3cb0ecb3..4cd24004 100644 --- a/src/easyreflectometry/fitting.py +++ b/src/easyreflectometry/fitting.py @@ -355,6 +355,24 @@ def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) ] return result + def record_fit_results(self, results: list[FitResults] | None) -> None: + """Record fit results produced outside this fitter instance. + + The application runs threaded fits directly on the lower-level + ``easy_science_multi_fitter`` (for progress reporting and cancellation) + rather than through :meth:`fit`. As a result, the high-level + ``_fit_results`` used by :attr:`chi2`, :attr:`reduced_chi`, and the + HTML summary's goodness-of-fit are never populated. Call this after such + a fit so those consumers reflect the latest results. Pass ``None`` to + clear. + + :param results: The list of ``FitResults`` from the completed fit, or + ``None`` to reset. + """ + self._fit_results = results + if not results: + self._classical_fit_metrics = None + def mcmc_sample( self, data: sc.DataGroup, diff --git a/src/easyreflectometry/summary/html_templates.py b/src/easyreflectometry/summary/html_templates.py index 9df780aa..fe520e2b 100644 --- a/src/easyreflectometry/summary/html_templates.py +++ b/src/easyreflectometry/summary/html_templates.py @@ -141,7 +141,7 @@ No. of constraints - num_constriants + num_constraints """ diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index f40f23ad..d8df7bd2 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -309,26 +309,28 @@ def _refinement_section(self) -> str: html_refinement = html_refinement.replace('num_total_params', f'{num_params}') html_refinement = html_refinement.replace('num_free_params', f'{num_free_params}') html_refinement = html_refinement.replace('num_fixed_params', f'{num_fixed_params}') - html_refinement = html_refinement.replace('num_constriants', f'{num_constraints}') + html_refinement = html_refinement.replace('num_constraints', f'{num_constraints}') return html_refinement def _compute_goodness_of_fit(self) -> str: - """Return reduced chi² as a formatted string, or 'N/A' if no fit has been run.""" - last_fit_results = getattr(self._project, '_last_fit_results', None) - if not last_fit_results: + """Return reduced chi² as a formatted string, or 'N/A' if no fit has been run. + + The value is read from the project's fitter, which computes the reduced + chi-square directly from the raw chi-square and the global degrees of + freedom across every fitted dataset. Deriving it this way keeps the + summary independent of the per-minimizer ``reduced_chi`` / ``reduced_chi2`` + attribute naming used by the underlying ``FitResults`` objects. + """ + fitter = self._project.fitter + if fitter is None: return 'N/A' try: - if len(last_fit_results) == 1: - gof = float(last_fit_results[0].reduced_chi2) - else: - total_chi2 = sum(float(r.chi2) for r in last_fit_results) - total_points = sum(len(r.x) for r in last_fit_results) - n_pars = last_fit_results[0].n_pars - dof = total_points - n_pars - gof = total_chi2 / dof if dof > 0 else 0.0 - return f'{gof:.4g}' + gof = fitter.reduced_chi except (AttributeError, TypeError, ValueError, ZeroDivisionError): return 'N/A' + if gof is None: + return 'N/A' + return f'{gof:.4g}' def _figures_section(self, interactive: bool = True) -> str: """Figures section. diff --git a/tests/summary/test_summary.py b/tests/summary/test_summary.py index 5f135fb0..271de2c4 100644 --- a/tests/summary/test_summary.py +++ b/tests/summary/test_summary.py @@ -4,6 +4,7 @@ import os from unittest.mock import MagicMock +import numpy as np import pytest from easyscience import global_object @@ -167,6 +168,51 @@ def test_refinement_section(self, project: Project) -> None: assert 'No. of free parameters:' in html assert '0' in html assert 'No. of constraints' in html + # The (previously misspelt) constraints token must be fully substituted. + assert 'num_constriants' not in html + assert 'num_constraints' not in html + + @staticmethod + def _populate_fit_results(project: Project, chi2: float, n_points: int, n_pars: int) -> None: + """Emulate a completed fit by storing results on the project's fitter.""" + fit_result = MagicMock() + fit_result.chi2 = chi2 + fit_result.x = np.arange(n_points) + fit_result.n_pars = n_pars + project.fitter._fit_results = [fit_result] + + def test_compute_goodness_of_fit_na_before_fit(self, project: Project) -> None: + # When + summary = Summary(project) + + # Then Expect: no fit has been run, so goodness-of-fit is unavailable. + assert summary._compute_goodness_of_fit() == 'N/A' + + def test_compute_goodness_of_fit_after_fit(self, project: Project) -> None: + # When: reduced chi² = 20 / (14 - 4) = 2.0 + summary = Summary(project) + self._populate_fit_results(project, chi2=20.0, n_points=14, n_pars=4) + + # Then + gof = summary._compute_goodness_of_fit() + + # Expect + assert gof != 'N/A' + assert float(gof) == pytest.approx(2.0) + + def test_refinement_section_shows_goodness_of_fit(self, project: Project) -> None: + # When + summary = Summary(project) + self._populate_fit_results(project, chi2=20.0, n_points=14, n_pars=4) + gof = summary._compute_goodness_of_fit() + + # Then + html = summary._refinement_section() + + # Expect: the template token is replaced with the actual value, not 'N/A'. + assert gof != 'N/A' + assert gof in html + assert 'goodness_of_fit' not in html def test_save_sld_plot(self, project: Project, tmp_path) -> None: # When diff --git a/tests/test_fitting.py b/tests/test_fitting.py index e4f938c8..896de593 100644 --- a/tests/test_fitting.py +++ b/tests/test_fitting.py @@ -287,6 +287,46 @@ def test_reduced_chi_uses_global_dof_across_fit_results(): assert fitter.reduced_chi == pytest.approx(expected) +def test_record_fit_results_populates_reduced_chi(): + """Results computed outside the fitter can be recorded so reduced_chi works. + + Mirrors the app path where the threaded fit runs on the low-level + easy_science_multi_fitter and the high-level results must be recorded + explicitly (otherwise the HTML summary goodness-of-fit shows 'N/A'). + """ + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + assert fitter.reduced_chi is None + + fit_result = MagicMock() + fit_result.chi2 = 20.0 + fit_result.x = np.arange(14) + fit_result.n_pars = 4 + + fitter.record_fit_results([fit_result]) + + assert fitter.reduced_chi == pytest.approx(20.0 / (14 - 4)) + + +def test_record_fit_results_none_clears_state(): + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + + fit_result = MagicMock() + fit_result.chi2 = 20.0 + fit_result.x = np.arange(14) + fit_result.n_pars = 4 + fitter.record_fit_results([fit_result]) + + fitter.record_fit_results(None) + + assert fitter.reduced_chi is None + assert fitter.chi2 is None + + def test_fit_single_data_set_1d_all_zero_variance_raises(): """Legacy mask mode raises when all points have zero variance.""" model = Model() From 01c07d5c14f580b20a540115b23cfad4534b1e9e Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Thu, 30 Jul 2026 20:00:48 +0200 Subject: [PATCH 19/38] Core fixes (#383) * moved to Sampler class from Core * reparent branch * fixed issue #367 (#382) * fixed issue #367 * disable automatic parallelization for windows * Changes after code review * added test * arviz fix * try to fix prettier issues * updated notebook * linting * fixing #370 (#388) * reparent to develop * Core fixes pointwise (#387) * fixed pointwise treatment on file load * more tests --- .github/workflows/pypi-test.yml | 4 +- .github/workflows/test.yml | 4 +- CHANGELOG.md | 50 ++ .../advancedfitting/bayesian_bumps.ipynb | 557 ++++++++++-------- docs/docs/tutorials/simulation/bilayer.ipynb | 10 +- .../docs/tutorials/simulation/magnetism.ipynb | 16 +- .../simulation/resolution_functions.ipynb | 10 +- pixi.lock | 67 +-- pixi.toml | 10 +- src/easyreflectometry/analysis/bayesian.py | 5 +- .../calculators/calculator_base.py | 2 +- src/easyreflectometry/calculators/factory.py | 2 +- .../calculators/refl1d/wrapper.py | 7 +- .../calculators/refnx/wrapper.py | 10 +- src/easyreflectometry/fitting.py | 101 +++- src/easyreflectometry/limits.py | 11 +- src/easyreflectometry/model/model.py | 4 +- .../model/resolution_functions.py | 45 +- src/easyreflectometry/project.py | 54 +- .../elements/materials/material_mixture.py | 17 +- src/easyreflectometry/special/calculations.py | 10 +- .../summary/html_templates.py | 2 +- src/easyreflectometry/summary/summary.py | 28 +- .../refl1d/test_refl1d_calculator.py | 8 +- .../refnx/test_refnx_calculator.py | 6 +- tests/calculators/refnx/test_refnx_wrapper.py | 2 +- .../test_resolution_conventions.py | 207 +++++++ .../test_cross_engine_resolution.py | 187 ++++++ tests/model/test_model.py | 17 +- tests/model/test_resolution_functions.py | 45 +- .../layers/test_layer_area_per_molecule.py | 18 +- .../materials/test_material_mixture.py | 58 +- tests/summary/test_summary.py | 48 +- tests/test_fitting.py | 420 +++++++++---- tests/test_project.py | 57 +- tests/test_topmost_nesting.py | 4 +- tests/unit/test_fitting_mcmc.py | 220 +++++++ tests/unit/test_material_and_calculations.py | 95 +++ tests/unit/test_project_core.py | 143 +++++ tests/unit/test_summary_goodness_of_fit.py | 86 +++ 40 files changed, 1957 insertions(+), 690 deletions(-) create mode 100644 tests/calculators/test_resolution_conventions.py create mode 100644 tests/integration/test_cross_engine_resolution.py create mode 100644 tests/unit/test_fitting_mcmc.py create mode 100644 tests/unit/test_material_and_calculations.py create mode 100644 tests/unit/test_project_core.py create mode 100644 tests/unit/test_summary_goodness_of_fit.py diff --git a/.github/workflows/pypi-test.yml b/.github/workflows/pypi-test.yml index 2f4f58ac..8bcb4b5e 100644 --- a/.github/workflows/pypi-test.yml +++ b/.github/workflows/pypi-test.yml @@ -71,7 +71,9 @@ jobs: - name: Run integration tests to verify the installation working-directory: easyreflectometry - run: pixi run python -m pytest ../tests/integration/ --color=yes -n auto + # No -n auto: concurrent xdist workers race on arviz's daily-warning + # stamp file when they import easyreflectometry. See pixi.toml. + run: pixi run python -m pytest ../tests/integration/ --color=yes # Job 2: Build and publish dashboard (reusable workflow) run-reusable-workflows: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6bf99c45..530c154f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -293,7 +293,9 @@ jobs: cd easyreflectometry_py$py_ver echo "Running tests" - pixi run python -m pytest ../tests/integration/ --color=yes -n auto -v ${{ needs.env-prepare.outputs.pytest-marks }} + # No -n auto: concurrent xdist workers race on arviz's daily-warning + # stamp file when they import easyreflectometry. See pixi.toml. + pixi run python -m pytest ../tests/integration/ --color=yes -v ${{ needs.env-prepare.outputs.pytest-marks }} echo "Exiting pixi project directory" cd .. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2537591f..55aafcae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,55 @@ # Unreleased +Restored the measured per-point resolution on data load (issue #368). + +- Loading data through `Project` (`load_new_experiment`, + `load_experiment_for_model_at_index`, + `load_all_experiments_from_file`) again sets a `Pointwise` resolution + function when the file carries per-point q-resolution (an sQz column + in `.ort` files, or a 4th column in text files). Since PR #293 the + loaders discarded this data and always applied a flat + `PercentageFwhm(5.0)` — a temporary workaround that never got + reverted. Fits of such data were smeared with 5% FWHM regardless of + what the instrument delivered and should be re-run. +- Files without q-resolution data keep the 5% FWHM default. The pre-#293 + fallback that built a `LinearSpline` from the _reflectivity_ error + (`sqrt(ye)`) was not restored: a reflectivity uncertainty is not a + q-width, and that branch produced effectively zero smearing. +- Known limitation (pre-existing): the resolution function lives on the + model, so when several experiments share one model the last-loaded + dataset's resolution wins. + +Fixed inconsistent interpretation of vector resolution functions between +the refnx and refl1d engines (issue #367). + +- **Reflectivity results change for two engine / resolution + combinations.** `LinearSpline` on refl1d previously **over-smeared by + a factor of 2.355** (its FWHM widths were passed to refl1d's + `probe.dQ`, which expects sigma). `Pointwise` on refnx previously + **under-smeared by the same factor** (its sigma widths were passed to + refnx's `x_err`, which expects FWHM). Both are now correct. Fits and + simulations that used either combination will produce different — + previously wrong — results and should be re-run. `PercentageFwhm` on + either engine, `LinearSpline` on refnx, and `Pointwise` on refl1d are + numerically unchanged. +- `ResolutionFunction.smearing()` now returns **sigma** (the Gaussian + standard deviation) for every subclass; each engine wrapper converts + to its backend's convention. This is a behavioural change to a public + method. Most visibly, `PercentageFwhm.smearing(q)` used to return the + _percentage_ itself (e.g. `5.0`) and now returns an absolute sigma + (e.g. `0.00212` at `q=0.1`); `LinearSpline.smearing(q)` returns its + `fwhm_values` divided by `2*sqrt(2*ln2)`. Callers relying on the old + values need to convert. The new `SIGMA_TO_FWHM` constant is exported + from `easyreflectometry.model.resolution_functions`. +- Constructors are **unchanged**: `PercentageFwhm(5)` still means 5% + FWHM and `LinearSpline(q, fwhm_values)` still takes FWHM. Only the + `smearing()` output convention moved, so existing model-building code + needs no edits. +- `PercentageFwhm.smearing(q)` given a scalar `q` now returns a 0-d + numpy scalar rather than a shape-`(1,)` array, matching + `LinearSpline`. `smearing(0.1)[0]` therefore raises `IndexError` where + it previously returned a value. + Migrated sample / model classes off the deprecated `easyscience.ObjBase` and `easyscience.CollectionBase` pipeline. diff --git a/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb b/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb index 1c7e1bf6..dcaaa025 100644 --- a/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb +++ b/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb @@ -16,11 +16,18 @@ "- Classical optimisation first (good starting point for Bayesian sampling).\n", "- High-level DREAM MCMC sampling via\n", " ``MultiFitter.mcmc_sample()`` and ``PosteriorResults``.\n", + "- Checking convergence with the Gelman-Rubin R-hat diagnostic, then\n", + " **extending an under-converged chain** with ``fitter.sampler.extend()`` and\n", + " comparing the posterior before and after.\n", "- Posterior inspection: summary table, marginal distributions, corner plot,\n", - " trace plot, credible intervals, Gelman-Rubin R-hat.\n", + " trace plot, credible intervals.\n", "- Posterior-predictive checks: reflectivity and SLD profile with 95 %\n", " credible bands.\n", "\n", + "The sampling run below is deliberately started short so that the diagnostic\n", + "flags it as not converged — this gives us something real to fix when we extend\n", + "the chain, rather than a cosmetic demonstration.\n", + "\n", "All posterior plots are rendered as **interactive Plotly figures**, the\n", "same ones shown by the EasyReflectometryApp Bayesian Posterior tab.\n", "\n", @@ -30,25 +37,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "61aa83ac", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:29:06.282846Z", - "iopub.status.busy": "2026-05-29T06:29:06.282846Z", - "iopub.status.idle": "2026-05-29T06:29:09.516170Z", - "shell.execute_reply": "2026-05-29T06:29:09.516170Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "All libraries imported successfully.\n" - ] - } - ], + "metadata": {}, + "outputs": [], "source": [ "import warnings\n", "\n", @@ -79,35 +71,10 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "14986b98", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:29:09.517778Z", - "iopub.status.busy": "2026-05-29T06:29:09.517778Z", - "iopub.status.idle": "2026-05-29T06:29:09.958508Z", - "shell.execute_reply": "2026-05-29T06:29:09.958508Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Data loaded with keys: ['data', 'coords', 'attrs']\n" - ] - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": {}, + "outputs": [], "source": [ "# ---- Load experimental data -------------------------------------------------\n", "# Fetch the .ort test data from the easyscience/reflectometry data repository.\n", @@ -134,29 +101,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "cd56a6eb", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:29:09.960548Z", - "iopub.status.busy": "2026-05-29T06:29:09.960548Z", - "iopub.status.idle": "2026-05-29T06:29:09.973595Z", - "shell.execute_reply": "2026-05-29T06:29:09.973595Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Model created with the following free parameters:\n", - " background: value=1e-06, bounds=(1e-07, 1e-05)\n", - " sld: value=2.0, bounds=(0.5, 4.0)\n", - " thickness: value=250.0, bounds=(100, 400)\n", - " scale: value=1.0, bounds=(0.8, 1.2)\n" - ] - } - ], + "metadata": {}, + "outputs": [], "source": [ "# ---- Create a monolayer model (Si / Film / D₂O) ----------------------------\n", "si = Material(sld=2.07, isld=0.0, name='Si')\n", @@ -204,25 +152,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "991e1169", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:29:09.974614Z", - "iopub.status.busy": "2026-05-29T06:29:09.974614Z", - "iopub.status.idle": "2026-05-29T06:29:09.982333Z", - "shell.execute_reply": "2026-05-29T06:29:09.982333Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Fitter ready with minimizer: Bumps\n" - ] - } - ], + "metadata": {}, + "outputs": [], "source": [ "# ---- Set up the calculator and fitter ---------------------------------------\n", "interface = CalculatorFactory()\n", @@ -238,36 +171,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "eb0f989e", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:29:09.983788Z", - "iopub.status.busy": "2026-05-29T06:29:09.983788Z", - "iopub.status.idle": "2026-05-29T06:29:11.760084Z", - "shell.execute_reply": "2026-05-29T06:29:11.760084Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Classical fit successful: True\n", - "Reduced χ² ≈ 73.7860402885366\n" - ] - }, - { - "data": { - "image/png": "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b5OaY0/QrZ636WfIc0cYBr5EREQ6EoyaY+/niDMZO962jcLLaATiEt0b3IuSBIVkxWWTA+oiHJcaIiIioi7zlD+MyE9VSxzLam9yKpneSF/mlvSHGV8iIiKKirZtRO1h4EtERERR0baNqD0sdSAiIiIiXWDgS0RERES6wFIHIiIi0i2XS2tVm0zRK+oD3/3792POnDk+l1955RXMnj07rOMiIiKi8Np6rKJ5MQ67w73YhiztPPeyi1CQEO7RUShEfeA7dOhQ7NixQ52vqalB//79MWPGjHAPi4iIiMIc9Er/4UpbI7It0n84XvUf3nO6Ck+s24d5k3IwPTs73MOkINNVje9bb72FadOmISkpKdxDISIiojCWN0imV4Le/n0SkRRvQozRoE4LMxJRXdeIV/9Zpu5H0SXsge8nn3yCG264Afn5+TAYDHjjjTda3eeZZ55RmVqz2YyJEydi8+bNXfpZa9as8Sl7ICIiIv2QQHZfaTVe23YSu09XIcsSr2IPb3I50xKHoxX1OFBWE7axUpSWOtTW1mLMmDG49957cfPNN7e6ffXq1ViwYAGef/55FfQuW7YMM2fOVLW62U1fQYwdOxYOh6PVYz/44AMVUIvq6mps3LgRq1at6oG9IiIiokit562y2XG2xo6a+kYUZCQhLSHW576y7LLdoanML0WXsAe+s2bNUlsgTz/9NO6//37cc8896rIEwGvXrsWf//xnPPLII+o6Tw1vW958801cc801KmvcloaGBrV5SMAsXC6X2rpKHqtpWreeg3oHzrV+cK71hfPde209dl7V7VbVNSLLEoeEODPO2+yornPg4BkrhmQnq44OHnV2J2JjDLDEx3C+e4mOzlPYA9+22O12bN26FT/72c+arzMajZg+fTo2bdrU6TKHBx54oN37LV68GI8++mir68+ePYv6+np0Z0KqqqrUH03ZB4penGv94FzrC+e7d3JpGl74+AgqaurQNzUeBrgQY9CQGBeD2gan6uZw7FwthsZIGweDmt8zVQ0YkGZCqsGGsrILyTCKXFartfcHvuXl5XA6ncjJyfG5Xi7v27evw88jf6ikLvi1115r974SZEtphXfGt6CgAFlZWUhJSenWH0ypG5Ln4R/M6Ma51g/Otb5wvnunfaVWnLI6kJuWCHP8hbCnsI8RB6WNmdMFm92JBqcRMTEGlNfYkZFs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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "metadata": {}, + "outputs": [], "source": [ "# ---- Classical fit first ----------------------------------------------------\n", "# A classical optimisation gives a good starting point and a sanity check\n", @@ -296,61 +203,44 @@ "cell_type": "code", "execution_count": null, "id": "20c9b0e0", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:29:11.762093Z", - "iopub.status.busy": "2026-05-29T06:29:11.762093Z", - "iopub.status.idle": "2026-05-29T06:30:41.903328Z", - "shell.execute_reply": "2026-05-29T06:30:41.903328Z" - } - }, - "outputs": [ - { - "ename": "TypeError", - "evalue": "Fitter.mcmc_sample() got an unexpected keyword argument 'chains'", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mTypeError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 10\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;66;03m# ---- Bayesian MCMC sampling -------------------------------------------------\u001b[39;00m\n\u001b[32m 2\u001b[39m \u001b[38;5;66;03m# ``MultiFitter.mcmc_sample()`` delegates to the BUMPS DREAM sampler.\u001b[39;00m\n\u001b[32m 3\u001b[39m \u001b[38;5;66;03m# All keyword arguments are forwarded with user-friendly names:\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 7\u001b[39m \u001b[38;5;66;03m# ``chains`` ← DREAM population count (alias for ``pop``)\u001b[39;00m\n\u001b[32m 8\u001b[39m \u001b[38;5;66;03m# ``population``← BUMPS‑native ``pop`` for advanced users\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m10\u001b[39m posterior_dict = \u001b[43mfitter\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmcmc_sample\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 11\u001b[39m \u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 12\u001b[39m \u001b[43m \u001b[49m\u001b[43msamples\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m2000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Short for demo; use 20 k+ in production\u001b[39;49;00m\n\u001b[32m 13\u001b[39m \u001b[43m \u001b[49m\u001b[43mburn\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m500\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 14\u001b[39m \u001b[43m \u001b[49m\u001b[43mthin\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m10\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 15\u001b[39m \u001b[43m)\u001b[49m\n\u001b[32m 17\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m'\u001b[39m\u001b[33mDREAM sampling complete.\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m 18\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m'\u001b[39m\u001b[33m Posterior shape : \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mposterior_dict[\u001b[33m\"\u001b[39m\u001b[33mdraws\u001b[39m\u001b[33m\"\u001b[39m].shape\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~\\projects\\easy\\ERA\\reflectometry-lib\\src\\easyreflectometry\\fitting.py:451\u001b[39m, in \u001b[36mMultiFitter.mcmc_sample\u001b[39m\u001b[34m(self, data, samples, burn, thin, chains, population, objective, initializer, resume_state, progress_callback, abort_test)\u001b[39m\n\u001b[32m 449\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m initializer \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 450\u001b[39m sampler_kwargs[\u001b[33m'\u001b[39m\u001b[33minit\u001b[39m\u001b[33m'\u001b[39m] = initializer\n\u001b[32m--> \u001b[39m\u001b[32m451\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43measy_science_multi_fitter\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmcmc_sample\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 452\u001b[39m \u001b[43m \u001b[49m\u001b[43mx\u001b[49m\u001b[43m=\u001b[49m\u001b[43mx\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 453\u001b[39m \u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m=\u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 454\u001b[39m \u001b[43m \u001b[49m\u001b[43mweights\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdy\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 455\u001b[39m \u001b[43m \u001b[49m\u001b[43msamples\u001b[49m\u001b[43m=\u001b[49m\u001b[43msamples\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 456\u001b[39m \u001b[43m \u001b[49m\u001b[43mburn\u001b[49m\u001b[43m=\u001b[49m\u001b[43mburn\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 457\u001b[39m \u001b[43m \u001b[49m\u001b[43mthin\u001b[49m\u001b[43m=\u001b[49m\u001b[43mthin\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 458\u001b[39m \u001b[43m \u001b[49m\u001b[43mchains\u001b[49m\u001b[43m=\u001b[49m\u001b[43mchains\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 459\u001b[39m \u001b[43m \u001b[49m\u001b[43mpopulation\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpopulation\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 460\u001b[39m \u001b[43m \u001b[49m\u001b[43mresume_state\u001b[49m\u001b[43m=\u001b[49m\u001b[43mresume_state\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 461\u001b[39m \u001b[43m \u001b[49m\u001b[43msampler_kwargs\u001b[49m\u001b[43m=\u001b[49m\u001b[43msampler_kwargs\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 462\u001b[39m \u001b[43m \u001b[49m\u001b[43mprogress_callback\u001b[49m\u001b[43m=\u001b[49m\u001b[43mprogress_callback\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 463\u001b[39m \u001b[43m \u001b[49m\u001b[43mabort_test\u001b[49m\u001b[43m=\u001b[49m\u001b[43mabort_test\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 464\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[31mTypeError\u001b[39m: Fitter.mcmc_sample() got an unexpected keyword argument 'chains'" - ] - } - ], + "metadata": {}, + "outputs": [], "source": [ "# ---- Bayesian MCMC sampling -------------------------------------------------\n", "# ``MultiFitter.mcmc_sample()`` delegates to the BUMPS DREAM sampler.\n", "# All keyword arguments are forwarded with user-friendly names:\n", "# ``samples`` ← total retained samples\n", - "# ``burn`` ← burn‑in steps\n", + "# ``burn`` ← burn-in steps\n", "# ``thin`` ← thinning interval\n", - "# ``population``← BUMPS‑native ``pop`` for advanced users\n", + "# ``population``← BUMPS-native ``pop`` for advanced users\n", + "#\n", + "# We deliberately start with a *very* short run. It finishes in seconds but is\n", + "# far too short to trust — which is exactly the situation the \"extend the chain\"\n", + "# section below exists to fix. In production you would ask for 20 k+ samples.\n", "\n", "posterior_dict = fitter.mcmc_sample(\n", " data,\n", - " samples=2000, # Short for demo; use 20 k+ in production\n", - " burn=500,\n", + " samples=500, # Deliberately too short — extended later in this notebook\n", + " burn=100,\n", " thin=10,\n", ")\n", "\n", "print('DREAM sampling complete.')\n", "print(f' Posterior shape : {posterior_dict[\"draws\"].shape}')\n", - "print(f' Parameters : {posterior_dict[\"param_names\"]}')" + "print(f' Parameters : {posterior_dict[\"param_names\"]}')\n", + "print()\n", + "print(\n", + " 'Note: the retained draws are fewer than samples/thin. BUMPS trims the\\n'\n", + " 'chain at a detected burn point and drops outlier chains, so the number of\\n'\n", + " 'rows is not predictable from the arguments — read it off the array.'\n", + ")" ] }, { "cell_type": "code", "execution_count": null, "id": "f23934a0", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:41.905335Z", - "iopub.status.busy": "2026-05-29T06:30:41.905335Z", - "iopub.status.idle": "2026-05-29T06:30:41.908528Z", - "shell.execute_reply": "2026-05-29T06:30:41.908528Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Wrap in PosteriorResults -----------------------------------------------\n", @@ -372,14 +262,7 @@ "cell_type": "code", "execution_count": null, "id": "ef300cb1", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:41.909855Z", - "iopub.status.busy": "2026-05-29T06:30:41.909855Z", - "iopub.status.idle": "2026-05-29T06:30:41.913882Z", - "shell.execute_reply": "2026-05-29T06:30:41.913882Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Posterior summary table ------------------------------------------------\n", @@ -388,6 +271,248 @@ "print(posterior.summary())" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "34e3cc3e", + "metadata": {}, + "outputs": [], + "source": [ + "# ---- Gelman-Rubin R-hat convergence diagnostic ------------------------------\n", + "# R-hat compares the variance *between* DREAM's parallel chains with the\n", + "# variance *within* each one. Values close to 1.0 mean the chains have mixed\n", + "# and agree on the same posterior; the usual rule of thumb is R̂ < 1.1.\n", + "#\n", + "# ``posterior.gelman_rubin()`` needs draws pre-split into chains, shape\n", + "# ``(n_chains, n_draws, n_params)``. ``mcmc_sample()`` returns the draws\n", + "# already pooled into a flat ``(n_samples, n_params)`` array, so we ask the\n", + "# BUMPS state itself — it still knows the individual chains.\n", + "\n", + "\n", + "def rhat_table(state, param_names):\n", + " \"\"\"R-hat per parameter, read from the chain-aware BUMPS state.\"\"\"\n", + " return dict(zip(param_names, state.gelman()))\n", + "\n", + "\n", + "rhat_short = rhat_table(posterior.sampler_state, posterior.param_names)\n", + "\n", + "print('Gelman-Rubin R-hat after the short run:')\n", + "for name, r in rhat_short.items():\n", + " flag = ' ✓' if r < 1.1 else ' ⚠ not converged'\n", + " print(f' {name:<30s} R̂ = {r:.3f}{flag}')\n", + "\n", + "# DREAM is stochastic and unseeded, so the exact values differ run to run.\n", + "n_bad = sum(r >= 1.1 for r in rhat_short.values())\n", + "if n_bad:\n", + " print(f'\\n{n_bad} parameter(s) above 1.1 — this chain is not converged and needs more samples.')\n", + "else:\n", + " print(\n", + " f'\\nR̂ passes, but this chain holds only {posterior.draws.shape[0]} draws — '\n", + " 'far too few to rely on,\\nso every posterior estimate from it is coarse. '\n", + " 'Either way, the answer is more samples.'\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "223f79f1", + "metadata": {}, + "source": [ + "## Extending the chain\n", + "\n", + "The short run above is not something to draw conclusions from — it usually\n", + "fails the R̂ < 1.1 check outright, and even when it scrapes past it, it holds\n", + "only a few dozen draws. The fix is more samples, but restarting from scratch\n", + "would throw away the work already done and pay the burn-in cost a second time.\n", + "\n", + "Instead, **continue the existing chain**. `MultiFitter.mcmc_sample()` keeps the\n", + "underlying `Sampler` on `fitter.sampler`, and `Sampler.extend()` picks the chain\n", + "up exactly where DREAM left off:\n", + "\n", + "```python\n", + "extended = fitter.sampler.extend(additional_samples=8000, thin=10)\n", + "```\n", + "\n", + "`extend()` runs with `burn=0` — re-burning an already-converged chain would be a\n", + "mistake, and BUMPS forces it to 0 on resume anyway. It also grows DREAM's\n", + "fixed-size ring buffer by exactly `additional_samples`, so none of the existing\n", + "draws are evicted.\n", + "\n", + "### What \"improvement\" does and does not mean here\n", + "\n", + "Two things get better, and one thing deliberately does not:\n", + "\n", + "* **R̂ drops** — the chains mix and settle onto the same posterior.\n", + "* **The Monte-Carlo error shrinks** — with more draws the posterior *estimate*\n", + " (mean, credible interval, histogram shape) stops moving from run to run.\n", + "* **The posterior width does _not_ shrink.** The spread of a parameter is set by\n", + " the data and the model, not by how long you sample. If the credible interval\n", + " collapsed as we added samples, that would be a bug, not a win. What improves is\n", + " how precisely we know that interval.\n", + "\n", + "DREAM is stochastic and this notebook does not seed it, so the exact numbers\n", + "below change on every run. The direction of travel does not." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5d26166", + "metadata": {}, + "outputs": [], + "source": [ + "# ---- Continue the existing chain --------------------------------------------\n", + "# ``extend()`` mutates the sampler's BUMPS state in place, so snapshot anything\n", + "# we want to compare against first. ``rhat_short`` (plain floats) was already\n", + "# captured above; the draws need an explicit copy.\n", + "\n", + "draws_short = np.array(posterior.draws, copy=True)\n", + "n_short = draws_short.shape[0]\n", + "\n", + "extended_results = fitter.sampler.extend(\n", + " additional_samples=8000, # Added on top of the original 2000\n", + " thin=10,\n", + ")\n", + "\n", + "posterior_extended = PosteriorResults(\n", + " draws=extended_results.draws,\n", + " param_names=extended_results.param_names,\n", + " logp=extended_results.logp,\n", + " sampler_state=extended_results.state,\n", + ")\n", + "\n", + "print('Chain extended.')\n", + "print(f' Retained draws before : {n_short}')\n", + "print(f' Retained draws after : {posterior_extended.draws.shape[0]}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6b17af8", + "metadata": {}, + "outputs": [], + "source": [ + "# ---- Did convergence actually improve? --------------------------------------\n", + "# The headline check: R-hat before vs after.\n", + "\n", + "rhat_extended = rhat_table(posterior_extended.sampler_state, posterior_extended.param_names)\n", + "\n", + "print(f'{\"parameter\":<32s} {\"short\":>8s} {\"extended\":>10s}')\n", + "print('-' * 54)\n", + "for name in posterior_extended.param_names:\n", + " before, after = rhat_short[name], rhat_extended[name]\n", + " flag = ' ✓' if after < 1.1 else ' ⚠'\n", + " print(f'{name:<32s} {before:8.3f} {after:10.3f}{flag}')\n", + "\n", + "n_bad_before = sum(r >= 1.1 for r in rhat_short.values())\n", + "n_bad_after = sum(r >= 1.1 for r in rhat_extended.values())\n", + "print(f'\\nParameters failing R̂ < 1.1: {n_bad_before} → {n_bad_after}')\n", + "print(f'Worst R̂ across parameters: {max(rhat_short.values()):.3f} → {max(rhat_extended.values()):.3f}')\n", + "\n", + "if n_bad_after == 0 and n_bad_before > 0:\n", + " print('\\nThe extended chain has converged; the short one had not.')\n", + "elif n_bad_after == 0:\n", + " print('\\nBoth chains pass R̂, but the extended one does so with far more draws behind it.')\n", + "else:\n", + " print('\\nStill above threshold — extend again, or revisit the model and its bounds.')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f620d42d", + "metadata": {}, + "outputs": [], + "source": [ + "# ---- Side-by-side posterior statistics --------------------------------------\n", + "# ``extend()`` returns the parameters in the same order as the original run, so\n", + "# the columns line up — but map by name rather than trusting the index.\n", + "\n", + "col_short = {name: i for i, name in enumerate(posterior.param_names)}\n", + "col_ext = {name: i for i, name in enumerate(posterior_extended.param_names)}\n", + "\n", + "\n", + "def ci_width(column, alpha=0.95):\n", + " lo, hi = np.percentile(column, [100 * (1 - alpha) / 2, 100 * (1 + alpha) / 2])\n", + " return hi - lo\n", + "\n", + "\n", + "header = (\n", + " f'{\"parameter\":<32s} {\"mean (short)\":>13s} {\"mean (ext)\":>13s} {\"shift\":>8s} {\"95% w (short)\":>14s} {\"95% w (ext)\":>13s}'\n", + ")\n", + "print(header)\n", + "print('-' * len(header))\n", + "\n", + "for name in posterior_extended.param_names:\n", + " a = draws_short[:, col_short[name]]\n", + " b = posterior_extended.draws[:, col_ext[name]]\n", + " # Mean shift expressed in extended-posterior standard deviations: how far the\n", + " # short run's answer sits from the better-resolved one, in units that matter.\n", + " shift = abs(np.mean(a) - np.mean(b)) / np.std(b)\n", + " print(f'{name:<32s} {np.mean(a):13.5g} {np.mean(b):13.5g} {shift:7.2f}σ {ci_width(a):14.4g} {ci_width(b):13.4g}')\n", + "\n", + "print(\n", + " '\\nRead this alongside the R̂ table, not on its own. The credible intervals do\\n'\n", + " 'not systematically shrink — they should not, since posterior width is set by\\n'\n", + " 'the data, not by sampling effort. What extending buys is trust: the means\\n'\n", + " 'stop drifting and the marginals below are resolved by many more draws.'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67c37350", + "metadata": {}, + "outputs": [], + "source": [ + "# ---- Visual overlay: marginals, short vs extended ----------------------------\n", + "# Same posterior, better resolved. The short run's histogram is ragged because\n", + "# it is built from few draws; the extended one traces a smooth marginal over the\n", + "# same range. Densities are normalised so the two are comparable despite the\n", + "# very different sample counts.\n", + "\n", + "n_params = len(posterior_extended.param_names)\n", + "fig, axes = plt.subplots(1, n_params, figsize=(4 * n_params, 3.2))\n", + "axes = np.atleast_1d(axes)\n", + "\n", + "for ax, name in zip(axes, posterior_extended.param_names):\n", + " a = draws_short[:, col_short[name]]\n", + " b = posterior_extended.draws[:, col_ext[name]]\n", + " bins = np.histogram_bin_edges(np.concatenate([a, b]), bins=30)\n", + " ax.hist(a, bins=bins, density=True, alpha=0.55, label=f'short (n={a.shape[0]})')\n", + " ax.hist(b, bins=bins, density=True, alpha=0.55, label=f'extended (n={b.shape[0]})')\n", + " ax.set_title(name, fontsize=9)\n", + " ax.set_ylabel('density')\n", + " ax.legend(fontsize=8)\n", + " ax.grid(True, alpha=0.3)\n", + "\n", + "fig.suptitle('Marginal posterior: original chain vs extended chain')\n", + "fig.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e46c3dd4", + "metadata": {}, + "outputs": [], + "source": [ + "# ---- Adopt the extended chain for the rest of the notebook -------------------\n", + "# Everything below (corner plots, credible intervals, posterior-predictive\n", + "# checks) now runs on the converged chain. ``posterior_short`` is kept around\n", + "# in case you want to re-run the comparisons above.\n", + "\n", + "posterior_short = posterior\n", + "posterior = posterior_extended\n", + "\n", + "print(posterior)\n", + "print()\n", + "print(posterior.summary())" + ] + }, { "cell_type": "markdown", "id": "d1f0a300", @@ -413,14 +538,7 @@ "cell_type": "code", "execution_count": null, "id": "5b9d3c2a", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:41.914887Z", - "iopub.status.busy": "2026-05-29T06:30:41.914887Z", - "iopub.status.idle": "2026-05-29T06:30:43.463799Z", - "shell.execute_reply": "2026-05-29T06:30:43.463252Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Marginal posterior distributions ---------------------------------------\n", @@ -448,14 +566,7 @@ "cell_type": "code", "execution_count": null, "id": "e759a2a0", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:43.464801Z", - "iopub.status.busy": "2026-05-29T06:30:43.464801Z", - "iopub.status.idle": "2026-05-29T06:30:43.847435Z", - "shell.execute_reply": "2026-05-29T06:30:43.847435Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Corner plot ------------------------------------------------------------\n", @@ -470,14 +581,7 @@ "cell_type": "code", "execution_count": null, "id": "593f5ed3", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:43.852778Z", - "iopub.status.busy": "2026-05-29T06:30:43.852778Z", - "iopub.status.idle": "2026-05-29T06:30:43.905089Z", - "shell.execute_reply": "2026-05-29T06:30:43.905089Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Trace plot -------------------------------------------------------------\n", @@ -492,14 +596,7 @@ "cell_type": "code", "execution_count": null, "id": "3049e68a", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:43.906601Z", - "iopub.status.busy": "2026-05-29T06:30:43.906601Z", - "iopub.status.idle": "2026-05-29T06:30:43.912231Z", - "shell.execute_reply": "2026-05-29T06:30:43.911707Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Credible intervals -----------------------------------------------------\n", @@ -524,14 +621,7 @@ "cell_type": "code", "execution_count": null, "id": "806bf47a", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:43.913735Z", - "iopub.status.busy": "2026-05-29T06:30:43.913735Z", - "iopub.status.idle": "2026-05-29T06:30:44.004052Z", - "shell.execute_reply": "2026-05-29T06:30:44.004052Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Pairwise focus: thickness vs SLD ---------------------------------------\n", @@ -548,49 +638,11 @@ "fig.show()" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "34e3cc3e", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:44.006603Z", - "iopub.status.busy": "2026-05-29T06:30:44.006603Z", - "iopub.status.idle": "2026-05-29T06:30:44.010758Z", - "shell.execute_reply": "2026-05-29T06:30:44.010226Z" - } - }, - "outputs": [], - "source": [ - "# ---- Gelman-Rubin R‑hat convergence diagnostic ------------------------------\n", - "# Requires ``arviz`` and at least 2 chains. Values close to 1.0 indicate\n", - "# good convergence.\n", - "\n", - "try:\n", - " rhat = posterior.gelman_rubin()\n", - " print('Gelman-Rubin R‑hat:')\n", - " for name, r in rhat.items():\n", - " flag = ' ✓' if r < 1.1 else ' ⚠'\n", - " print(f' {name:<30s} R̂ = {r:.3f}{flag}')\n", - "except ValueError:\n", - " print(\n", - " 'Skipped: Gelman-Rubin R‑hat requires at least 2 chains. '\n", - " 'Run with multiple DREAM populations to obtain multi-chain draws.'\n", - " )" - ] - }, { "cell_type": "code", "execution_count": null, "id": "c7e5816e", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:44.012760Z", - "iopub.status.busy": "2026-05-29T06:30:44.012760Z", - "iopub.status.idle": "2026-05-29T06:30:45.165079Z", - "shell.execute_reply": "2026-05-29T06:30:45.165079Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Posterior-predictive reflectivity --------------------------------------\n", @@ -630,14 +682,7 @@ "cell_type": "code", "execution_count": null, "id": "ef27b8ec", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:45.166642Z", - "iopub.status.busy": "2026-05-29T06:30:45.166642Z", - "iopub.status.idle": "2026-05-29T06:30:45.413146Z", - "shell.execute_reply": "2026-05-29T06:30:45.413146Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Posterior-predictive SLD profile ---------------------------------------\n", @@ -671,14 +716,7 @@ "cell_type": "code", "execution_count": null, "id": "37e78b4e", - "metadata": { - "execution": { - "iopub.execute_input": "2026-05-29T06:30:45.415149Z", - "iopub.status.busy": "2026-05-29T06:30:45.415149Z", - "iopub.status.idle": "2026-05-29T06:30:45.419700Z", - "shell.execute_reply": "2026-05-29T06:30:45.419700Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# ---- Summary ----------------------------------------------------------------\n", @@ -688,13 +726,17 @@ "print()\n", "print('API surface demonstrated:')\n", "print(' MultiFitter.mcmc_sample(data, samples=, burn=, thin=)')\n", + "print(' MultiFitter.sampler — the Sampler behind the last run')\n", + "print(' .extend(additional_samples=, thin=)')\n", + "print(' — continue the chain, no re-burn')\n", + "print(' .state.gelman() — chain-aware R̂ (works on pooled draws)')\n", "print(' PosteriorResults(draws, param_names, logp=, sampler_state=)')\n", "print(' .summary() — formatted parameter table')\n", "print(' .distribution() — per-parameter marginal Plotly figure')\n", "print(' .corner() — pairwise correlation Plotly figure')\n", "print(' .trace() — MCMC chain trace plot')\n", "print(' .credible_interval(alpha) — equal-tailed credible intervals')\n", - "print(' .gelman_rubin() — R̂ convergence diagnostic')\n", + "print(' .gelman_rubin() — R̂ for draws pre-split into chains')\n", "print()\n", "print(' Standalone functions (work on raw dict; the plot helpers return')\n", "print(' the same interactive Plotly figures as the App):')\n", @@ -706,7 +748,8 @@ "print(' posterior_predictive_reflectivity(draws, names, model, q, n)')\n", "print(' posterior_predictive_sld_profile(draws, names, model, n)')\n", "print()\n", - "print('The high-level API provides clean, safe access to BUMPS DREAM sampling.')" + "print('Workflow: fit → sample short → check R̂ → extend until converged → analyse.')\n", + "print('Extending continues the existing chain, so the burn-in is paid only once.')" ] } ], @@ -726,7 +769,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.11" + "version": "3.12.12" } }, "nbformat": 4, diff --git a/docs/docs/tutorials/simulation/bilayer.ipynb b/docs/docs/tutorials/simulation/bilayer.ipynb index 987cd8e1..5f37b010 100644 --- a/docs/docs/tutorials/simulation/bilayer.ipynb +++ b/docs/docs/tutorials/simulation/bilayer.ipynb @@ -331,7 +331,7 @@ "q = np.linspace(0.005, 0.3, 500)\n", "\n", "# Calculate reflectometry\n", - "reflectivity = model.interface().reflectivity_profile(q, model.unique_name)\n", + "reflectivity = model.interface().reflectity_profile(q, model.unique_name)\n", "\n", "# Plot\n", "plt.figure(figsize=(10, 6))\n", @@ -391,7 +391,7 @@ "outputs": [], "source": [ "# First, compute reflectivity with current conformal roughness (3.0 Å)\n", - "reflectivity_conformal = model.interface().reflectivity_profile(q, model.unique_name)\n", + "reflectivity_conformal = model.interface().reflectity_profile(q, model.unique_name)\n", "\n", "# Disable conformal roughness to allow independent roughness per layer\n", "bilayer.conformal_roughness = False\n", @@ -406,7 +406,7 @@ "bilayer.back_head_layer.roughness.value = 4.0\n", "\n", "# Compute reflectivity with variable roughness\n", - "reflectivity_variable_roughness = model.interface().reflectivity_profile(q, model.unique_name)\n", + "reflectivity_variable_roughness = model.interface().reflectity_profile(q, model.unique_name)\n", "\n", "# Plot comparison\n", "plt.figure(figsize=(10, 6))\n", @@ -629,8 +629,8 @@ "outputs": [], "source": [ "# Calculate reflectivity for both contrasts\n", - "reflectivity_d2o = model.interface().reflectivity_profile(q, model.unique_name)\n", - "reflectivity_h2o = model_h2o.interface().reflectivity_profile(q, model_h2o.unique_name)\n", + "reflectivity_d2o = model.interface().reflectity_profile(q, model.unique_name)\n", + "reflectivity_h2o = model_h2o.interface().reflectity_profile(q, model_h2o.unique_name)\n", "\n", "plt.figure(figsize=(10, 6))\n", "plt.semilogy(q, reflectivity_d2o, 'b-', linewidth=2, label='D₂O contrast')\n", diff --git a/docs/docs/tutorials/simulation/magnetism.ipynb b/docs/docs/tutorials/simulation/magnetism.ipynb index ef226c41..8efdb9e4 100644 --- a/docs/docs/tutorials/simulation/magnetism.ipynb +++ b/docs/docs/tutorials/simulation/magnetism.ipynb @@ -252,7 +252,7 @@ "model.resolution_function = PercentageFwhm(0)\n", "model_interface = model.interface()\n", "model_interface.magnetism = False\n", - "model_data_no_magnetism_ref1d_easy = model.interface().reflectivity_profile(\n", + "model_data_no_magnetism_ref1d_easy = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -284,7 +284,7 @@ "model.interface = interface\n", "model_interface = model.interface()\n", "model_interface.include_magnetism = True\n", - "model_data_magnetism = model.interface().reflectivity_profile(\n", + "model_data_magnetism = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -301,7 +301,7 @@ "model_interface._wrapper.update_layer(\n", " list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175\n", ")\n", - "model_data_magnetism_layer_1 = model.interface().reflectivity_profile(\n", + "model_data_magnetism_layer_1 = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -365,7 +365,7 @@ "model_interface._wrapper.update_layer(\n", " list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175\n", ")\n", - "model_data_magnetism_easy = model.interface().reflectivity_profile(\n", + "model_data_magnetism_easy = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -478,7 +478,7 @@ "interface.switch('refnx')\n", "model.interface = interface\n", "model_interface = model.interface()\n", - "model_data_no_magnetism_refnx = model.interface().reflectivity_profile(\n", + "model_data_no_magnetism_refnx = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -488,7 +488,7 @@ "interface.switch('refl1d')\n", "model.interface = interface\n", "model_interface = model.interface()\n", - "model_data_no_magnetism_ref1d = model.interface().reflectivity_profile(\n", + "model_data_no_magnetism_ref1d = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -518,7 +518,7 @@ "model.interface = interface\n", "model_interface = model.interface()\n", "model_interface.magnetism = True\n", - "model_data_magnetism = model.interface().reflectivity_profile(\n", + "model_data_magnetism = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -529,7 +529,7 @@ "model.interface = interface\n", "model_interface = model.interface()\n", "model_interface.magnetism = False\n", - "model_data_no_magnetism = model.interface().reflectivity_profile(\n", + "model_data_no_magnetism = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", diff --git a/docs/docs/tutorials/simulation/resolution_functions.ipynb b/docs/docs/tutorials/simulation/resolution_functions.ipynb index cc7abe10..d46a76cb 100644 --- a/docs/docs/tutorials/simulation/resolution_functions.ipynb +++ b/docs/docs/tutorials/simulation/resolution_functions.ipynb @@ -310,7 +310,7 @@ " num=1000,\n", " )\n", " model.resolution_function = resolution_function_dict[key]\n", - " model_data = model.interface().reflectivity_profile(\n", + " model_data = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", " )\n", @@ -363,14 +363,14 @@ ")\n", "\n", "model.resolution_function = resolution_function_dict[key]\n", - "model_data = model.interface().reflectivity_profile(\n", + "model_data = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", "plt.plot(model_coords, model_data, 'k-', label='Variable', linewidth=5)\n", "\n", "model.resolution_function = PercentageFwhm(1.0)\n", - "model_data = model.interface().reflectivity_profile(\n", + "model_data = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -402,7 +402,7 @@ ")\n", "\n", "model.resolution_function = resolution_function_dict[key]\n", - "model_data = model.interface().reflectivity_profile(\n", + "model_data = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", @@ -415,7 +415,7 @@ "data_points.append(reference_data) # R\n", "data_points.append(reference_variances) # sQz (variance of Qz)\n", "model.resolution_function = Pointwise(q_data_points=data_points)\n", - "model_data = model.interface().reflectivity_profile(\n", + "model_data = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", diff --git a/pixi.lock b/pixi.lock index 983bc0bd..36a67a04 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,21 +1,8 @@ version: 7 platforms: - name: linux-64 - virtual-packages: - - __unix=0=0 - - __linux=4.18 - - __glibc=2.28 - - __archspec=0=x86_64 -- name: p1 - subdir: osx-arm64 - virtual-packages: - - __osx=14.0 - - __unix=0=0 - - __archspec=0=m1 +- name: osx-arm64 - name: win-64 - virtual-packages: - - __win=10.0 - - __archspec=0=x86_64 environments: default: channels: @@ -202,8 +189,8 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -339,7 +326,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - p1: + osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -513,8 +500,8 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -814,8 +801,8 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -1137,8 +1124,8 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1272,7 +1259,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - p1: + osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda @@ -1442,8 +1429,8 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1737,8 +1724,8 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2060,8 +2047,8 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2197,7 +2184,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - p1: + osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -2371,8 +2358,8 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2672,8 +2659,8 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: ./ - - pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 + - pypi: . + - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -3083,7 +3070,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - p1: + osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -8310,11 +8297,11 @@ packages: purls: [] size: 388453 timestamp: 1764777142545 -- pypi: ./ +- pypi: . name: easyreflectometry requires_dist: - bumps - - easyscience @ git+https://github.com/easyscience/corelib.git@develop + - easyscience @ git+https://github.com/easyscience/corelib.git@bayesian_extend - orsopy - plotly - pooch @@ -8358,9 +8345,9 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.11' -- pypi: git+https://github.com/easyscience/corelib.git?rev=develop#aadbd4891b94f6aa18187d48be8c2ab6f81113b0 +- pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca name: easyscience - version: 2.3.1+dev8 + version: 2.4.0+dev17 requires_dist: - asteval - bumps diff --git a/pixi.toml b/pixi.toml index b0fb2b8d..ab6c977e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -94,7 +94,15 @@ user = { features = ['py-max', 'user'] } unit-tests = 'python -m pytest tests/unit/ --color=yes -v' functional-tests = 'python -m pytest tests/functional/ --color=yes -v' -integration-tests = 'python -m pytest tests/integration/ --color=yes -n auto -v' +# No -n auto: importing easyreflectometry pulls in arviz, and arviz 0.23.4 +# (py-311-env) writes a "warn once per day" stamp file on import via a +# _atomic_write_text() that is not atomic -- every process writes the same +# fixed `daily_warning.tmp` and renames it onto `daily_warning`. Concurrent +# xdist workers therefore race on that rename: FileNotFoundError on Linux, +# PermissionError (WinError 32) on Windows. The real fix is +# to stop importing arviz in easyreflectometry/__init__.py, after which +# xdist can come back. +integration-tests = 'python -m pytest tests/integration/ --color=yes -v' notebook-tests = 'python -m pytest --nbmake docs/docs/tutorials/**/ --nbmake-timeout=1200 --color=yes -n auto -v' test = { depends-on = ['unit-tests'] } diff --git a/src/easyreflectometry/analysis/bayesian.py b/src/easyreflectometry/analysis/bayesian.py index 39389a84..17ab252c 100644 --- a/src/easyreflectometry/analysis/bayesian.py +++ b/src/easyreflectometry/analysis/bayesian.py @@ -1332,8 +1332,9 @@ def load_posterior(path: str, skip: int = 0) -> 'PosteriorResults': """Reload a trace saved by :func:`save_posterior` into a :class:`PosteriorResults`. - The returned object's ``sampler_state`` can be fed back into - ``MultiFitter.mcmc_sample(..., resume_state=...)`` to extend the chain. + The returned object's ``sampler_state`` can be fed back into the core + ``Sampler`` (via ``Sampler.load_state(...)`` / ``Sampler.extend(...)``) + to extend the chain. :param path: File path prefix used in :func:`save_posterior`. :type path: str diff --git a/src/easyreflectometry/calculators/calculator_base.py b/src/easyreflectometry/calculators/calculator_base.py index 06fa5f07..e2a92804 100644 --- a/src/easyreflectometry/calculators/calculator_base.py +++ b/src/easyreflectometry/calculators/calculator_base.py @@ -189,7 +189,7 @@ def remove_item_from_model(self, item_id: str, model_id: str) -> None: """ self._wrapper.remove_item(item_id, model_id) - def reflectivity_profile(self, x_array: np.ndarray, model_id: str) -> np.ndarray: + def reflectity_profile(self, x_array: np.ndarray, model_id: str) -> np.ndarray: """Determines the reflectivity profile for the given range and model. Parameters diff --git a/src/easyreflectometry/calculators/factory.py b/src/easyreflectometry/calculators/factory.py index b1c1b170..c3e1479c 100644 --- a/src/easyreflectometry/calculators/factory.py +++ b/src/easyreflectometry/calculators/factory.py @@ -40,6 +40,6 @@ def fit_func(self) -> Callable: def __fit_func(*args, **kwargs): """Fit func.""" - return self().reflectivity_profile(*args, **kwargs) + return self().reflectity_profile(*args, **kwargs) return __fit_func diff --git a/src/easyreflectometry/calculators/refl1d/wrapper.py b/src/easyreflectometry/calculators/refl1d/wrapper.py index 7a07c917..6985d47c 100644 --- a/src/easyreflectometry/calculators/refl1d/wrapper.py +++ b/src/easyreflectometry/calculators/refl1d/wrapper.py @@ -8,8 +8,6 @@ from refl1d import names from refl1d.sample.layers import Repeat -from easyreflectometry.model import PercentageFwhm - from ..wrapper_base import WrapperBase RESOLUTION_PADDING = 3.5 @@ -205,12 +203,9 @@ def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: Reflectivity calculated at q. """ sample = _build_sample(self.storage, model_name) + # smearing() returns sigma, which is exactly what refl1d's probe.dQ expects. dq_array = self._resolution_function.smearing(q_array) - if isinstance(self._resolution_function, PercentageFwhm): - # Get percentage of Q and change from sigma to FWHM - dq_array = dq_array * q_array / 100 / (2 * np.sqrt(2 * np.log(2))) - if not self._magnetism: probe = _get_probe( q_array=q_array, diff --git a/src/easyreflectometry/calculators/refnx/wrapper.py b/src/easyreflectometry/calculators/refnx/wrapper.py index 3742d727..65dc8662 100644 --- a/src/easyreflectometry/calculators/refnx/wrapper.py +++ b/src/easyreflectometry/calculators/refnx/wrapper.py @@ -8,6 +8,7 @@ from refnx import reflect from easyreflectometry.model import PercentageFwhm +from easyreflectometry.model.resolution_functions import SIGMA_TO_FWHM from ..wrapper_base import WrapperBase @@ -191,9 +192,12 @@ def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: dq_vector = self._resolution_function.smearing(q_array) if isinstance(self._resolution_function, PercentageFwhm): - # FWHM Percentage resolution is constant given as - # For a constant resolution percentage refnx supports to pass a scalar value rather than a vector - dq_vector = dq_vector[0] + # refnx interprets a scalar x_err as a constant dq/q (FWHM percentage), + # so pass the percentage directly rather than a per-point vector. + dq_vector = self._resolution_function.constant + else: + # smearing() returns sigma; refnx expects the FWHM at each point. + dq_vector = dq_vector * SIGMA_TO_FWHM return model(x=q_array, x_err=dq_vector) diff --git a/src/easyreflectometry/fitting.py b/src/easyreflectometry/fitting.py index 4cd24004..0efaaaa5 100644 --- a/src/easyreflectometry/fitting.py +++ b/src/easyreflectometry/fitting.py @@ -10,6 +10,7 @@ import scipp as sc from easyscience.fitting import AvailableMinimizers from easyscience.fitting import FitResults +from easyscience.fitting import Sampler from easyscience.fitting.multi_fitter import MultiFitter as EasyScienceMultiFitter from easyreflectometry.data import DataSet1D @@ -194,6 +195,7 @@ def wrapped(*args, **kwargs): self._fit_results: list[FitResults] | None = None self._classical_fit_metrics: list[dict] | None = None self._objective = _validate_objective(objective) + self._sampler: Sampler | None = None def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> sc.DataGroup: """Perform the fitting and populate the DataGroups with the result. @@ -355,24 +357,6 @@ def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) ] return result - def record_fit_results(self, results: list[FitResults] | None) -> None: - """Record fit results produced outside this fitter instance. - - The application runs threaded fits directly on the lower-level - ``easy_science_multi_fitter`` (for progress reporting and cancellation) - rather than through :meth:`fit`. As a result, the high-level - ``_fit_results`` used by :attr:`chi2`, :attr:`reduced_chi`, and the - HTML summary's goodness-of-fit are never populated. Call this after such - a fit so those consumers reflect the latest results. Pass ``None`` to - clear. - - :param results: The list of ``FitResults`` from the completed fit, or - ``None`` to reset. - """ - self._fit_results = results - if not results: - self._classical_fit_metrics = None - def mcmc_sample( self, data: sc.DataGroup, @@ -399,12 +383,18 @@ def mcmc_sample( :param initializer: DREAM population initializer. One of ``'eps'``, ``'cov'``, ``'lhs'``, or ``'random'``. By default, None (BUMPS uses ``'eps'``). - — the population already exists in the saved state. :param progress_callback: Optional callback for progress updates during sampling. Forwarded to the core MultiFitter. - :return: Dictionary with keys ``'draws'``, ``'param_names'``, - ``'internal_bumps_object'``, and ``'logp'``. + :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, + and ``'logp'``. :raises RuntimeError: If the current minimizer is not a BUMPS instance. + + The underlying :class:`~easyscience.fitting.Sampler` is retained on + :attr:`sampler`, so the chain can be continued without re-running the + burn-in:: + + fitter.mcmc_sample(data, samples=2000, burn=500, thin=10) + extended = fitter.sampler.extend(additional_samples=8000, thin=10) """ minimizer = self.easy_science_multi_fitter.minimizer if not (hasattr(minimizer, 'package') and minimizer.package == 'bumps'): @@ -426,6 +416,14 @@ def mcmc_sample( y_vals = data['data'][f'R_{i}'].values variances = data['data'][f'R_{i}'].variances + if obj != 'mighell' and np.all(np.asarray(variances) <= 0.0): + raise ValueError( + f'Cannot run Bayesian sampling on reflectivity {i}: all points have zero variance. ' + 'The likelihood is undefined without measurement uncertainties. Supply uncertainties, ' + "or explicitly opt in to the Mighell transform with objective='mighell' " + '(a chi-square bias correction, not a true likelihood).' + ) + x_out, y_eff, weights, stats = _prepare_fit_arrays(x_vals, y_vals, variances, obj) if stats['masked'] > 0: @@ -433,18 +431,43 @@ def mcmc_sample( f'Masked {stats["masked"]} data point(s) in reflectivity {i} due to zero variance during sampling.', UserWarning, ) + if stats.get('transformed_all_points'): + warnings.warn( + f'Applied Mighell transform to all {len(y_vals)} point(s) in reflectivity {i} during sampling. ' + 'The Mighell transform is a chi-square bias correction, not a true likelihood; ' + 'posterior widths may be unreliable.', + UserWarning, + ) + elif stats['mighell_substituted'] > 0: + warnings.warn( + f'Applied Mighell substitution to {stats["mighell_substituted"]} ' + f'zero-variance point(s) in reflectivity {i} during sampling. ' + 'The Mighell transform is a chi-square bias correction, not a true likelihood; ' + 'posterior widths may be unreliable.', + UserWarning, + ) x.append(x_out) y.append(y_eff) dy.append(weights) - # Delegate the actual BUMPS/DREAM sampling to the core MultiFitter + # Delegate the actual BUMPS/DREAM sampling to the core ``Sampler``. + # The core API moved from ``MultiFitter.mcmc_sample()`` to a dedicated + # ``Sampler`` class: construct it with the configured fitter and the + # bound data, then call ``sample()``. ``Sampler`` handles the + # multi-dataset reshaping internally. sampler_kwargs = {} if initializer is not None: sampler_kwargs['init'] = initializer - return self.easy_science_multi_fitter.mcmc_sample( + + sampler = Sampler( + self.easy_science_multi_fitter, x=x, y=y, weights=dy, + ) + # Retained so the chain can be continued afterwards via ``self.sampler.extend()``. + self._sampler = sampler + results = sampler.sample( samples=samples, burn=burn, thin=thin, @@ -453,6 +476,22 @@ def mcmc_sample( progress_callback=progress_callback, abort_test=abort_test, ) + return { + 'draws': results.draws, + 'param_names': results.param_names, + 'state': results.state, + 'logp': results.logp, + } + + @property + def sampler(self) -> Sampler | None: + """The ``Sampler`` behind the most recent :meth:`mcmc_sample` call, or None. + + Holds the live BUMPS chain state, so the sampling run can be continued + with ``fitter.sampler.extend(additional_samples=...)`` instead of + starting a fresh chain. + """ + return self._sampler @property def chi2(self) -> float | None: @@ -512,3 +551,19 @@ def switch_minimizer(self, minimizer: AvailableMinimizers) -> None: Minimizer to be switched to. """ self.easy_science_multi_fitter.switch_minimizer(minimizer) + + +def _flatten_list(this_list: list) -> list: + """Flatten nested lists. + + Parameters + ---------- + this_list : list + List to be flattened. + + Returns + ------- + list + Flattened list. + """ + return np.array([item for sublist in this_list for item in sublist]) diff --git a/src/easyreflectometry/limits.py b/src/easyreflectometry/limits.py index 8962ab9b..001bba64 100644 --- a/src/easyreflectometry/limits.py +++ b/src/easyreflectometry/limits.py @@ -31,19 +31,14 @@ def apply_default_limits(parameter: Parameter, kind: str) -> None: def _apply_percentage_limits(parameter: Parameter) -> None: - """Set bounds to 50%-200% of the current value, only if current bounds are inf. - - For negative values 0.5*value > 2*value, so the candidates are ordered - to keep min <= value <= max. - """ + """Set min to 50% and max to 200% of the current value, only if current bounds are inf.""" value = parameter.value if value == 0.0: return - low, high = sorted((0.5 * value, 2.0 * value)) if np.isinf(parameter.min): - parameter.min = low + parameter.min = 0.5 * value if np.isinf(parameter.max): - parameter.max = high + parameter.max = 2.0 * value def _apply_fixed_limits(parameter: Parameter, low: float, high: float) -> None: diff --git a/src/easyreflectometry/model/model.py b/src/easyreflectometry/model/model.py index 5aa32833..da1396d0 100644 --- a/src/easyreflectometry/model/model.py +++ b/src/easyreflectometry/model/model.py @@ -148,12 +148,12 @@ def background(self, value: float) -> None: # ----- assembly management ----- - def add_assemblies(self, *assemblies: BaseAssembly) -> None: + def add_assemblies(self, *assemblies: list[BaseAssembly]) -> None: """Add assemblies to the model sample. Parameters ---------- - *assemblies : BaseAssembly + *assemblies : list[BaseAssembly] Assemblies to add to model sample. """ if not assemblies: diff --git a/src/easyreflectometry/model/resolution_functions.py b/src/easyreflectometry/model/resolution_functions.py index c723bf14..9579ad66 100644 --- a/src/easyreflectometry/model/resolution_functions.py +++ b/src/easyreflectometry/model/resolution_functions.py @@ -6,11 +6,18 @@ Gaussian distribution with a FWHM of the percentage of the q value. To convert from a sigma value to a FWHM value we use the formula FWHM = 2.35 * sigma [2 * np.sqrt(2 * np.log(2)) * sigma]. + +The :meth:`ResolutionFunction.smearing` contract returns **sigma** +(the standard deviation of the Gaussian resolution) for every resolution +type. This matches the ``sQz`` convention used by data reduction and the +natural output of :class:`Pointwise`. Each calculation engine wrapper is +responsible for converting sigma to the width convention of its backend +(FWHM for refnx, sigma for refl1d), so that vector resolutions are +interpreted consistently across engines (see GitHub issue #367). """ from __future__ import annotations -from abc import ABC from abc import abstractmethod from typing import List from typing import Optional @@ -20,10 +27,15 @@ DEFAULT_RESOLUTION_FWHM_PERCENTAGE = 5.0 +# Conversion factor between sigma and FWHM for a Gaussian: FWHM = SIGMA_TO_FWHM * sigma. +SIGMA_TO_FWHM = 2 * np.sqrt(2 * np.log(2)) + -class ResolutionFunction(ABC): +class ResolutionFunction: @abstractmethod - def smearing(self, q: Union[np.array, float]) -> np.array: ... + def smearing(self, q: Union[np.array, float]) -> np.array: + """Return the resolution as sigma (standard deviation) at each ``q``.""" + ... @abstractmethod def as_dict(self, skip: Optional[List[str]] = None) -> dict: ... @@ -52,8 +64,14 @@ def __init__(self, constant: Union[None, float] = None): self.constant = constant def smearing(self, q: Union[np.array, float]) -> np.array: - """Smearing function.""" - return np.ones(np.array(q).size) * self.constant + """Return per-point sigma values from the constant FWHM percentage. + + ``constant`` is a FWHM percentage of ``q``; it is converted to an + absolute sigma so the smearing() contract is sigma for all types. + """ + q_array = np.asarray(q, dtype=float) + fwhm = (self.constant / 100.0) * q_array + return fwhm / SIGMA_TO_FWHM def as_dict( self, skip: Optional[List[str]] = None @@ -69,8 +87,13 @@ def __init__(self, q_data_points: np.array, fwhm_values: np.array): self.fwhm_values = fwhm_values def smearing(self, q: Union[np.array, float]) -> np.array: - """Smearing function.""" - return np.interp(q, self.q_data_points, self.fwhm_values) + """Return per-point sigma values from the FWHM knots. + + The stored ``fwhm_values`` are FWHM widths; they are interpolated + onto ``q`` and converted to sigma to satisfy the smearing() contract. + """ + fwhm = np.interp(np.asarray(q, dtype=float), self.q_data_points, self.fwhm_values) + return fwhm / SIGMA_TO_FWHM def as_dict( self, skip: Optional[List[str]] = None @@ -111,10 +134,12 @@ def __init__(self, q_data_points: List[np.ndarray]): self.q_data_points = q_data_points def smearing(self, q: Optional[Union[np.ndarray, float]] = None) -> np.ndarray: - """Return the resolution width interpolated onto ``q``. + """Return the resolution sigma interpolated onto ``q``. - The width at each data point is ``sqrt(sQz)``; values are linearly - interpolated onto the requested ``q``. When ``q`` is ``None`` the widths + ``sQz`` is the variance of ``Qz``, so the sigma at each data point is + ``sqrt(sQz)``; values are linearly interpolated onto the requested + ``q``. This already satisfies the sigma smearing() contract, so no + FWHM conversion is applied. When ``q`` is ``None`` the sigma values are returned at the stored data points. """ Qz = np.asarray(self.q_data_points[0], dtype=float) diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index fe4af1bf..aa0eacf6 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -28,6 +28,7 @@ from easyreflectometry.model import Model from easyreflectometry.model import ModelCollection from easyreflectometry.model import PercentageFwhm +from easyreflectometry.model import Pointwise from easyreflectometry.sample import Layer from easyreflectometry.sample import Material from easyreflectometry.sample import MaterialCollection @@ -346,30 +347,29 @@ def path_json(self): """Path json.""" return self.path / 'project.json' - def _get_or_add_material_index(self, name: str, sld: float, isld: float) -> int: - """Return the index of the named material, adding it to the project - materials first if not present. This mutates ``self._materials``.""" - names = [material.name for material in self._materials] - if name not in names: - self._materials.add_material(Material(name=name, sld=sld, isld=isld)) - names.append(name) - return names.index(name) - def get_index_air(self) -> int: - """Index of the Air material, adding it to the project if missing.""" - return self._get_or_add_material_index('Air', sld=0.0, isld=0.0) + """Get index air.""" + if 'Air' not in [material.name for material in self._materials]: + self._materials.add_material(Material(name='Air', sld=0.0, isld=0.0)) + return [material.name for material in self._materials].index('Air') def get_index_si(self) -> int: - """Index of the Si material, adding it to the project if missing.""" - return self._get_or_add_material_index('Si', sld=2.07, isld=0.0) + """Get index si.""" + if 'Si' not in [material.name for material in self._materials]: + self._materials.add_material(Material(name='Si', sld=2.07, isld=0.0)) + return [material.name for material in self._materials].index('Si') def get_index_sio2(self) -> int: - """Index of the SiO2 material, adding it to the project if missing.""" - return self._get_or_add_material_index('SiO2', sld=3.47, isld=0.0) + """Get index sio2.""" + if 'SiO2' not in [material.name for material in self._materials]: + self._materials.add_material(Material(name='SiO2', sld=3.47, isld=0.0)) + return [material.name for material in self._materials].index('SiO2') def get_index_d2o(self) -> int: - """Index of the D2O material, adding it to the project if missing.""" - return self._get_or_add_material_index('D2O', sld=6.36, isld=0.0) + """Get index d2o.""" + if 'D2O' not in [material.name for material in self._materials]: + self._materials.add_material(Material(name='D2O', sld=6.36, isld=0.0)) + return [material.name for material in self._materials].index('D2O') def load_orso_file(self, path: Union[Path, str]) -> None: """Load an ORSO file and optionally create a model and a data from it.""" @@ -387,6 +387,7 @@ def load_orso_file(self, path: Union[Path, str]) -> None: self._experiments[0].name = 'Experiment from ORSO' self._experiments[0].model = self.models[0] self._with_experiments = True + pass def set_sample_from_orso(self, sample: Sample) -> None: """Replace the current project model collection with a single model built from an ORSO-parsed sample. @@ -521,6 +522,10 @@ def _apply_resolution_function( ) -> None: """Set the resolution function on *model* based on variance data in *experiment*. + Uses the measured per-point q-resolution (``Pointwise``) when the + experiment carries q-variance data (``xe``, i.e. sQz²); otherwise + falls back to the default 5% FWHM percentage resolution. + Parameters ---------- experiment : DataSet1D @@ -528,7 +533,10 @@ def _apply_resolution_function( model : Model The model whose resolution function is set. """ - model.resolution_function = PercentageFwhm(5.0) + if experiment.xe is not None and np.any(experiment.xe): + model.resolution_function = Pointwise(q_data_points=[experiment.x, experiment.y, experiment.xe]) + else: + model.resolution_function = PercentageFwhm(5.0) @staticmethod def _auto_set_background(experiment: DataSet1D) -> None: @@ -665,7 +673,7 @@ def model_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.arr if q_range is None: q_range = np.linspace(self.q_min, self.q_max, self.q_resolution) self.models[index].interface = self._calculator - reflectivity = self.models[index].interface().reflectivity_profile(q_range, self._models[index].unique_name) + reflectivity = self.models[index].interface().reflectity_profile(q_range, self._models[index].unique_name) return DataSet1D( name=f'Reflectivity for Model {index}', x=q_range, @@ -868,10 +876,10 @@ def as_dict(self, include_materials_not_in_model=False): self._as_dict_add_materials_not_in_model_dict(project_dict) if self._with_experiments: self._as_dict_add_experiments(project_dict) - # Read the minimizer without touching the lazy `fitter` property: - # serialization must not construct a MultiFitter as a side effect. - if self.minimizer is not None: - project_dict['fitter_minimizer'] = self.minimizer.name + if self.fitter is not None: + project_dict['fitter_minimizer'] = self.fitter.easy_science_multi_fitter.minimizer.name + elif self._minimizer_selection is not None: + project_dict['fitter_minimizer'] = self._minimizer_selection.name if self._calculator is not None: project_dict['calculator'] = self._calculator.current_interface_name if self._colors is not None: diff --git a/src/easyreflectometry/sample/elements/materials/material_mixture.py b/src/easyreflectometry/sample/elements/materials/material_mixture.py index b5b88e27..1ab76ddc 100644 --- a/src/easyreflectometry/sample/elements/materials/material_mixture.py +++ b/src/easyreflectometry/sample/elements/materials/material_mixture.py @@ -152,16 +152,17 @@ def fraction(self, value: float) -> None: # ----- derived sld / isld parameters (shared shape with Material) ----- # # These are *derived* via the constraints set up in `_materials_constraints` - # (not constructor arguments), so unlike Material there are no setters: - # their values follow the child materials and the fraction. + # (not constructor arguments) so we expose them as floats to match the + # legacy MaterialMixture API. The underlying Parameter objects remain + # available as `self._sld` / `self._isld`. @property - def sld(self) -> Parameter: - return self._sld + def sld(self) -> float: + return self._sld.value @property - def isld(self) -> Parameter: - return self._isld + def isld(self) -> float: + return self._isld.value # ----- calculator binding ----- @@ -170,8 +171,8 @@ def _get_linkable_attributes(self): Override of the inherited `BaseCore._get_linkable_attributes`, which walks `get_all_variables()` and would otherwise expose the **child** - materials' sld/isld alongside the mixed ones. The calculator's - `InterfaceFactoryTemplate.generate_bindings` + materials' sld/isld (because our own `sld` / `isld` are floats, not + Parameters). The calculator's `InterfaceFactoryTemplate.generate_bindings` matches by parameter `name`; without this override it binds to `material_a.sld` and reflectivity is computed off the wrong SLD. """ diff --git a/src/easyreflectometry/special/calculations.py b/src/easyreflectometry/special/calculations.py index 9c6e43c2..07844d3c 100644 --- a/src/easyreflectometry/special/calculations.py +++ b/src/easyreflectometry/special/calculations.py @@ -44,13 +44,11 @@ def neutron_scattering_length(formula: str) -> complex: scattering_length = 0 + 0j for key, value in formula_as_dict.items(): scattering_length += pt.elements.symbol(key).neutron.b_c * value - # b_c_i is the imaginary (absorption) part of the bound coherent - # scattering length, not the incoherent scattering length. if pt.elements.symbol(key).neutron.b_c_i: - imag = pt.elements.symbol(key).neutron.b_c_i + inc = pt.elements.symbol(key).neutron.b_c_i else: - imag = 0 - scattering_length += imag * 1j * value + inc = 0 + scattering_length += inc * 1j * value return scattering_length * 1e-5 @@ -65,7 +63,7 @@ def molecular_weight(formula: str) -> float: Returns ------- float - Molecular weight of the material in u (g/mol). + Molecular weight of the material in kilograms. """ formula_as_dict = parse_formula(formula) mw = 0 diff --git a/src/easyreflectometry/summary/html_templates.py b/src/easyreflectometry/summary/html_templates.py index fe520e2b..9df780aa 100644 --- a/src/easyreflectometry/summary/html_templates.py +++ b/src/easyreflectometry/summary/html_templates.py @@ -141,7 +141,7 @@ No. of constraints - num_constraints + num_constriants """ diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index d8df7bd2..f40f23ad 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -309,28 +309,26 @@ def _refinement_section(self) -> str: html_refinement = html_refinement.replace('num_total_params', f'{num_params}') html_refinement = html_refinement.replace('num_free_params', f'{num_free_params}') html_refinement = html_refinement.replace('num_fixed_params', f'{num_fixed_params}') - html_refinement = html_refinement.replace('num_constraints', f'{num_constraints}') + html_refinement = html_refinement.replace('num_constriants', f'{num_constraints}') return html_refinement def _compute_goodness_of_fit(self) -> str: - """Return reduced chi² as a formatted string, or 'N/A' if no fit has been run. - - The value is read from the project's fitter, which computes the reduced - chi-square directly from the raw chi-square and the global degrees of - freedom across every fitted dataset. Deriving it this way keeps the - summary independent of the per-minimizer ``reduced_chi`` / ``reduced_chi2`` - attribute naming used by the underlying ``FitResults`` objects. - """ - fitter = self._project.fitter - if fitter is None: + """Return reduced chi² as a formatted string, or 'N/A' if no fit has been run.""" + last_fit_results = getattr(self._project, '_last_fit_results', None) + if not last_fit_results: return 'N/A' try: - gof = fitter.reduced_chi + if len(last_fit_results) == 1: + gof = float(last_fit_results[0].reduced_chi2) + else: + total_chi2 = sum(float(r.chi2) for r in last_fit_results) + total_points = sum(len(r.x) for r in last_fit_results) + n_pars = last_fit_results[0].n_pars + dof = total_points - n_pars + gof = total_chi2 / dof if dof > 0 else 0.0 + return f'{gof:.4g}' except (AttributeError, TypeError, ValueError, ZeroDivisionError): return 'N/A' - if gof is None: - return 'N/A' - return f'{gof:.4g}' def _figures_section(self, interactive: bool = True) -> str: """Figures section. diff --git a/tests/calculators/refl1d/test_refl1d_calculator.py b/tests/calculators/refl1d/test_refl1d_calculator.py index a27d1f7a..50de2db4 100644 --- a/tests/calculators/refl1d/test_refl1d_calculator.py +++ b/tests/calculators/refl1d/test_refl1d_calculator.py @@ -27,7 +27,7 @@ def test_init(self): assert_equal(p._model_link['background'], 'bkg') assert_equal(p.name, 'refl1d') - def test_reflectivity_profile(self): + def test_reflectity_profile(self): p = Refl1d() p._wrapper.create_material('Material1') p._wrapper.update_material('Material1', rho=0.000, irho=0.000) @@ -63,7 +63,7 @@ def test_reflectivity_profile(self): 1.3093e-07, 1.0520e-07, ] - assert_almost_equal(p.reflectivity_profile(q, 'MyModel'), expected, decimal=4) + assert_almost_equal(p.reflectity_profile(q, 'MyModel'), expected, decimal=4) def test_calculate2(self): p = Refl1d() @@ -95,7 +95,7 @@ def test_calculate2(self): p._wrapper.add_item('Item3', 'MyModel') p._wrapper.update_item('Item2', repeat=10) q = np.linspace(0.001, 0.3, 10) - actual = p.reflectivity_profile(q, 'MyModel') + actual = p.reflectity_profile(q, 'MyModel') expected = [ 9.9949e-01, 8.7414e-03, @@ -140,7 +140,7 @@ def test_calculate_magnetic(self): p._wrapper.add_item('Item2', 'MyModel') p._wrapper.add_item('Item3', 'MyModel') q = np.linspace(0.001, 0.3, 10) - actual = p.reflectivity_profile(q, 'MyModel') + actual = p.reflectity_profile(q, 'MyModel') expected = [ 9.99491251e-01, 1.08413641e-02, diff --git a/tests/calculators/refnx/test_refnx_calculator.py b/tests/calculators/refnx/test_refnx_calculator.py index e4efae68..baeb9296 100644 --- a/tests/calculators/refnx/test_refnx_calculator.py +++ b/tests/calculators/refnx/test_refnx_calculator.py @@ -27,7 +27,7 @@ def test_init(self): assert_equal(p._model_link['background'], 'bkg') assert_equal(p.name, 'refnx') - def test_reflectivity_profile(self): + def test_reflectity_profile(self): p = Refnx() p._wrapper.create_material('Material1') p._wrapper.update_material('Material1', real=0.000, imag=0.000) @@ -62,7 +62,7 @@ def test_reflectivity_profile(self): 1.26726993e-07, 1.01842852e-07, ] - assert_almost_equal(p.reflectivity_profile(q, 'MyModel'), expected) + assert_almost_equal(p.reflectity_profile(q, 'MyModel'), expected) def test_calculate2(self): p = Refnx() @@ -105,7 +105,7 @@ def test_calculate2(self): 3.4981523e-07, 2.5424356e-07, ] - assert_almost_equal(p.reflectivity_profile(q, 'MyModel'), expected) + assert_almost_equal(p.reflectity_profile(q, 'MyModel'), expected) def test_sld_profile(self): p = Refnx() diff --git a/tests/calculators/refnx/test_refnx_wrapper.py b/tests/calculators/refnx/test_refnx_wrapper.py index f9d2a4cc..bb99d633 100644 --- a/tests/calculators/refnx/test_refnx_wrapper.py +++ b/tests/calculators/refnx/test_refnx_wrapper.py @@ -451,7 +451,7 @@ def test_calculate_github_test4_spline_resolution(self): p.add_item('Item3', 'MyModel') p.add_item('Item4', 'MyModel') p.update_model('MyModel', bkg=0) - sigma_to_fwhm = 2.355 + sigma_to_fwhm = 2.0 * np.sqrt(2.0 * np.log(2.0)) p.set_resolution_function(LinearSpline(test4_dat[:, 0], sigma_to_fwhm * test4_dat[:, 3])) assert_allclose(p.calculate(test4_dat[:, 0], 'MyModel'), test4_dat[:, 1], rtol=0.03) diff --git a/tests/calculators/test_resolution_conventions.py b/tests/calculators/test_resolution_conventions.py new file mode 100644 index 00000000..5b7c7fb7 --- /dev/null +++ b/tests/calculators/test_resolution_conventions.py @@ -0,0 +1,207 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Absolute checks on the width convention each wrapper hands to its backend. + +``ResolutionFunction.smearing()`` returns sigma for every resolution type; each +wrapper is then responsible for converting to what its backend expects: + +* refnx -- ``x_err`` is the **FWHM** at each q (a scalar ``x_err`` is instead a + constant dQ/Q FWHM *percentage*). +* refl1d -- ``QProbe.dQ`` is **sigma**. + +The cross-engine tests in ``tests/integration/test_cross_engine_resolution.py`` +only pin the engines against each other, so they cannot catch an error applied +consistently to both. These tests intercept the value at each engine boundary +and assert the exact numbers, which pins the convention absolutely. In +particular this is the only absolute check on the refl1d resolution path. + +See GitHub issue #367 for background. +""" + +import numpy as np +import pytest +from numpy.testing import assert_allclose +from refl1d import names +from refnx import reflect + +from easyreflectometry.calculators.refl1d.wrapper import Refl1dWrapper +from easyreflectometry.calculators.refnx.wrapper import RefnxWrapper +from easyreflectometry.model.resolution_functions import SIGMA_TO_FWHM +from easyreflectometry.model.resolution_functions import LinearSpline +from easyreflectometry.model.resolution_functions import PercentageFwhm +from easyreflectometry.model.resolution_functions import Pointwise + +Q = np.linspace(0.01, 0.3, 20) + +Q_KNOTS = np.linspace(0.001, 0.5, 10) +FWHM_KNOTS = 0.02 * Q_KNOTS + 0.001 + +QZ = np.linspace(0.001, 0.5, 50) +SIGMA_POINTS = 0.01 * QZ + 0.0005 +SQZ = SIGMA_POINTS**2 + + +def _build_refnx(): + wrapper = RefnxWrapper() + wrapper.reset_storage() + wrapper.create_material('Substrate') + wrapper.update_material('Substrate', real=2.07, imag=0.0) + wrapper.create_material('Film') + wrapper.update_material('Film', real=3.45, imag=0.0) + wrapper.create_layer('SubstrateLayer') + wrapper.assign_material_to_layer('Substrate', 'SubstrateLayer') + wrapper.create_layer('FilmLayer') + wrapper.assign_material_to_layer('Film', 'FilmLayer') + wrapper.update_layer('FilmLayer', thick=100.0, rough=3.0) + wrapper.create_item('Item') + wrapper.add_layer_to_item('FilmLayer', 'Item') + wrapper.add_layer_to_item('SubstrateLayer', 'Item') + wrapper.create_model('MyModel') + wrapper.add_item('Item', 'MyModel') + wrapper.update_model('MyModel', bkg=0.0) + return wrapper + + +def _build_refl1d(): + wrapper = Refl1dWrapper() + wrapper.reset_storage() + wrapper.create_material('Substrate') + wrapper.update_material('Substrate', rho=2.07, irho=0.0) + wrapper.create_material('Film') + wrapper.update_material('Film', rho=3.45, irho=0.0) + wrapper.create_layer('SubstrateLayer') + wrapper.assign_material_to_layer('Substrate', 'SubstrateLayer') + wrapper.create_layer('FilmLayer') + wrapper.assign_material_to_layer('Film', 'FilmLayer') + wrapper.update_layer('FilmLayer', thickness=100.0, interface=3.0) + wrapper.create_item('Item') + wrapper.add_layer_to_item('FilmLayer', 'Item') + wrapper.add_layer_to_item('SubstrateLayer', 'Item') + wrapper.create_model('MyModel') + wrapper.add_item('Item', 'MyModel') + wrapper.update_model('MyModel', bkg=0.0) + return wrapper + + +def _capture_refnx_x_err(monkeypatch, resolution_function): + """Run RefnxWrapper.calculate and return the x_err handed to refnx.""" + captured = {} + real_call = reflect.ReflectModel.__call__ + + def spy(self, x, p=None, x_err=None): + captured['x_err'] = x_err + return real_call(self, x, p=p, x_err=x_err) + + monkeypatch.setattr(reflect.ReflectModel, '__call__', spy) + + wrapper = _build_refnx() + wrapper.set_resolution_function(resolution_function) + wrapper.calculate(Q, 'MyModel') + return captured['x_err'] + + +def _capture_refl1d_dq(monkeypatch, resolution_function): + """Run Refl1dWrapper.calculate and return the dQ handed to refl1d's QProbe.""" + captured = {} + real_qprobe = names.QProbe + + def spy(**kwargs): + captured['dQ'] = np.asarray(kwargs['dQ'], dtype=float) + return real_qprobe(**kwargs) + + monkeypatch.setattr(names, 'QProbe', spy) + + wrapper = _build_refl1d() + wrapper.set_resolution_function(resolution_function) + wrapper.calculate(Q, 'MyModel') + return captured['dQ'] + + +# ----- the constant itself ----- + + +@pytest.mark.fast +def test_sigma_to_fwhm_is_the_gaussian_ratio(): + """Pin SIGMA_TO_FWHM against a literal. + + Every other test in this module imports SIGMA_TO_FWHM -- the same constant + the production code uses -- so a wrong value would cancel out on both sides + of the assertion and stay invisible. This is the one place the constant is + checked against an external fact: the FWHM/sigma ratio of a Gaussian, + 2*sqrt(2*ln2). + """ + assert SIGMA_TO_FWHM == pytest.approx(2.3548200450309493) + + +# ----- refnx expects FWHM ----- + + +@pytest.mark.fast +def test_refnx_receives_fwhm_for_linear_spline(monkeypatch): + x_err = _capture_refnx_x_err(monkeypatch, LinearSpline(Q_KNOTS, FWHM_KNOTS)) + + expected_fwhm = np.interp(Q, Q_KNOTS, FWHM_KNOTS) + assert_allclose(x_err, expected_fwhm) + + +@pytest.mark.fast +def test_refnx_receives_fwhm_for_pointwise(monkeypatch): + x_err = _capture_refnx_x_err(monkeypatch, Pointwise([QZ, np.ones_like(QZ), SQZ])) + + expected_sigma = np.interp(Q, QZ, np.sqrt(SQZ)) + assert_allclose(x_err, expected_sigma * SIGMA_TO_FWHM) + + +@pytest.mark.fast +def test_refnx_receives_scalar_percentage_for_percentage_fwhm(monkeypatch): + x_err = _capture_refnx_x_err(monkeypatch, PercentageFwhm(5.0)) + + # refnx reads a scalar x_err as a constant dQ/Q FWHM percentage, so the + # percentage is passed through verbatim -- not converted to a width. + assert np.isscalar(x_err) or np.ndim(x_err) == 0 + assert_allclose(x_err, 5.0) + + +# ----- refl1d expects sigma ----- + + +@pytest.mark.fast +def test_refl1d_receives_sigma_for_linear_spline(monkeypatch): + dq = _capture_refl1d_dq(monkeypatch, LinearSpline(Q_KNOTS, FWHM_KNOTS)) + + expected_fwhm = np.interp(Q, Q_KNOTS, FWHM_KNOTS) + assert_allclose(dq, expected_fwhm / SIGMA_TO_FWHM) + + +@pytest.mark.fast +def test_refl1d_receives_sigma_for_pointwise(monkeypatch): + dq = _capture_refl1d_dq(monkeypatch, Pointwise([QZ, np.ones_like(QZ), SQZ])) + + expected_sigma = np.interp(Q, QZ, np.sqrt(SQZ)) + assert_allclose(dq, expected_sigma) + + +@pytest.mark.fast +def test_refl1d_receives_sigma_for_percentage_fwhm(monkeypatch): + dq = _capture_refl1d_dq(monkeypatch, PercentageFwhm(5.0)) + + expected_sigma = (5.0 / 100.0) * Q / SIGMA_TO_FWHM + assert_allclose(dq, expected_sigma) + + +# ----- the two backends must receive widths that differ by exactly SIGMA_TO_FWHM ----- + + +@pytest.mark.fast +def test_engines_receive_widths_differing_by_sigma_to_fwhm(monkeypatch): + """The whole point of issue #367, stated directly. + + Catches a common-mode error that the cross-engine reflectivity comparison + cannot see: whatever the widths are, refnx's must be exactly SIGMA_TO_FWHM + times refl1d's. + """ + x_err = _capture_refnx_x_err(monkeypatch, LinearSpline(Q_KNOTS, FWHM_KNOTS)) + dq = _capture_refl1d_dq(monkeypatch, LinearSpline(Q_KNOTS, FWHM_KNOTS)) + + assert_allclose(x_err, dq * SIGMA_TO_FWHM) diff --git a/tests/integration/test_cross_engine_resolution.py b/tests/integration/test_cross_engine_resolution.py new file mode 100644 index 00000000..6da7be16 --- /dev/null +++ b/tests/integration/test_cross_engine_resolution.py @@ -0,0 +1,187 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Cross-engine consistency checks for resolution function width conventions. + +The same model + resolution function should produce broadly the same +reflectivity on refnx and refl1d. A width-convention error at one engine +boundary shows up as a systematic disagreement between them. + +.. warning:: + + These are **smoke tests, not the regression tests for issue #367.** They + compare the engines against each other, so they are blind to any error + applied consistently to both, and -- measured, not assumed -- they are only + sensitive enough to catch *one* of the two bugs #367 fixed: + + * ``LinearSpline``: the pre-fix refl1d code **over**-smeared by 2.355x, + which moves the curve enough to be caught here (measured separation ~2x). + * ``Pointwise``: the pre-fix refnx code **under**-smeared by 2.355x from an + already-small width. That barely moves the curve -- measured separation + ~1.1x, against a baseline engine disagreement of the same size -- so **no + tolerance can catch it here.** The Pointwise test below is a consistency + check only. + + The actual, exact regression tests for both conventions live in + ``tests/calculators/test_resolution_conventions.py``, which intercepts the + widths handed to each backend and asserts them to floating-point precision. + Fix that file first if these ever conflict. + +.. note:: + + Reflectivity spans several decades and the engines' different resolution + algorithms (refnx: pointwise convolution; refl1d: oversampling) disagree + most at fringe minima, where R is tiny and *relative* differences explode. + The comparison is therefore made on ``log10(R)``, and the tolerances are + measured values with roughly 1.5x headroom rather than round numbers. + +See GitHub issue #367 for background. +""" + +import numpy as np +import pytest + +from easyreflectometry.calculators.refl1d.wrapper import Refl1dWrapper +from easyreflectometry.calculators.refnx.wrapper import RefnxWrapper +from easyreflectometry.model.resolution_functions import SIGMA_TO_FWHM +from easyreflectometry.model.resolution_functions import LinearSpline +from easyreflectometry.model.resolution_functions import PercentageFwhm +from easyreflectometry.model.resolution_functions import Pointwise + +Q = np.geomspace(0.005, 0.3, 100) + + +def _build_simple_model_refnx(wrapper): + """Build an ambient | 100 A film | substrate model on a refnx wrapper. + + The ambient layer is not optional decoration: both engines treat the first + layer as the semi-infinite superphase and ignore its thickness. Without it + the "film" becomes the ambient, leaving a bare interface with no Kiessig + fringes -- and resolution smearing acts almost entirely on fringes, so the + model would be insensitive to the very thing under test. + """ + wrapper.reset_storage() + wrapper.create_material('Ambient') + wrapper.update_material('Ambient', real=0.0, imag=0.0) + wrapper.create_material('Film') + wrapper.update_material('Film', real=3.45, imag=0.0) + wrapper.create_material('Substrate') + wrapper.update_material('Substrate', real=2.07, imag=0.0) + wrapper.create_layer('AmbientLayer') + wrapper.assign_material_to_layer('Ambient', 'AmbientLayer') + wrapper.create_layer('FilmLayer') + wrapper.assign_material_to_layer('Film', 'FilmLayer') + wrapper.update_layer('FilmLayer', thick=100.0, rough=3.0) + wrapper.create_layer('SubstrateLayer') + wrapper.assign_material_to_layer('Substrate', 'SubstrateLayer') + wrapper.update_layer('SubstrateLayer', rough=3.0) + wrapper.create_item('Item') + wrapper.add_layer_to_item('AmbientLayer', 'Item') + wrapper.add_layer_to_item('FilmLayer', 'Item') + wrapper.add_layer_to_item('SubstrateLayer', 'Item') + wrapper.create_model('MyModel') + wrapper.add_item('Item', 'MyModel') + wrapper.update_model('MyModel', bkg=0.0) + + +def _build_simple_model_refl1d(wrapper): + """Build the same ambient | 100 A film | substrate model on refl1d.""" + wrapper.reset_storage() + wrapper.create_material('Ambient') + wrapper.update_material('Ambient', rho=0.0, irho=0.0) + wrapper.create_material('Film') + wrapper.update_material('Film', rho=3.45, irho=0.0) + wrapper.create_material('Substrate') + wrapper.update_material('Substrate', rho=2.07, irho=0.0) + wrapper.create_layer('AmbientLayer') + wrapper.assign_material_to_layer('Ambient', 'AmbientLayer') + wrapper.create_layer('FilmLayer') + wrapper.assign_material_to_layer('Film', 'FilmLayer') + wrapper.update_layer('FilmLayer', thickness=100.0, interface=3.0) + wrapper.create_layer('SubstrateLayer') + wrapper.assign_material_to_layer('Substrate', 'SubstrateLayer') + wrapper.update_layer('SubstrateLayer', interface=3.0) + wrapper.create_item('Item') + wrapper.add_layer_to_item('AmbientLayer', 'Item') + wrapper.add_layer_to_item('FilmLayer', 'Item') + wrapper.add_layer_to_item('SubstrateLayer', 'Item') + wrapper.create_model('MyModel') + wrapper.add_item('Item', 'MyModel') + wrapper.update_model('MyModel', bkg=0.0) + + +def _both_engines(resolution_function): + """Return (refnx_reflectivity, refl1d_reflectivity) for one resolution.""" + refnx_w = RefnxWrapper() + _build_simple_model_refnx(refnx_w) + refnx_w.set_resolution_function(resolution_function) + refnx_r = refnx_w.calculate(Q, 'MyModel') + + refl1d_w = Refl1dWrapper() + _build_simple_model_refl1d(refl1d_w) + refl1d_w.set_resolution_function(resolution_function) + refl1d_r = refl1d_w.calculate(Q, 'MyModel') + + return refnx_r, refl1d_r + + +def _assert_log_close(refnx_r, refl1d_r, atol): + """Assert the engines agree to `atol` decades of R at every q.""" + deviation = np.abs(np.log10(refnx_r) - np.log10(refl1d_r)) + assert deviation.max() <= atol, ( + f'engines disagree by {deviation.max():.4f} decades ' + f'(factor {10 ** deviation.max():.2f}) at q={Q[np.argmax(deviation)]:.4f}, tolerance {atol}' + ) + + +@pytest.mark.fast +@pytest.mark.parametrize(('resolution_pct', 'atol'), [(1.0, 0.07), (5.0, 0.23), (10.0, 0.29)]) +def test_percentage_fwhm_consistent_across_engines(resolution_pct, atol): + """PercentageFwhm gives consistent results across engines. + + Measured disagreement grows with the width (0.041 / 0.149 / 0.191 decades + at 1% / 5% / 10% dQ/Q), so the tolerance is parametrized with it rather + than set to one blanket value. This combination was correct both before + and after issue #367; the test guards against regression. + """ + refnx_r, refl1d_r = _both_engines(PercentageFwhm(resolution_pct)) + _assert_log_close(refnx_r, refl1d_r, atol=atol) + + +@pytest.mark.fast +def test_linear_spline_consistent_across_engines(): + """LinearSpline gives consistent results across engines. + + This one does earn its keep: pre-fix, refl1d read the FWHM knots as sigma + and over-smeared by 2.355x. Measured max |dlog10(R)|: 0.085 with the fix, + 0.182 without it, so atol=0.13 separates them with ~1.5x headroom either + way. + """ + q_knots = np.linspace(0.001, 0.5, 10) + fwhm_knots = 0.02 * q_knots + 0.001 + + refnx_r, refl1d_r = _both_engines(LinearSpline(q_knots, fwhm_knots)) + _assert_log_close(refnx_r, refl1d_r, atol=0.13) + + +@pytest.mark.fast +def test_pointwise_consistent_across_engines(): + """Pointwise (sigma from sQz) is consistent across engines. + + Consistency check only. Pre-fix, refnx under-smeared these widths by + 2.355x, but measured max |dlog10(R)| is 0.092 pre-fix versus 0.085 with + the fix -- indistinguishable, because under-smearing an already-small + width barely moves the curve. Do not add a tolerance here expecting it to + catch that bug; ``tests/calculators/test_resolution_conventions.py`` is + what actually pins it. + + The sQz values mirror the LinearSpline knots, so the applied smearing -- + and hence the measured agreement -- matches that test. + """ + qz = np.linspace(0.001, 0.5, 50) + r = np.ones_like(qz) # only kept for serialization round-trips + sigma = (0.02 * qz + 0.001) / SIGMA_TO_FWHM + sqz = sigma**2 + + refnx_r, refl1d_r = _both_engines(Pointwise([qz, r, sqz])) + _assert_log_close(refnx_r, refl1d_r, atol=0.13) diff --git a/tests/model/test_model.py b/tests/model/test_model.py index 0c83d5dc..a2dd46ea 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -45,8 +45,9 @@ def test_default(self): assert_equal(p.background.min, 0.0) assert_equal(p.background.max, np.inf) assert_equal(p.background.fixed, True) - assert p._resolution_function.smearing([1]) == 5.0 - assert p._resolution_function.smearing([100]) == 5.0 + sigma_to_fwhm = 2.0 * np.sqrt(2.0 * np.log(2.0)) + assert np.allclose(p._resolution_function.smearing([1]), 5.0 / 100.0 * 1.0 / sigma_to_fwhm) + assert np.allclose(p._resolution_function.smearing([100]), 5.0 / 100.0 * 100.0 / sigma_to_fwhm) def test_from_pars(self): m1 = Material(6.908, -0.278, 'Boron') @@ -81,8 +82,9 @@ def test_from_pars(self): assert_equal(mod.background.min, 0.0) assert_equal(mod.background.max, np.inf) assert_equal(mod.background.fixed, True) - assert mod._resolution_function.smearing([1]) == 2.0 - assert mod._resolution_function.smearing([100]) == 2.0 + sigma_to_fwhm = 2.0 * np.sqrt(2.0 * np.log(2.0)) + assert np.allclose(mod._resolution_function.smearing([1]), 2.0 / 100.0 * 1.0 / sigma_to_fwhm) + assert np.allclose(mod._resolution_function.smearing([100]), 2.0 / 100.0 * 100.0 / sigma_to_fwhm) def test_add_assemblies(self): m1 = Material(6.908, -0.278, 'Boron') @@ -427,8 +429,8 @@ def test_dict_round_trip(interface): if interface is not None: assert model.interface().name == model_from_dict.interface().name assert_almost_equal( - model.interface().reflectivity_profile([0.3], model.unique_name), - model_from_dict.interface().reflectivity_profile([0.3], model_from_dict.unique_name), + model.interface().reflectity_profile([0.3], model.unique_name), + model_from_dict.interface().reflectity_profile([0.3], model_from_dict.unique_name), ) @@ -525,7 +527,8 @@ def test_round_trip_preserves_resolution_function(self): d = model.as_dict() global_object.map._clear() restored = Model.from_dict(d) - assert restored._resolution_function.smearing(100) == 3.0 + sigma_to_fwhm = 2.0 * np.sqrt(2.0 * np.log(2.0)) + assert np.allclose(restored._resolution_function.smearing(100), 3.0 / 100.0 * 100.0 / sigma_to_fwhm) def test_round_trip_preserves_interface(self): global_object.map._clear() diff --git a/tests/model/test_resolution_functions.py b/tests/model/test_resolution_functions.py index 1391949b..480ca2c6 100644 --- a/tests/model/test_resolution_functions.py +++ b/tests/model/test_resolution_functions.py @@ -6,6 +6,7 @@ import numpy as np from easyreflectometry.model.resolution_functions import DEFAULT_RESOLUTION_FWHM_PERCENTAGE +from easyreflectometry.model.resolution_functions import SIGMA_TO_FWHM from easyreflectometry.model.resolution_functions import LinearSpline from easyreflectometry.model.resolution_functions import PercentageFwhm from easyreflectometry.model.resolution_functions import Pointwise @@ -17,28 +18,31 @@ def test_constructor(self): # When resolution_function = PercentageFwhm(1.0) - # Then Expect - assert np.all(resolution_function.smearing([0, 2.5]) == np.array([1.0, 1.0])) - assert resolution_function.smearing([-100]) == np.array([1.0]) - assert resolution_function.smearing([100]) == np.array([1.0]) + # Then Expect: smearing() returns sigma = (constant / 100) * q / SIGMA_TO_FWHM + # Negative q is not asserted: sigma scales with q here, so q < 0 yields a + # negative width, which is meaningless. Leaving it unpinned keeps the door + # open to guarding with abs(q) without failing this test. + expected = (1.0 / 100.0) * np.array([0.0, 2.5]) / SIGMA_TO_FWHM + assert np.allclose(resolution_function.smearing([0, 2.5]), expected) + assert np.allclose(resolution_function.smearing([100]), (1.0 / 100.0) * 100.0 / SIGMA_TO_FWHM) def test_constructor_none(self): # When resolution_function = PercentageFwhm() - # Then Expect - assert np.all( - resolution_function.smearing([0, 2.5]) == [DEFAULT_RESOLUTION_FWHM_PERCENTAGE, DEFAULT_RESOLUTION_FWHM_PERCENTAGE] - ) - assert resolution_function.smearing([-100]) == DEFAULT_RESOLUTION_FWHM_PERCENTAGE - assert resolution_function.smearing([100]) == DEFAULT_RESOLUTION_FWHM_PERCENTAGE + # Then Expect: defaults to DEFAULT_RESOLUTION_FWHM_PERCENTAGE, returned as sigma + # Negative q is not asserted -- see test_constructor. + c = DEFAULT_RESOLUTION_FWHM_PERCENTAGE + expected = (c / 100.0) * np.array([0.0, 2.5]) / SIGMA_TO_FWHM + assert np.allclose(resolution_function.smearing([0, 2.5]), expected) + assert np.allclose(resolution_function.smearing([100]), (c / 100.0) * 100.0 / SIGMA_TO_FWHM) def test_as_dict(self): # When resolution_function = PercentageFwhm(1.0) # Then Expect - assert resolution_function.as_dict() == {'smearing': 'PercentageFwhm', 'constant': 1.0} + resolution_function.as_dict() == {'smearing': 'PercentageFwhm', 'constant': 1.0} def test_dict_round_trip(self): # When @@ -57,17 +61,19 @@ def test_constructor(self): # When resolution_function = LinearSpline(q_data_points=[0, 10], fwhm_values=[5, 10]) - # Then Expect - assert np.all(resolution_function.smearing([0, 2.5]) == np.array([5, 6.25])) - assert resolution_function.smearing([-100]) == np.array([5.0]) - assert resolution_function.smearing([100]) == np.array([10.0]) + # Then Expect: smearing() returns sigma (FWHM knots converted to sigma) + # Unlike PercentageFwhm, q outside the knot range is meaningful here: + # np.interp clamps to the end knots, so the width stays positive. + assert np.allclose(resolution_function.smearing([0, 2.5]), np.array([5, 6.25]) / SIGMA_TO_FWHM) + assert np.allclose(resolution_function.smearing([-100]), np.array([5.0]) / SIGMA_TO_FWHM) + assert np.allclose(resolution_function.smearing([100]), np.array([10.0]) / SIGMA_TO_FWHM) def test_as_dict(self): # When resolution_function = LinearSpline(q_data_points=[0, 10], fwhm_values=[5, 10]) # Then Expect - assert resolution_function.as_dict() == { + resolution_function.as_dict() == { 'smearing': 'LinearSpline', 'q_data_points': [0, 10], 'fwhm_values': [5, 10], @@ -117,12 +123,7 @@ def test_as_dict(self): resolution_function = Pointwise(q_data_points=self.data_points) # Then Expect - assert resolution_function.as_dict() == { - 'smearing': 'Pointwise', - 'q_data_points': self.data_points[0], - 'R_data_points': self.data_points[1], - 'sQz_data_points': self.data_points[2], - } + assert resolution_function.as_dict(), {'smearing': 'Pointwise', 'q_data_points': [0, 10]} def test_dict_round_trip(self): # When diff --git a/tests/sample/elements/layers/test_layer_area_per_molecule.py b/tests/sample/elements/layers/test_layer_area_per_molecule.py index 7fe04009..0d466be7 100644 --- a/tests/sample/elements/layers/test_layer_area_per_molecule.py +++ b/tests/sample/elements/layers/test_layer_area_per_molecule.py @@ -27,8 +27,8 @@ def test_default(self): assert p.roughness.value == 3.3 assert str(p.roughness.unit) == 'Å' assert p.roughness.fixed is True - assert_almost_equal(p.material.sld.value, 2.268770124481328) - assert_almost_equal(p.material.isld.value, 0) + assert_almost_equal(p.material.sld, 2.268770124481328) + assert_almost_equal(p.material.isld, 0) assert p.material.name == 'C10H18NO8P in D2O' assert p.solvent.sld.value == 6.36 assert p.solvent.isld.value == 0 @@ -69,7 +69,7 @@ def test_from_pars_constraint(self): ) assert p.molecular_formula == 'C8O10H12P' assert p.area_per_molecule.value == 50 - assert_almost_equal(p.material.sld.value, 0.31494833333333333) + assert_almost_equal(p.material.sld, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 @@ -77,10 +77,10 @@ def test_from_pars_constraint(self): assert p.solvent_fraction.value == 0.5 p.area_per_molecule = 30 assert p.area_per_molecule.value == 30 - assert_almost_equal(p.material.sld.value, 0.7119138888888887) + assert_almost_equal(p.material.sld, 0.7119138888888887) p.thickness.value = 10 assert p.thickness.value == 10 - assert_almost_equal(p.material.sld.value, 0.9103966666666665) + assert_almost_equal(p.material.sld, 0.9103966666666665) @unittest.skip('Instantiation of LayerAreaPerMolecule fails, despite working everywhere else.') def test_solvent_change(self): @@ -97,7 +97,7 @@ def test_solvent_change(self): assert p.molecular_formula == 'C8O10H12P' assert p.area_per_molecule.value == 50 print(p.material) - assert_almost_equal(p.material.sld.value, 0.31494833333333333) + assert_almost_equal(p.material.sld, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 @@ -107,7 +107,7 @@ def test_solvent_change(self): p.solvent = d2o assert p.molecular_formula == 'C8O10H12P' assert p.area_per_molecule.value == 50 - assert_almost_equal(p.material.sld.value, 3.762948333333333) + assert_almost_equal(p.material.sld, 3.762948333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == 6.335 @@ -127,7 +127,7 @@ def test_molecular_formula_change(self): ) assert p.molecular_formula == 'C8O10H12P' assert p.area_per_molecule.value == 50 - assert_almost_equal(p.material.sld.value, 0.31494833333333333) + assert_almost_equal(p.material.sld, 0.31494833333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 @@ -138,7 +138,7 @@ def test_molecular_formula_change(self): p.molecular_formula = 'C8O10D12P' assert p.molecular_formula == 'C8O10D12P' assert p.area_per_molecule.value == 50 - assert_almost_equal(p.material.sld.value, 1.3558483333333333) + assert_almost_equal(p.material.sld, 1.3558483333333333) assert p.thickness.value == 12 assert p.roughness.value == 2 assert p.solvent.sld.value == -0.561 diff --git a/tests/sample/elements/materials/test_material_mixture.py b/tests/sample/elements/materials/test_material_mixture.py index d3912b7d..3c2a3f65 100644 --- a/tests/sample/elements/materials/test_material_mixture.py +++ b/tests/sample/elements/materials/test_material_mixture.py @@ -15,8 +15,8 @@ def test_default(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 4.186) - assert_almost_equal(material_mixture.isld.value, 0) + assert_almost_equal(material_mixture.sld, 4.186) + assert_almost_equal(material_mixture.isld, 0) assert str(material_mixture._sld.unit) == '1/Å^2' assert str(material_mixture._isld.unit) == '1/Å^2' @@ -24,12 +24,12 @@ def test_default_constraint(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 4.186) - assert_almost_equal(material_mixture.isld.value, 0) + assert_almost_equal(material_mixture.sld, 4.186) + assert_almost_equal(material_mixture.isld, 0) material_mixture.material_a.sld.value = 0 material_mixture.material_b.isld.value = -1 - assert_almost_equal(material_mixture.sld.value, 2.093) - assert_almost_equal(material_mixture.isld.value, -0.5) + assert_almost_equal(material_mixture.sld, 2.093) + assert_almost_equal(material_mixture.isld, -0.5) assert str(material_mixture._sld.unit) == '1/Å^2' assert str(material_mixture._isld.unit) == '1/Å^2' @@ -38,59 +38,59 @@ def test_fraction_constraint(self): q = Material(6.908, -0.278, 'Boron') material_mixture = MaterialMixture(p, q, 0.2) assert material_mixture.fraction.value == 0.2 - assert_almost_equal(material_mixture.sld.value, 4.7304) - assert_almost_equal(material_mixture.isld.value, -0.0556) + assert_almost_equal(material_mixture.sld, 4.7304) + assert_almost_equal(material_mixture.isld, -0.0556) material_mixture._fraction.value = 0.5 assert material_mixture.fraction.value == 0.5 - assert_almost_equal(material_mixture.sld.value, 5.54700) - assert_almost_equal(material_mixture.isld.value, -0.1390) + assert_almost_equal(material_mixture.sld, 5.54700) + assert_almost_equal(material_mixture.isld, -0.1390) def test_material_a_change(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 4.186) - assert_almost_equal(material_mixture.isld.value, 0) + assert_almost_equal(material_mixture.sld, 4.186) + assert_almost_equal(material_mixture.isld, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_a = q assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 5.54700) - assert_almost_equal(material_mixture.isld.value, -0.1390) + assert_almost_equal(material_mixture.sld, 5.54700) + assert_almost_equal(material_mixture.isld, -0.1390) def test_material_b_change(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 4.186) - assert_almost_equal(material_mixture.isld.value, 0) + assert_almost_equal(material_mixture.sld, 4.186) + assert_almost_equal(material_mixture.isld, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_b = q assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 5.54700) - assert_almost_equal(material_mixture.isld.value, -0.1390) + assert_almost_equal(material_mixture.sld, 5.54700) + assert_almost_equal(material_mixture.isld, -0.1390) def test_material_b_change_double(self) -> None: material_mixture = MaterialMixture() assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 4.186) - assert_almost_equal(material_mixture.isld.value, 0) + assert_almost_equal(material_mixture.sld, 4.186) + assert_almost_equal(material_mixture.isld, 0) q = Material(6.908, -0.278, 'Boron') material_mixture.material_b = q assert material_mixture.name == 'EasyMaterial/Boron' assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 5.54700) - assert_almost_equal(material_mixture.isld.value, -0.1390) + assert_almost_equal(material_mixture.sld, 5.54700) + assert_almost_equal(material_mixture.isld, -0.1390) r = Material(0.00, 0.00, 'ACMW') material_mixture.material_b = r assert material_mixture.name == 'EasyMaterial/ACMW' assert material_mixture.fraction.value == 0.5 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 2.0930) - assert_almost_equal(material_mixture.isld.value, 0.0000) + assert_almost_equal(material_mixture.sld, 2.0930) + assert_almost_equal(material_mixture.isld, 0.0000) def test_from_pars(self): p = Material() @@ -98,8 +98,8 @@ def test_from_pars(self): material_mixture = MaterialMixture(p, q, 0.2) assert material_mixture.fraction.value == 0.2 assert str(material_mixture._fraction.unit) == 'dimensionless' - assert_almost_equal(material_mixture.sld.value, 4.7304) - assert_almost_equal(material_mixture.isld.value, -0.0556) + assert_almost_equal(material_mixture.sld, 4.7304) + assert_almost_equal(material_mixture.isld, -0.0556) assert str(material_mixture._sld.unit) == '1/Å^2' assert str(material_mixture._isld.unit) == '1/Å^2' @@ -158,7 +158,7 @@ def test_calculator_binding_uses_mixed_sld(self) -> None: mixture = MaterialMixture(material_a, material_b, fraction=0.25, interface=interface) # 2 * 0.75 + 6 * 0.25 = 1.5 + 1.5 = 3.0 - assert_almost_equal(mixture.sld.value, 3.0) + assert_almost_equal(mixture.sld, 3.0) wrapper_material = interface()._wrapper.storage['material'][mixture.unique_name] assert_almost_equal(wrapper_material.real.value, 3.0) assert_almost_equal(wrapper_material.imag.value, 0.0) @@ -173,8 +173,8 @@ def test_mutation_propagates_after_round_trip(self) -> None: global_object.map._clear() q = MaterialMixture.from_dict(p_dict) - assert_almost_equal(q.sld.value, 3.0) + assert_almost_equal(q.sld, 3.0) q.fraction = 0.8 # 2 * 0.2 + 6 * 0.8 = 0.4 + 4.8 = 5.2 - assert_almost_equal(q.sld.value, 5.2) + assert_almost_equal(q.sld, 5.2) diff --git a/tests/summary/test_summary.py b/tests/summary/test_summary.py index 271de2c4..a636b504 100644 --- a/tests/summary/test_summary.py +++ b/tests/summary/test_summary.py @@ -4,7 +4,6 @@ import os from unittest.mock import MagicMock -import numpy as np import pytest from easyscience import global_object @@ -137,7 +136,7 @@ def test_experiments_section(self, project: Project) -> None: assert 'No. of data points' in html assert '408' in html assert 'Resolution function' in html - assert 'PercentageFwhm' in html + assert 'Pointwise' in html def test_experiments_section_percentage_fhwm(self, project: Project) -> None: # When @@ -168,51 +167,6 @@ def test_refinement_section(self, project: Project) -> None: assert 'No. of free parameters:' in html assert '0' in html assert 'No. of constraints' in html - # The (previously misspelt) constraints token must be fully substituted. - assert 'num_constriants' not in html - assert 'num_constraints' not in html - - @staticmethod - def _populate_fit_results(project: Project, chi2: float, n_points: int, n_pars: int) -> None: - """Emulate a completed fit by storing results on the project's fitter.""" - fit_result = MagicMock() - fit_result.chi2 = chi2 - fit_result.x = np.arange(n_points) - fit_result.n_pars = n_pars - project.fitter._fit_results = [fit_result] - - def test_compute_goodness_of_fit_na_before_fit(self, project: Project) -> None: - # When - summary = Summary(project) - - # Then Expect: no fit has been run, so goodness-of-fit is unavailable. - assert summary._compute_goodness_of_fit() == 'N/A' - - def test_compute_goodness_of_fit_after_fit(self, project: Project) -> None: - # When: reduced chi² = 20 / (14 - 4) = 2.0 - summary = Summary(project) - self._populate_fit_results(project, chi2=20.0, n_points=14, n_pars=4) - - # Then - gof = summary._compute_goodness_of_fit() - - # Expect - assert gof != 'N/A' - assert float(gof) == pytest.approx(2.0) - - def test_refinement_section_shows_goodness_of_fit(self, project: Project) -> None: - # When - summary = Summary(project) - self._populate_fit_results(project, chi2=20.0, n_points=14, n_pars=4) - gof = summary._compute_goodness_of_fit() - - # Then - html = summary._refinement_section() - - # Expect: the template token is replaced with the actual value, not 'N/A'. - assert gof != 'N/A' - assert gof in html - assert 'goodness_of_fit' not in html def test_save_sld_plot(self, project: Project, tmp_path) -> None: # When diff --git a/tests/test_fitting.py b/tests/test_fitting.py index 896de593..9fd02a4b 100644 --- a/tests/test_fitting.py +++ b/tests/test_fitting.py @@ -4,6 +4,7 @@ import os from unittest.mock import MagicMock +from unittest.mock import patch import numpy as np import pytest @@ -287,46 +288,6 @@ def test_reduced_chi_uses_global_dof_across_fit_results(): assert fitter.reduced_chi == pytest.approx(expected) -def test_record_fit_results_populates_reduced_chi(): - """Results computed outside the fitter can be recorded so reduced_chi works. - - Mirrors the app path where the threaded fit runs on the low-level - easy_science_multi_fitter and the high-level results must be recorded - explicitly (otherwise the HTML summary goodness-of-fit shows 'N/A'). - """ - model = Model() - model.interface = CalculatorFactory() - fitter = MultiFitter(model) - - assert fitter.reduced_chi is None - - fit_result = MagicMock() - fit_result.chi2 = 20.0 - fit_result.x = np.arange(14) - fit_result.n_pars = 4 - - fitter.record_fit_results([fit_result]) - - assert fitter.reduced_chi == pytest.approx(20.0 / (14 - 4)) - - -def test_record_fit_results_none_clears_state(): - model = Model() - model.interface = CalculatorFactory() - fitter = MultiFitter(model) - - fit_result = MagicMock() - fit_result.chi2 = 20.0 - fit_result.x = np.arange(14) - fit_result.n_pars = 4 - fitter.record_fit_results([fit_result]) - - fitter.record_fit_results(None) - - assert fitter.reduced_chi is None - assert fitter.chi2 is None - - def test_fit_single_data_set_1d_all_zero_variance_raises(): """Legacy mask mode raises when all points have zero variance.""" model = Model() @@ -848,6 +809,44 @@ def _fake_fit(*, x, y, weights): # --------------------------------------------------------------------------- +def _fake_sampling_results(draws=None, param_names=None, state=None, logp=None): + """Build a stand-in for the core ``SamplingResults`` returned by ``Sampler.sample``.""" + res = MagicMock() + res.draws = np.ones((10, 2)) if draws is None else draws + res.param_names = ['a', 'b'] if param_names is None else param_names + res.state = state + res.logp = logp + return res + + +def _patch_sampler(capture, results=None): + """Patch ``easyreflectometry.fitting.Sampler`` and capture its call args. + + Records the constructor's ``(x, y, weights)`` and the ``sample()`` + hyperparameters into the ``capture`` dict, and returns ``results`` (a + fake ``SamplingResults``) from ``sample()``. + """ + results = results if results is not None else _fake_sampling_results() + + def _ctor(fitter, *, x, y, weights, **kwargs): + capture['fitter'] = fitter + capture['x'] = x + capture['y'] = y + capture['weights'] = weights + capture.update(kwargs) # e.g. sampler_kwargs if passed to the ctor + instance = MagicMock() + + def _sample(**sample_kwargs): + capture.update(sample_kwargs) + return results + + instance.sample = MagicMock(side_effect=_sample) + capture['instance'] = instance + return instance + + return patch('easyreflectometry.fitting.Sampler', side_effect=_ctor) + + class TestMCMCSampleRequiresBumpsEngine: """mcmc_sample() must raise when the core engine is not a BUMPS instance.""" @@ -864,138 +863,129 @@ def test_raises_runtime_error_when_not_bumps(self): with pytest.raises(RuntimeError, match='Bayesian sampling requires a BUMPS minimizer'): fitter.mcmc_sample(data) - def test_wrapper_check_runs_before_core_mcmc_sample(self): - """The wrapper-level guard must fire before delegating to the core sampler. + def test_wrapper_check_runs_before_sampler(self): + """The wrapper-level guard must fire before constructing the core ``Sampler``. - Replace the core ``mcmc_sample`` with a sentinel that would record any call; - the guard should raise without invoking it. + Patch ``Sampler`` with a sentinel that would record any instantiation; + the guard should raise without ever building it. """ model = Model() model.interface = CalculatorFactory() fitter = MultiFitter(model) # default minimizer is LMFit, not BUMPS - core_called = {'count': 0} - - def _should_not_be_called(**_kwargs): - core_called['count'] += 1 - return {'draws': np.empty((0, 0)), 'param_names': [], 'state': None, 'logp': None} - - fitter.easy_science_multi_fitter.mcmc_sample = _should_not_be_called + capture = {} data = sc.DataGroup({ 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, }) - with pytest.raises(RuntimeError, match='Bayesian sampling requires a BUMPS minimizer'): - fitter.mcmc_sample(data) - assert core_called['count'] == 0 + with _patch_sampler(capture) as sampler_cls: + with pytest.raises(RuntimeError, match='Bayesian sampling requires a BUMPS minimizer'): + fitter.mcmc_sample(data) + sampler_cls.assert_not_called() class TestMCMCSampleBasic: """Basic mcmc_sample() dispatch and return-value forwarding.""" - def test_returns_core_result_dict(self): - """mcmc_sample() returns whatever the core MultiFitter.mcmc_sample() returns.""" + def test_returns_result_dict_from_sampler(self): + """mcmc_sample() returns a dict built from the core Sampler's SamplingResults.""" model = Model() model.interface = CalculatorFactory() fitter = MultiFitter(model) - # Mock the core MultiFitter.mcmc_sample to return a known dict - fake_result = {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} fitter.easy_science_multi_fitter = MagicMock() fitter.easy_science_multi_fitter.minimizer.package = 'bumps' - fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(return_value=fake_result) + + draws = np.ones((10, 2)) + sentinel_state = object() + logp = np.zeros(10) + results = _fake_sampling_results(draws=draws, param_names=['a', 'b'], state=sentinel_state, logp=logp) + + capture = {} data = sc.DataGroup({ 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, }) - result = fitter.mcmc_sample(data, samples=100, burn=20, thin=2, population=5) - assert result is fake_result + with _patch_sampler(capture, results=results): + result = fitter.mcmc_sample(data, samples=100, burn=20, thin=2, population=5) + + # The fitter passed to Sampler is the core MultiFitter + assert capture['fitter'] is fitter.easy_science_multi_fitter + assert result['draws'] is draws + assert result['param_names'] == ['a', 'b'] + assert result['state'] is sentinel_state + assert result['logp'] is logp - def test_forwards_hyperparams_to_core(self): - """Samples, burn, thin, population, chains are forwarded to core.""" + def test_forwards_hyperparams_to_sampler(self): + """Samples, burn, thin, population are forwarded to Sampler.sample().""" model = Model() model.interface = CalculatorFactory() fitter = MultiFitter(model) - captured = {} - - def _fake_mcmc_sample(*, x, y, weights, samples, burn, thin, population, **kwargs): - captured['samples'] = samples - captured['burn'] = burn - captured['thin'] = thin - captured['population'] = population - return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} - fitter.easy_science_multi_fitter = MagicMock() fitter.easy_science_multi_fitter.minimizer.package = 'bumps' - fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + capture = {} data = sc.DataGroup({ 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, }) - fitter.mcmc_sample(data, samples=500, burn=100, thin=5, population=8) - assert captured['samples'] == 500 - assert captured['burn'] == 100 - assert captured['thin'] == 5 - assert captured['population'] == 8 + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=500, burn=100, thin=5, population=8) + assert capture['samples'] == 500 + assert capture['burn'] == 100 + assert capture['thin'] == 5 + assert capture['population'] == 8 - def test_forwards_population_to_core(self): - """'population' argument is forwarded to core.""" + def test_forwards_population_to_sampler(self): + """'population' argument is forwarded to Sampler.sample().""" model = Model() model.interface = CalculatorFactory() fitter = MultiFitter(model) - captured = {} - - def _fake_mcmc_sample(*, x, y, weights, population, **kwargs): - captured['population'] = population - return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} - fitter.easy_science_multi_fitter = MagicMock() fitter.easy_science_multi_fitter.minimizer.package = 'bumps' - fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + capture = {} data = sc.DataGroup({ 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, }) - fitter.mcmc_sample(data, samples=100, burn=20, thin=2, population=6) - assert captured['population'] == 6 + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2, population=6) + assert capture['population'] == 6 class TestMCMCSampleInitializer: """initializer parameter is forwarded via sampler_kwargs.""" def test_initializer_passed_as_sampler_kwargs_init(self): - """initializer='lhs' should be passed as sampler_kwargs={'init': 'lhs'} to core.""" + """initializer='lhs' should be passed as sampler_kwargs={'init': 'lhs'} to Sampler.sample().""" model = Model() model.interface = CalculatorFactory() fitter = MultiFitter(model) - captured = {} - - def _fake_mcmc_sample(*, sampler_kwargs, **kwargs): - captured['sampler_kwargs'] = sampler_kwargs - return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} - fitter.easy_science_multi_fitter = MagicMock() fitter.easy_science_multi_fitter.minimizer.package = 'bumps' - fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + capture = {} data = sc.DataGroup({ 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, }) - fitter.mcmc_sample(data, samples=100, burn=20, thin=2, initializer='lhs') - assert captured['sampler_kwargs'] == {'init': 'lhs'} + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2, initializer='lhs') + assert capture['sampler_kwargs'] == {'init': 'lhs'} def test_initializer_none_omits_sampler_kwargs(self): """When initializer is None, sampler_kwargs should be None, not an empty dict.""" @@ -1003,23 +993,19 @@ def test_initializer_none_omits_sampler_kwargs(self): model.interface = CalculatorFactory() fitter = MultiFitter(model) - captured = {} - - def _fake_mcmc_sample(*, sampler_kwargs, **kwargs): - captured['sampler_kwargs'] = sampler_kwargs - return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} - fitter.easy_science_multi_fitter = MagicMock() fitter.easy_science_multi_fitter.minimizer.package = 'bumps' - fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) + + capture = {} data = sc.DataGroup({ 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, 10))}, 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(10), variances=np.ones(10) * 0.01)}, }) - fitter.mcmc_sample(data, samples=100, burn=20, thin=2) - assert captured['sampler_kwargs'] is None + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + assert capture['sampler_kwargs'] is None class TestMCMCSampleZeroVariance: @@ -1034,17 +1020,10 @@ def test_hybrid_transforms_zero_variance_points(self): # Use legacy_mask so zero-variance points are dropped fitter = MultiFitter(model, objective='legacy_mask') - captured = {} - - def _fake_mcmc_sample(*, x, y, weights, **kwargs): - captured['x'] = x - captured['y'] = y - captured['weights'] = weights - return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + capture = {} fitter.easy_science_multi_fitter = MagicMock() fitter.easy_science_multi_fitter.minimizer.package = 'bumps' - fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) qz = np.linspace(0.01, 0.3, 10) r = np.exp(-qz * 50) @@ -1058,12 +1037,13 @@ def _fake_mcmc_sample(*, x, y, weights, **kwargs): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') - fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) # legacy_mask should drop the 2 zero-variance points - assert len(captured['x'][0]) == 8 - assert len(captured['y'][0]) == 8 - assert len(captured['weights'][0]) == 8 + assert len(capture['x'][0]) == 8 + assert len(capture['y'][0]) == 8 + assert len(capture['weights'][0]) == 8 mask_warnings = [str(ww.message) for ww in w if 'Masked' in str(ww.message)] assert len(mask_warnings) == 1 @@ -1077,16 +1057,10 @@ def test_per_call_objective_override(self): model.interface = CalculatorFactory() fitter = MultiFitter(model, objective='legacy_mask') # default - captured = {} - - def _fake_mcmc_sample(*, x, y, weights, **kwargs): - captured['x'] = x - captured['y'] = y - return {'draws': np.ones((10, 2)), 'param_names': ['a', 'b'], 'state': None, 'logp': None} + capture = {} fitter.easy_science_multi_fitter = MagicMock() fitter.easy_science_multi_fitter.minimizer.package = 'bumps' - fitter.easy_science_multi_fitter.mcmc_sample = MagicMock(side_effect=_fake_mcmc_sample) qz = np.linspace(0.01, 0.3, 10) r = np.exp(-qz * 50) @@ -1101,6 +1075,194 @@ def _fake_mcmc_sample(*, x, y, weights, **kwargs): # Override to hybrid — should keep all 10 points with warnings.catch_warnings(record=True): warnings.simplefilter('always') - fitter.mcmc_sample(data, samples=100, burn=20, thin=2, objective='hybrid') + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2, objective='hybrid') + + assert len(capture['x'][0]) == 10 # all points kept (Mighell-substituted) + + +class TestMCMCSampleMighellWarningsAndZeroVarianceGuard: + """mcmc_sample() must warn about Mighell-transformed points feeding the + Bayesian likelihood, and refuse data with no uncertainties at all.""" + + @staticmethod + def _make_fitter(objective='hybrid'): + model = Model() + model.interface = CalculatorFactory() + fitter = MultiFitter(model, objective=objective) + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + return fitter + + @staticmethod + def _make_data(variances): + qz = np.linspace(0.01, 0.3, 10) + r = np.exp(-qz * 50) + return sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=qz)}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=r, variances=variances)}, + }) + + def test_hybrid_partial_zero_variance_warns_mighell_substitution(self): + fitter = self._make_fitter() + variances = np.ones(10) * 0.01 + variances[3:5] = 0.0 + data = self._make_data(variances) + + capture = {} + with pytest.warns( + UserWarning, + match=r'Mighell substitution to 2 zero-variance point\(s\) in reflectivity 0 during sampling', + ): + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + + assert len(capture['x'][0]) == 10 + + def test_mighell_objective_warns_transform_all_points(self): + fitter = self._make_fitter(objective='mighell') + data = self._make_data(np.ones(10) * 0.01) + + capture = {} + with pytest.warns( + UserWarning, + match=r'Applied Mighell transform to all 10 point\(s\) in reflectivity 0 during sampling', + ): + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + + assert len(capture['x'][0]) == 10 + + def test_all_zero_variance_hybrid_raises(self): + fitter = self._make_fitter() + data = self._make_data(np.zeros(10)) + + capture = {} + with _patch_sampler(capture) as sampler_cls: + with pytest.raises(ValueError, match='all points have zero variance'): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + sampler_cls.assert_not_called() + + def test_all_zero_variance_legacy_mask_raises(self): + fitter = self._make_fitter(objective='legacy_mask') + data = self._make_data(np.zeros(10)) + + capture = {} + with _patch_sampler(capture) as sampler_cls: + with pytest.raises(ValueError, match='all points have zero variance'): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + sampler_cls.assert_not_called() + + def test_all_zero_variance_allowed_with_explicit_mighell(self): + """Explicitly opting in to objective='mighell' keeps working on + variance-free (e.g. raw count) data, with a warning.""" + fitter = self._make_fitter(objective='mighell') + data = self._make_data(np.zeros(10)) + + capture = {} + with pytest.warns(UserWarning, match='not a true likelihood'): + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + + assert len(capture['x'][0]) == 10 + + +# --------------------------------------------------------------------------- +# Analytic weighted-least-squares convention test (issue #370) +# --------------------------------------------------------------------------- + + +def _analytic_wls(design, y, point_weights): + """Solve min_beta sum_i (point_weights_i * (y_i - design_i . beta))^2. + + Returns the solution and the unscaled covariance (X^T W X)^-1 of the + corresponding weighted least-squares problem. + """ + a = design * point_weights[:, None] + b = y * point_weights + beta, *_ = np.linalg.lstsq(a, b, rcond=None) + covariance = np.linalg.inv(a.T @ a) + return beta, covariance + + +@pytest.mark.parametrize('minimizer', [AvailableMinimizers.LMFit, AvailableMinimizers.Bumps]) +def test_fit_weight_convention_matches_analytic_wls(minimizer): + """Pin the weights = 1/sigma convention end-to-end against analytic WLS. + + A reflectometry model is exactly linear in (scale, background): + R(q) = scale * f(q) + background, so weighted least squares has the + closed-form solution beta = (X^T W X)^-1 X^T W y with W = diag(1/sigma^2). + On heteroscedastic data the candidate weight conventions (1/sigma, sigma, + 1/sigma^2) give measurably different solutions, so this test fails if the + convention between ``_prepare_fit_arrays`` and the EasyScience core + minimizers ever drifts. See issue #370. + """ + construction_background = 1e-7 + si = Material(2.07, 0, 'Si') + sio2 = Material(3.47, 0, 'SiO2') + d2o = Material(6.36, 0, 'D2O') + sample = Sample( + Multilayer(Layer(si, 0, 0, 'Si layer')), + Multilayer(Layer(sio2, 30, 3, 'SiO2 layer')), + Multilayer(Layer(d2o, 0, 3, 'D2O Subphase')), + name='WLS Structure', + ) + model = Model(sample, 1.0, construction_background, PercentageFwhm(0.02), 'WLS Model') + model.interface = CalculatorFactory() + fitter = MultiFitter(model) + fitter.easy_science_multi_fitter.switch_minimizer(minimizer) + + # Unit-scale, zero-background reflectivity curve of the fixed structure + q = np.linspace(0.01, 0.25, 30) + f = fitter._fit_func[0](q) - construction_background + assert np.all(f > 0) + + # Deterministic heteroscedastic data that does NOT lie on the model + scale_true, background_true = 1.3, 4.0e-6 + signal = scale_true * f + background_true + fractional_error = 0.03 + 0.02 * np.cos(40.0 * q) ** 2 + sigma = fractional_error * signal + perturbation = 0.8 * np.cos(7.0 * np.arange(q.size) + 0.3) + y = signal + perturbation * sigma + variances = sigma**2 + + design = np.column_stack([f, np.ones_like(f)]) + beta, covariance = _analytic_wls(design, y, 1.0 / sigma) + beta_if_sigma, _ = _analytic_wls(design, y, sigma) + beta_if_inverse_variance, _ = _analytic_wls(design, y, 1.0 / sigma**2) + + # Sanity check: the conventions are distinguishable well beyond the fit tolerance + scale_tolerance = 1e-3 + background_tolerance = 5e-8 + scale_margin = min(abs(beta_if_sigma[0] - beta[0]), abs(beta_if_inverse_variance[0] - beta[0])) + assert scale_margin > 10 * scale_tolerance, 'test data cannot discriminate weight conventions' + + model.scale.fixed = False + model.scale.bounds = (0.5, 3.0) + model.scale.value = 1.0 + model.background.fixed = False + model.background.bounds = (1e-9, 1e-4) + model.background.value = 1e-6 + + data = DataSet1D( + name='wls_convention', + x=q, + y=y, + ye=variances, + model=model, + auto_background=False, + ) + result = fitter.fit_single_data_set_1d(data) - assert len(captured['x'][0]) == 10 # all points kept (Mighell-substituted) + assert result.success + assert model.scale.value == pytest.approx(beta[0], abs=scale_tolerance) + assert model.background.value == pytest.approx(beta[1], abs=background_tolerance) + + if minimizer == AvailableMinimizers.LMFit: + # lmfit scales the covariance by reduced chi-square (scale_covar=True) + residual = y - design @ beta + chi2 = float(np.sum((residual / sigma) ** 2)) + reduced_chi2 = chi2 / (q.size - 2) + expected_errors = np.sqrt(np.diag(covariance) * reduced_chi2) + assert model.scale.error == pytest.approx(expected_errors[0], rel=0.05) + assert model.background.error == pytest.approx(expected_errors[1], rel=0.05) diff --git a/tests/test_project.py b/tests/test_project.py index b1ae72eb..62dd67d2 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -19,6 +19,7 @@ from easyreflectometry.model import Model from easyreflectometry.model import ModelCollection from easyreflectometry.model import PercentageFwhm +from easyreflectometry.model import Pointwise from easyreflectometry.project import Project from easyreflectometry.sample import Layer from easyreflectometry.sample import Material @@ -476,7 +477,7 @@ def test_as_dict_minimizer(self): project = Project() project._fitter = MagicMock() project._fitter.easy_science_multi_fitter = MagicMock() - project._fitter.easy_science_multi_fitter.minimizer.enum = AvailableMinimizers.LMFit + project._fitter.easy_science_multi_fitter.minimizer = AvailableMinimizers.LMFit # Then project_dict = project.as_dict() @@ -663,7 +664,8 @@ def test_load_experiment(self): assert isinstance(project.experiments[5], DataSet1D) assert project.experiments[5].name == 'Example data file from refnx docs' assert project.experiments[5].model == model_5 - assert isinstance(project.models[5].resolution_function, PercentageFwhm) + # example.ort carries an sQz column, so the measured resolution is used + assert isinstance(project.models[5].resolution_function, Pointwise) assert isinstance(project.models[4].resolution_function, PercentageFwhm) def test_load_experiment_sets_resolution_function_pointwise_when_xe_present(self, tmp_path): @@ -679,12 +681,13 @@ def test_load_experiment_sets_resolution_function_pointwise_when_xe_present(self # Then project.load_experiment_for_model_at_index(str(fpath)) - # Resolution is always set to PercentageFwhm - from easyreflectometry.model.resolution_functions import PercentageFwhm + # Expect Pointwise because xe (q-resolution) is present + resolution_function = project.models[0].resolution_function + assert isinstance(resolution_function, Pointwise) + # The 4th column is sQz (sigma); smearing() must return it unchanged + assert_allclose(resolution_function.smearing([0.01, 0.02]), [1e-4, 1e-4]) - assert isinstance(project.models[0].resolution_function, PercentageFwhm) - - def test_load_experiment_sets_linearspline_when_only_ye_present(self, tmp_path): + def test_load_experiment_keeps_percentage_fwhm_when_no_xe(self, tmp_path): # When global_object.map._clear() project = Project() @@ -697,11 +700,45 @@ def test_load_experiment_sets_linearspline_when_only_ye_present(self, tmp_path): # Then project.load_experiment_for_model_at_index(str(fpath)) - # Resolution is always set to PercentageFwhm - from easyreflectometry.model.resolution_functions import PercentageFwhm - + # No q-resolution data available, so the 5% FWHM default is kept assert isinstance(project.models[0].resolution_function, PercentageFwhm) + def test_apply_resolution_function_prefers_pointwise_falls_back_to_percentage(self): + # When + global_object.map._clear() + project = Project() + model = Model() + with_xe = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1], xe=[1e-8, 4e-8]) + zero_xe = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1], xe=[0.0, 0.0]) + no_xe = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1]) + + # Then Expect + project._apply_resolution_function(with_xe, model) + assert isinstance(model.resolution_function, Pointwise) + # xe holds sQz variances; smearing() must return sigma = sqrt(xe) + assert_allclose(model.resolution_function.smearing([0.01, 0.02]), [1e-4, 2e-4]) + + project._apply_resolution_function(zero_xe, model) + assert isinstance(model.resolution_function, PercentageFwhm) + + project._apply_resolution_function(no_xe, model) + assert isinstance(model.resolution_function, PercentageFwhm) + + def test_load_all_experiments_from_file_sets_pointwise_when_sqz_present(self): + # When + global_object.map._clear() + project = Project() + project.models = ModelCollection(Model()) + fpath = os.path.join(PATH_STATIC, 'test_example2.ort') + + # Then + n_loaded = project.load_all_experiments_from_file(fpath) + + # Expect + assert n_loaded == 2 + assert list(project.experiments.keys()) == [0, 1] + assert isinstance(project.models[0].resolution_function, Pointwise) + def test_experimental_data_at_index(self): # When global_object.map._clear() diff --git a/tests/test_topmost_nesting.py b/tests/test_topmost_nesting.py index 1003efe5..622991b2 100644 --- a/tests/test_topmost_nesting.py +++ b/tests/test_topmost_nesting.py @@ -47,8 +47,8 @@ def test_copy(): assert model._resolution_function.smearing(5.5) == model_copy._resolution_function.smearing(5.5) assert model.interface().name == model_copy.interface().name assert_almost_equal( - model.interface().reflectivity_profile([0.3], model.unique_name), - model_copy.interface().reflectivity_profile([0.3], model_copy.unique_name), + model.interface().reflectity_profile([0.3], model.unique_name), + model_copy.interface().reflectity_profile([0.3], model_copy.unique_name), ) assert model.unique_name != model_copy.unique_name assert model.name == model_copy.name diff --git a/tests/unit/test_fitting_mcmc.py b/tests/unit/test_fitting_mcmc.py new file mode 100644 index 00000000..ec2e67ec --- /dev/null +++ b/tests/unit/test_fitting_mcmc.py @@ -0,0 +1,220 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Unit tests for the ``Sampler``-based MCMC workflow in ``MultiFitter.mcmc_sample``. + +The actual BUMPS/DREAM sampling is delegated to ``easyscience.fitting.Sampler``; +these tests mock the ``Sampler`` class so they stay fast while still exercising +the wrapper logic: data preparation, zero-variance guards, warning emission, +result-dict construction, and retention of the sampler for chain extension. +""" + +from unittest.mock import MagicMock +from unittest.mock import patch + +import numpy as np +import pytest +import scipp as sc +from easyscience import global_object +from easyscience.fitting.minimizers.factory import AvailableMinimizers + +from easyreflectometry.calculators import CalculatorFactory +from easyreflectometry.fitting import MultiFitter +from easyreflectometry.fitting import _fit_result_reduced_chi +from easyreflectometry.fitting import _flatten_list +from easyreflectometry.model import Model + + +@pytest.fixture(autouse=True) +def clear_global_map(): + global_object.map._clear() + yield + global_object.map._clear() + + +def _make_fitter() -> MultiFitter: + model = Model() + model.interface = CalculatorFactory() + return MultiFitter(model) + + +def _make_bumps_fitter() -> MultiFitter: + """A MultiFitter whose core fitter reports a BUMPS minimizer without running one.""" + fitter = _make_fitter() + fitter.easy_science_multi_fitter = MagicMock() + fitter.easy_science_multi_fitter.minimizer.package = 'bumps' + return fitter + + +def _make_data(variances: np.ndarray, n: int = 10) -> sc.DataGroup: + return sc.DataGroup({ + 'coords': {'Qz_0': sc.array(dims=['Qz_0'], values=np.linspace(0.01, 0.3, n))}, + 'data': {'R_0': sc.array(dims=['Qz_0'], values=np.ones(n), variances=variances)}, + }) + + +def _fake_sampling_results(): + results = MagicMock() + results.draws = np.ones((10, 2)) + results.param_names = ['a', 'b'] + results.state = object() + results.logp = np.zeros(10) + return results + + +def _patch_sampler(capture: dict, results=None): + """Patch ``easyreflectometry.fitting.Sampler`` recording ctor and sample() args.""" + results = results if results is not None else _fake_sampling_results() + + def _ctor(fitter, *, x, y, weights, **kwargs): + capture['fitter'] = fitter + capture['x'] = x + capture['y'] = y + capture['weights'] = weights + instance = MagicMock() + + def _sample(**sample_kwargs): + capture.update(sample_kwargs) + return results + + instance.sample = MagicMock(side_effect=_sample) + capture['instance'] = instance + return instance + + return patch('easyreflectometry.fitting.Sampler', side_effect=_ctor) + + +class TestMCMCSampleGuards: + def test_raises_runtime_error_when_minimizer_is_not_bumps(self): + fitter = _make_fitter() # default minimizer is LMFit, not BUMPS + data = _make_data(np.ones(10) * 0.01) + + with pytest.raises(RuntimeError, match='Bayesian sampling requires a BUMPS minimizer'): + fitter.mcmc_sample(data) + + def test_all_zero_variance_raises_value_error(self): + """Sampling without any uncertainties has no defined likelihood.""" + fitter = _make_bumps_fitter() + data = _make_data(np.zeros(10)) + + capture = {} + with _patch_sampler(capture) as sampler_cls: + with pytest.raises(ValueError, match='all points have zero variance'): + fitter.mcmc_sample(data) + sampler_cls.assert_not_called() + + def test_all_zero_variance_allowed_with_mighell_objective(self): + """objective='mighell' is the explicit opt-in for missing uncertainties.""" + fitter = _make_bumps_fitter() + data = _make_data(np.zeros(10)) + + capture = {} + with _patch_sampler(capture): + with pytest.warns(UserWarning, match='Mighell transform to all'): + result = fitter.mcmc_sample(data, samples=100, burn=10, thin=2, objective='mighell') + assert set(result) == {'draws', 'param_names', 'state', 'logp'} + + +class TestMCMCSampleWarnings: + def test_legacy_mask_warns_about_masked_points(self): + fitter = _make_bumps_fitter() + variances = np.ones(10) * 0.01 + variances[3] = 0.0 + data = _make_data(variances) + + capture = {} + with _patch_sampler(capture): + with pytest.warns(UserWarning, match='Masked 1 data point'): + fitter.mcmc_sample(data, samples=100, burn=10, thin=2, objective='legacy_mask') + # The masked point must not reach the Sampler + assert len(capture['x'][0]) == 9 + + def test_hybrid_warns_about_mighell_substitution(self): + fitter = _make_bumps_fitter() + variances = np.ones(10) * 0.01 + variances[3] = 0.0 + data = _make_data(variances) + + capture = {} + with _patch_sampler(capture): + with pytest.warns(UserWarning, match='Mighell substitution to 1'): + fitter.mcmc_sample(data, samples=100, burn=10, thin=2) + # Hybrid keeps every point + assert len(capture['x'][0]) == 10 + + +class TestMCMCSampleDispatch: + def test_returns_dict_built_from_sampling_results(self): + fitter = _make_bumps_fitter() + data = _make_data(np.ones(10) * 0.01) + results = _fake_sampling_results() + + capture = {} + with _patch_sampler(capture, results=results): + result = fitter.mcmc_sample(data, samples=100, burn=20, thin=2, population=5) + + assert capture['fitter'] is fitter.easy_science_multi_fitter + assert result['draws'] is results.draws + assert result['param_names'] == results.param_names + assert result['state'] is results.state + assert result['logp'] is results.logp + + def test_forwards_hyperparameters_to_sampler_sample(self): + fitter = _make_bumps_fitter() + data = _make_data(np.ones(10) * 0.01) + + capture = {} + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=500, burn=100, thin=5, population=8) + assert capture['samples'] == 500 + assert capture['burn'] == 100 + assert capture['thin'] == 5 + assert capture['population'] == 8 + assert capture['sampler_kwargs'] is None + + def test_initializer_forwarded_via_sampler_kwargs(self): + fitter = _make_bumps_fitter() + data = _make_data(np.ones(10) * 0.01) + + capture = {} + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2, initializer='lhs') + assert capture['sampler_kwargs'] == {'init': 'lhs'} + + def test_sampler_retained_for_chain_extension(self): + """The Sampler instance must be kept on ``fitter.sampler`` so the chain + can be extended with ``fitter.sampler.extend(...)`` without a new burn-in.""" + fitter = _make_bumps_fitter() + assert fitter.sampler is None + + data = _make_data(np.ones(10) * 0.01) + capture = {} + with _patch_sampler(capture): + fitter.mcmc_sample(data, samples=100, burn=20, thin=2) + + assert fitter.sampler is capture['instance'] + + +class TestMultiFitterHelpers: + def test_switch_minimizer_delegates_to_core_fitter(self): + fitter = _make_fitter() + fitter.easy_science_multi_fitter = MagicMock() + fitter.switch_minimizer(AvailableMinimizers.Bumps) + fitter.easy_science_multi_fitter.switch_minimizer.assert_called_once_with(AvailableMinimizers.Bumps) + + def test_flatten_list_flattens_nested_lists(self): + result = _flatten_list([[1, 2], [3], [4, 5]]) + assert isinstance(result, np.ndarray) + assert list(result) == [1, 2, 3, 4, 5] + + def test_fit_result_reduced_chi_raises_without_any_attribute(self): + result = MagicMock(spec=[]) # no reduced_chi, no reduced_chi2 + with pytest.raises(AttributeError, match='neither reduced_chi nor reduced_chi2'): + _fit_result_reduced_chi(result) + + def test_fit_func_computes_reflectivity_through_calculator(self): + """The factory's fit_func must evaluate the model reflectivity profile.""" + fitter = _make_fitter() + q = np.linspace(0.01, 0.1, 5) + reflectivity = fitter._fit_func[0](q) + assert np.shape(reflectivity) == (5,) + assert np.all(np.isfinite(reflectivity)) diff --git a/tests/unit/test_material_and_calculations.py b/tests/unit/test_material_and_calculations.py new file mode 100644 index 00000000..7c5977b9 --- /dev/null +++ b/tests/unit/test_material_and_calculations.py @@ -0,0 +1,95 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Unit tests for MaterialMixture derived sld/isld, special calculations, +and default parameter limits.""" + +import numpy as np +import pytest +from easyscience import global_object +from easyscience.variable import Parameter + +from easyreflectometry.limits import apply_default_limits +from easyreflectometry.sample import Material +from easyreflectometry.sample.elements.materials.material_mixture import MaterialMixture +from easyreflectometry.special.calculations import molecular_weight +from easyreflectometry.special.calculations import neutron_scattering_length +from easyreflectometry.special.calculations import weighted_average + + +@pytest.fixture(autouse=True) +def clear_global_map(): + global_object.map._clear() + yield + global_object.map._clear() + + +class TestMaterialMixtureSld: + def test_sld_and_isld_are_floats_from_weighted_average(self): + material_a = Material(name='A', sld=2.0, isld=0.5) + material_b = Material(name='B', sld=6.0, isld=1.5) + mixture = MaterialMixture(material_a=material_a, material_b=material_b, fraction=0.25) + + assert isinstance(mixture.sld, float) + assert isinstance(mixture.isld, float) + assert mixture.sld == pytest.approx(weighted_average(2.0, 6.0, 0.25)) + assert mixture.isld == pytest.approx(weighted_average(0.5, 1.5, 0.25)) + + def test_sld_and_isld_follow_fraction_changes(self): + material_a = Material(name='A', sld=2.0, isld=0.0) + material_b = Material(name='B', sld=6.0, isld=1.0) + mixture = MaterialMixture(material_a=material_a, material_b=material_b, fraction=0.25) + + mixture.fraction = 0.75 + + assert mixture.sld == pytest.approx(5.0) + assert mixture.isld == pytest.approx(0.75) + + +class TestNeutronScatteringLength: + def test_element_without_absorption_has_zero_imaginary_part(self): + result = neutron_scattering_length('Si') + assert result.real == pytest.approx(4.1507e-05, rel=1e-3) + assert result.imag == 0.0 + + def test_element_with_absorption_has_negative_imaginary_part(self): + # Boron has a non-zero imaginary bound coherent scattering length (b_c_i) + result = neutron_scattering_length('B') + assert result.real == pytest.approx(5.3e-05, rel=1e-3) + assert result.imag == pytest.approx(-2.1e-06, rel=1e-3) + + def test_formula_scales_with_stoichiometry(self): + single = neutron_scattering_length('B') + double = neutron_scattering_length('B2') + assert double.real == pytest.approx(2 * single.real) + assert double.imag == pytest.approx(2 * single.imag) + + +class TestMolecularWeight: + def test_molecular_weight_of_water(self): + assert molecular_weight('H2O') == pytest.approx(18.015, rel=1e-3) + + +class TestApplyDefaultLimits: + def test_percentage_limits_set_for_infinite_bounds(self): + param = Parameter('thickness', 10.0, min=-np.inf, max=np.inf) + apply_default_limits(param, 'thickness') + assert param.min == pytest.approx(5.0) + assert param.max == pytest.approx(20.0) + + def test_percentage_limits_leave_finite_bounds_untouched(self): + param = Parameter('roughness', 10.0, min=2.0, max=30.0) + apply_default_limits(param, 'roughness') + assert param.min == 2.0 + assert param.max == 30.0 + + def test_percentage_limits_skip_zero_value(self): + param = Parameter('thickness', 0.0, min=-np.inf, max=np.inf) + apply_default_limits(param, 'thickness') + assert np.isinf(param.min) + assert np.isinf(param.max) + + def test_sld_gets_fixed_limits(self): + param = Parameter('sld', 4.0, min=-np.inf, max=np.inf) + apply_default_limits(param, 'sld') + assert param.min == -1.0 + assert param.max == 10.0 diff --git a/tests/unit/test_project_core.py b/tests/unit/test_project_core.py new file mode 100644 index 00000000..5a227255 --- /dev/null +++ b/tests/unit/test_project_core.py @@ -0,0 +1,143 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Unit tests for Project material-index helpers, resolution auto-selection, +ORSO loading, model data generation, and minimizer serialization.""" + +import os + +import numpy as np +import pytest +from easyscience import global_object + +import easyreflectometry +from easyreflectometry.data import DataSet1D +from easyreflectometry.model import PercentageFwhm +from easyreflectometry.model import Pointwise +from easyreflectometry.project import Project + +PATH_STATIC = os.path.join(os.path.dirname(easyreflectometry.__file__), '..', '..', 'tests', '_static') + + +@pytest.fixture(autouse=True) +def clear_global_map(): + global_object.map._clear() + yield + global_object.map._clear() + + +@pytest.fixture +def project() -> Project: + return Project() + + +class TestGetIndexMaterials: + def test_get_index_air_adds_material_when_missing(self, project: Project): + assert len(project._materials) == 0 + index = project.get_index_air() + assert project._materials[index].name == 'Air' + assert project._materials[index].sld.value == 0.0 + + def test_get_index_si_adds_material_when_missing(self, project: Project): + index = project.get_index_si() + assert project._materials[index].name == 'Si' + assert project._materials[index].sld.value == 2.07 + + def test_get_index_sio2_adds_material_when_missing(self, project: Project): + index = project.get_index_sio2() + assert project._materials[index].name == 'SiO2' + assert project._materials[index].sld.value == 3.47 + + def test_get_index_d2o_adds_material_when_missing(self, project: Project): + index = project.get_index_d2o() + assert project._materials[index].name == 'D2O' + assert project._materials[index].sld.value == 6.36 + + def test_get_index_is_idempotent(self, project: Project): + first = project.get_index_d2o() + count_after_first = len(project._materials) + second = project.get_index_d2o() + assert first == second + assert len(project._materials) == count_after_first + + def test_indices_point_at_distinct_materials(self, project: Project): + indices = { + 'Air': project.get_index_air(), + 'D2O': project.get_index_d2o(), + 'Si': project.get_index_si(), + 'SiO2': project.get_index_sio2(), + } + assert len(set(indices.values())) == 4 + for name, index in indices.items(): + assert project._materials[index].name == name + + +class TestApplyResolutionFunction: + def test_pointwise_used_when_experiment_has_q_variances(self, project: Project): + project.default_model() + model = project.models[0] + experiment = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1], xe=[1e-8, 4e-8]) + + project._apply_resolution_function(experiment, model) + + assert isinstance(model.resolution_function, Pointwise) + # sigma at the data points is sqrt(sQz) + np.testing.assert_allclose(model.resolution_function.smearing(), np.sqrt([1e-8, 4e-8])) + + def test_percentage_fwhm_fallback_when_q_variances_all_zero(self, project: Project): + project.default_model() + model = project.models[0] + experiment = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1], xe=[0.0, 0.0]) + + project._apply_resolution_function(experiment, model) + + assert isinstance(model.resolution_function, PercentageFwhm) + assert model.resolution_function.constant == 5.0 + + def test_percentage_fwhm_fallback_when_q_variances_absent(self, project: Project): + project.default_model() + model = project.models[0] + experiment = DataSet1D(x=[0.01, 0.02], y=[1.0, 2.0], ye=[0.1, 0.1]) + + project._apply_resolution_function(experiment, model) + + assert isinstance(model.resolution_function, PercentageFwhm) + + +class TestLoadOrsoFile: + def test_load_orso_file_creates_model_and_experiment(self, project: Project): + with pytest.warns(UserWarning): + project.load_orso_file(os.path.join(PATH_STATIC, 'example.ort')) + + assert len(project.models) == 1 + assert len(project.experiments) == 1 + assert project.experiments[0].name == 'Experiment from ORSO' + assert project.experiments[0].model is project.models[0] + assert project._with_experiments is True + + +class TestModelData: + def test_model_data_for_model_at_index_returns_reflectivity(self, project: Project): + project.default_model() + q_range = np.linspace(0.01, 0.1, 5) + + dataset = project.model_data_for_model_at_index(0, q_range=q_range) + + np.testing.assert_array_equal(dataset.x, q_range) + assert dataset.y.shape == (5,) + assert np.all(np.isfinite(dataset.y)) + # Reflectivity near total reflection is of order unity and decays with q + assert dataset.y[0] > dataset.y[-1] + + +class TestAsDictMinimizer: + def test_as_dict_uses_selection_when_no_fitter_exists(self, project: Project): + # No models -> the lazy fitter property stays None -> fall back to selection + project_dict = project.as_dict() + assert project._fitter is None + assert project_dict['fitter_minimizer'] == project._minimizer_selection.name + + def test_as_dict_reads_minimizer_from_fitter_when_models_exist(self, project: Project): + project.default_model() + project_dict = project.as_dict() + assert project._fitter is not None + assert project_dict['fitter_minimizer'] == project._fitter.easy_science_multi_fitter.minimizer.name diff --git a/tests/unit/test_summary_goodness_of_fit.py b/tests/unit/test_summary_goodness_of_fit.py new file mode 100644 index 00000000..cb1fb427 --- /dev/null +++ b/tests/unit/test_summary_goodness_of_fit.py @@ -0,0 +1,86 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Unit tests for the summary goodness-of-fit computation and refinement section.""" + +from unittest.mock import MagicMock + +import pytest +from easyscience import global_object + +from easyreflectometry import Project +from easyreflectometry.summary import Summary + + +@pytest.fixture +def project() -> Project: + global_object.map._clear() + project = Project() + project.default_model() + return project + + +def _fit_result(chi2=None, reduced_chi2=None, n_points=0, n_pars=0): + result = MagicMock() + result.chi2 = chi2 + result.reduced_chi2 = reduced_chi2 + result.x = list(range(n_points)) + result.n_pars = n_pars + return result + + +class TestComputeGoodnessOfFit: + def test_returns_na_when_no_fit_has_been_run(self, project: Project): + summary = Summary(project) + assert summary._compute_goodness_of_fit() == 'N/A' + + def test_returns_na_when_last_fit_results_is_empty(self, project: Project): + project._last_fit_results = [] + summary = Summary(project) + assert summary._compute_goodness_of_fit() == 'N/A' + + def test_single_result_uses_its_reduced_chi2(self, project: Project): + project._last_fit_results = [_fit_result(reduced_chi2=1.2345)] + summary = Summary(project) + assert summary._compute_goodness_of_fit() == '1.234' + + def test_multiple_results_aggregate_over_global_dof(self, project: Project): + # total chi2 = 30, total points = 16, n_pars = 6 -> dof = 10 -> gof = 3 + project._last_fit_results = [ + _fit_result(chi2=10.0, n_points=8, n_pars=6), + _fit_result(chi2=20.0, n_points=8, n_pars=6), + ] + summary = Summary(project) + assert summary._compute_goodness_of_fit() == '3' + + def test_multiple_results_with_nonpositive_dof_return_zero(self, project: Project): + project._last_fit_results = [ + _fit_result(chi2=10.0, n_points=2, n_pars=6), + _fit_result(chi2=20.0, n_points=2, n_pars=6), + ] + summary = Summary(project) + assert summary._compute_goodness_of_fit() == '0' + + def test_returns_na_when_result_values_are_invalid(self, project: Project): + project._last_fit_results = [_fit_result(reduced_chi2='not-a-number')] + summary = Summary(project) + assert summary._compute_goodness_of_fit() == 'N/A' + + +class TestRefinementSection: + def test_refinement_section_renders_counts_and_gof(self, project: Project): + project._last_fit_results = [_fit_result(reduced_chi2=2.5)] + summary = Summary(project) + + html = summary._refinement_section() + + assert '2.5' in html + # every placeholder must have been substituted with a number + for placeholder in ( + 'num_total_params', + 'num_free_params', + 'num_fixed_params', + 'num_constriants', + 'num_constraints', + 'goodness_of_fit', + ): + assert placeholder not in html From 576d45becce0aeeb9960dbb9b10351fc6784570d Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 31 Jul 2026 12:42:24 +0200 Subject: [PATCH 20/38] reparented easyscience, updated CHANGELOG --- CHANGELOG.md | 2 +- pyproject.toml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55aafcae..938f27b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# Version 1.7.0 (1 Aug 2026) Restored the measured per-point resolution on data load (issue #368). diff --git a/pyproject.toml b/pyproject.toml index ac183b21..9f90457f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,8 +23,7 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'easyscience @ git+https://github.com/easyscience/corelib.git@develop', - # 'easyscience', + 'easyscience', 'scipp', 'refnx', 'refl1d>=1.0.0', From eb381d9726906c8dd803f6d26a02b6f7dad7fd5e Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 31 Jul 2026 13:37:31 +0200 Subject: [PATCH 21/38] codecov related fixes (#391) --- pixi.toml | 6 +- tests/test_bayesian.py | 406 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 411 insertions(+), 1 deletion(-) diff --git a/pixi.toml b/pixi.toml index ab6c977e..f0023fba 100644 --- a/pixi.toml +++ b/pixi.toml @@ -92,7 +92,11 @@ user = { features = ['py-max', 'user'] } # 🧪 Testing Tasks ################## -unit-tests = 'python -m pytest tests/unit/ --color=yes -v' +# The bulk of the unit-test suite still lives at the top level of tests/ +# (pending migration into tests/unit/), so run the whole tree minus the +# functional and integration subtrees -- otherwise CI coverage only sees +# the handful of files under tests/unit/. +unit-tests = 'python -m pytest tests/ --ignore=tests/functional --ignore=tests/integration --color=yes -v' functional-tests = 'python -m pytest tests/functional/ --color=yes -v' # No -n auto: importing easyreflectometry pulls in arviz, and arviz 0.23.4 # (py-311-env) writes a "warn once per day" stamp file on import via a diff --git a/tests/test_bayesian.py b/tests/test_bayesian.py index 15404645..1cb78d4f 100644 --- a/tests/test_bayesian.py +++ b/tests/test_bayesian.py @@ -474,3 +474,409 @@ def test_in_analysis_namespace(self): assert hasattr(analysis, 'plot_distribution') assert 'plot_distribution' in analysis.__all__ + + +# =================================================================== +# Label wrapping helper +# =================================================================== + + +class TestWrapPairLabel: + def test_empty_string_unchanged(self): + from easyreflectometry.analysis.bayesian import _wrap_pair_label + + assert _wrap_pair_label('') == '' + + def test_short_name_unchanged(self): + from easyreflectometry.analysis.bayesian import _wrap_pair_label + + assert _wrap_pair_label('thickness') == 'thickness' + + def test_dotted_name_breaks_on_dots(self): + from easyreflectometry.analysis.bayesian import _wrap_pair_label + + assert _wrap_pair_label('layer1.thickness') == 'layer1.
thickness' + + def test_long_multiword_name_wraps(self): + from easyreflectometry.analysis.bayesian import _wrap_pair_label + + wrapped = _wrap_pair_label('a very long parameter name indeed', max_len=16) + assert '
' in wrapped + assert wrapped.replace('
', ' ') == 'a very long parameter name indeed' + + def test_long_single_word_unchanged(self): + from easyreflectometry.analysis.bayesian import _wrap_pair_label + + name = 'averyveryverylongsingleword' + assert _wrap_pair_label(name, max_len=16) == name + + +# =================================================================== +# Optional-dependency guards +# =================================================================== + + +class TestRequireHelpers: + def test_require_arviz_raises_when_unavailable(self, monkeypatch): + from easyreflectometry.analysis import bayesian as bayesian_mod + + monkeypatch.setattr(bayesian_mod, '_HAS_ARVIZ', False) + with pytest.raises(ImportError, match='arviz'): + bayesian_mod._require_arviz() + + def test_require_plotly_raises_when_unavailable(self, monkeypatch): + import builtins + + from easyreflectometry.analysis.bayesian import _require_plotly + + real_import = builtins.__import__ + + def _fake_import(name, *args, **kwargs): + if name.startswith('plotly'): + raise ImportError('plotly disabled for test') + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, '__import__', _fake_import) + with pytest.raises(ImportError, match='plotly'): + _require_plotly() + + def test_gelman_rubin_warns_and_returns_none_without_arviz(self, sample_draws, monkeypatch): + from easyreflectometry.analysis import bayesian as bayesian_mod + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + pr = PosteriorResults(draws, param_names) + monkeypatch.setattr(bayesian_mod, '_HAS_ARVIZ', False) + with pytest.warns(UserWarning, match='arviz'): + result = pr.gelman_rubin() + assert result is None + + +# =================================================================== +# arviz data conversion +# =================================================================== + + +class TestToArvizData: + def test_2d_draws_become_single_chain(self, sample_draws): + pytest.importorskip('arviz') + from easyreflectometry.analysis.bayesian import _to_arviz_data + + draws, param_names = sample_draws + idata = _to_arviz_data(draws, param_names) + posterior = idata.posterior + assert posterior.sizes['chain'] == 1 + assert posterior.sizes['draw'] == draws.shape[0] + for name in param_names: + assert name in posterior + + +# =================================================================== +# Plot construction (plotly available) +# =================================================================== + + +class TestPlotTraceFigure: + def test_returns_figure_for_2d_draws(self, sample_draws): + Figure = pytest.importorskip('plotly.graph_objects').Figure + from easyreflectometry.analysis.bayesian import plot_trace + + draws, param_names = sample_draws + fig = plot_trace(draws, param_names, return_figure=True) + assert isinstance(fig, Figure) + # One line trace and one histogram per parameter for the single chain. + assert len(fig.data) == 2 * len(param_names) + + def test_returns_figure_for_multi_chain_draws(self, sample_draws): + Figure = pytest.importorskip('plotly.graph_objects').Figure + from easyreflectometry.analysis.bayesian import plot_trace + + draws, param_names = sample_draws + multi = np.stack([draws, draws + 1.0], axis=0) # (2, n_draws, n_params) + fig = plot_trace(multi, param_names, return_figure=True) + assert isinstance(fig, Figure) + assert len(fig.data) == 2 * 2 * len(param_names) + + def test_inline_path_delegates_to_arviz(self, sample_draws, monkeypatch): + pytest.importorskip('arviz') + from unittest.mock import MagicMock + + from easyreflectometry.analysis import bayesian as bayesian_mod + + draws, param_names = sample_draws + mock_plot = MagicMock() + monkeypatch.setattr(bayesian_mod._arviz, 'plot_trace', mock_plot) + result = bayesian_mod.plot_trace(draws, param_names) + assert result is None + mock_plot.assert_called_once() + + +class TestPlotDistributionFigure: + def test_returns_none_without_return_figure(self, sample_draws): + from easyreflectometry.analysis.bayesian import plot_distribution + + draws, param_names = sample_draws + assert plot_distribution(draws, param_names) is None + + def test_returns_figure_with_expected_overlays(self, sample_draws): + Figure = pytest.importorskip('plotly.graph_objects').Figure + from easyreflectometry.analysis.bayesian import plot_distribution + + draws, param_names = sample_draws + logp = np.arange(draws.shape[0], dtype=float) + fig = plot_distribution(draws, param_names, logp=logp, return_figure=True) + assert isinstance(fig, Figure) + trace_names = {trace.name for trace in fig.data} + assert 'Posterior histogram' in trace_names + assert '95% credible interval' in trace_names + assert 'Median' in trace_names + # logp was supplied, so the best posterior sample line must be drawn. + assert 'Best posterior sample' in trace_names + + def test_accepts_3d_draws(self, sample_draws): + Figure = pytest.importorskip('plotly.graph_objects').Figure + from easyreflectometry.analysis.bayesian import plot_distribution + + draws, param_names = sample_draws + multi = np.stack([draws, draws], axis=0) # (2, n_draws, n_params) + fig = plot_distribution(multi, param_names, return_figure=True) + assert isinstance(fig, Figure) + + +class TestPosteriorResultsPlotDelegates: + def test_corner_returns_figure(self, sample_draws): + Figure = pytest.importorskip('plotly.graph_objects').Figure + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + fig = PosteriorResults(draws, param_names).corner() + assert isinstance(fig, Figure) + + def test_distribution_returns_figure(self, sample_draws): + Figure = pytest.importorskip('plotly.graph_objects').Figure + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + fig = PosteriorResults(draws, param_names).distribution() + assert isinstance(fig, Figure) + + def test_trace_delegates_to_plot_trace(self, sample_draws, monkeypatch): + from unittest.mock import MagicMock + + from easyreflectometry.analysis import bayesian as bayesian_mod + from easyreflectometry.analysis.bayesian import PosteriorResults + + draws, param_names = sample_draws + mock_plot = MagicMock() + monkeypatch.setattr(bayesian_mod, 'plot_trace', mock_plot) + PosteriorResults(draws, param_names).trace() + mock_plot.assert_called_once() + + +class TestPlotCornerEdgeCases: + def test_accepts_3d_draws(self, sample_draws): + Figure = pytest.importorskip('plotly.graph_objects').Figure + from easyreflectometry.analysis.bayesian import plot_corner + + draws, param_names = sample_draws + multi = np.stack([draws, draws], axis=0) + fig = plot_corner(multi, param_names) + assert isinstance(fig, Figure) + + def test_thins_scatter_for_large_posteriors(self): + go = pytest.importorskip('plotly.graph_objects') + from easyreflectometry.analysis.bayesian import _POSTERIOR_PAIR_SCATTER_MAX_POINTS + from easyreflectometry.analysis.bayesian import plot_corner + + rng = np.random.default_rng(3) + n_samples = _POSTERIOR_PAIR_SCATTER_MAX_POINTS * 2 + draws = rng.normal(size=(n_samples, 2)) + fig = plot_corner(draws, ['a', 'b']) + scatters = [t for t in fig.data if isinstance(t, go.Scatter) and t.name == 'Posterior samples'] + assert scatters + assert all(len(t.x) <= _POSTERIOR_PAIR_SCATTER_MAX_POINTS for t in scatters) + + def test_single_sample_falls_back_to_histogram(self): + go = pytest.importorskip('plotly.graph_objects') + from easyreflectometry.analysis.bayesian import plot_corner + + # A single draw defeats the KDE, so the diagonal must fall back to a + # histogram and the pair panels must omit contours. + draws = np.array([[250.0, 2.0]]) + fig = plot_corner(draws, ['thickness', 'sld']) + assert any(isinstance(t, go.Histogram) for t in fig.data) + assert not any(isinstance(t, go.Contour) for t in fig.data) + + +# =================================================================== +# Density-estimation helpers +# =================================================================== + + +class TestPosteriorAxisBounds: + def test_empty_returns_none(self): + from easyreflectometry.analysis.bayesian import _posterior_axis_bounds + + assert _posterior_axis_bounds(np.array([])) is None + assert _posterior_axis_bounds(np.array([np.nan, np.inf])) is None + + def test_constant_values_get_padding(self): + from easyreflectometry.analysis.bayesian import _posterior_axis_bounds + + lo, hi = _posterior_axis_bounds(np.array([5.0, 5.0, 5.0])) + assert lo < 5.0 < hi + + def test_constant_zero_gets_padding(self): + from easyreflectometry.analysis.bayesian import _posterior_axis_bounds + + lo, hi = _posterior_axis_bounds(np.zeros(3)) + assert lo < 0.0 < hi + + +class TestPosteriorDensityCurve: + def test_too_few_samples_returns_none(self): + from easyreflectometry.analysis.bayesian import _posterior_density_curve + + assert _posterior_density_curve(np.array([1.0])) is None + + def test_constant_samples_yield_gaussian_bump(self): + pytest.importorskip('scipy') + from easyreflectometry.analysis.bayesian import _posterior_density_curve + + result = _posterior_density_curve(np.full(50, 3.0)) + assert result is not None + grid, density = result + # Density peaks at the constant value and integrates to ~1. + assert grid[np.argmax(density)] == pytest.approx(3.0, abs=(grid[1] - grid[0])) + assert np.trapezoid(density, grid) == pytest.approx(1.0, rel=1e-6) + + def test_returns_none_without_scipy(self, sample_draws, monkeypatch): + import builtins + + from easyreflectometry.analysis.bayesian import _posterior_density_curve + + real_import = builtins.__import__ + + def _fake_import(name, *args, **kwargs): + if name.startswith('scipy'): + raise ImportError('scipy disabled for test') + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, '__import__', _fake_import) + draws, _ = sample_draws + assert _posterior_density_curve(draws[:, 0]) is None + + +class TestPosteriorDensitySurface: + def test_degenerate_samples_return_none(self): + pytest.importorskip('scipy') + from easyreflectometry.analysis.bayesian import _posterior_density_surface + + constant = np.full(50, 1.0) + # Both axes constant. + assert _posterior_density_surface(constant, constant) is None + # One axis constant: rank-deficient covariance. + rng = np.random.default_rng(11) + assert _posterior_density_surface(constant, rng.normal(size=50)) is None + + def test_too_few_finite_samples_return_none(self): + pytest.importorskip('scipy') + from easyreflectometry.analysis.bayesian import _posterior_density_surface + + x = np.array([1.0, np.nan, np.nan]) + y = np.array([2.0, np.nan, np.nan]) + assert _posterior_density_surface(x, y) is None + + def test_valid_samples_return_grids(self, sample_draws): + pytest.importorskip('scipy') + from easyreflectometry.analysis.bayesian import _posterior_density_surface + + draws, _ = sample_draws + result = _posterior_density_surface(draws[:, 0], draws[:, 1]) + assert result is not None + x_grid, y_grid, density = result + assert density.shape == (len(y_grid), len(x_grid)) + + def test_returns_none_without_scipy(self, sample_draws, monkeypatch): + import builtins + + from easyreflectometry.analysis.bayesian import _posterior_density_surface + + real_import = builtins.__import__ + + def _fake_import(name, *args, **kwargs): + if name.startswith('scipy'): + raise ImportError('scipy disabled for test') + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, '__import__', _fake_import) + draws, _ = sample_draws + assert _posterior_density_surface(draws[:, 0], draws[:, 1]) is None + + +class TestPosteriorContourColorscales: + def test_negative_correlation_selects_red_palette(self): + from easyreflectometry.analysis.bayesian import _POSTERIOR_NEGATIVE_CONTOUR_FILL_COLORSCALE + from easyreflectometry.analysis.bayesian import _posterior_contour_colorscales + + x = np.linspace(0, 1, 50) + fill, _ = _posterior_contour_colorscales(x, -x) + assert fill is _POSTERIOR_NEGATIVE_CONTOUR_FILL_COLORSCALE + + def test_positive_correlation_selects_blue_palette(self): + from easyreflectometry.analysis.bayesian import _POSTERIOR_CONTOUR_FILL_COLORSCALE + from easyreflectometry.analysis.bayesian import _posterior_contour_colorscales + + x = np.linspace(0, 1, 50) + fill, _ = _posterior_contour_colorscales(x, x) + assert fill is _POSTERIOR_CONTOUR_FILL_COLORSCALE + + +class TestPosteriorMarginalYRange: + def test_covers_histogram_and_kde_peaks(self, sample_draws): + from easyreflectometry.analysis.bayesian import _posterior_density_curve + from easyreflectometry.analysis.bayesian import _posterior_marginal_y_range + + draws, _ = sample_draws + values = draws[:, 0] + curve = _posterior_density_curve(values) + y_range = _posterior_marginal_y_range(values, curve) + assert y_range is not None + lo, hi = y_range + assert lo == 0.0 + hist, _ = np.histogram(values, bins=40, density=True) + assert hi >= np.max(hist) + + def test_no_data_returns_none(self): + from easyreflectometry.analysis.bayesian import _posterior_marginal_y_range + + assert _posterior_marginal_y_range(np.array([]), None) is None + + +# =================================================================== +# Metadata helpers +# =================================================================== + + +class TestMetadataHelpers: + def test_version_returns_string(self): + from easyreflectometry.analysis.bayesian import _easyreflectometry_version + + assert isinstance(_easyreflectometry_version(), str) + + def test_data_fingerprint_is_deterministic(self): + from easyreflectometry.analysis.bayesian import _data_fingerprint + + x = [np.array([1.0, 2.0])] + y = [np.array([3.0, 4.0])] + w = [np.array([0.1, 0.2])] + first = _data_fingerprint(x, y, w) + assert isinstance(first, str) + assert len(first) == 64 + assert _data_fingerprint(x, y, w) == first + assert _data_fingerprint(x, y, [np.array([0.1, 0.3])]) != first + + def test_data_fingerprint_returns_none_on_bad_input(self): + from easyreflectometry.analysis.bayesian import _data_fingerprint + + assert _data_fingerprint([object()], [], []) is None From 299127dfa5aa08c42b382ab8aef5673f26c858ab Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 31 Jul 2026 14:30:11 +0200 Subject: [PATCH 22/38] Codecov fixes 2 (#392) * try to fix failing test * another attempt --- pixi.lock | 162 +++++++++++---------- src/easyreflectometry/analysis/bayesian.py | 9 +- tests/test_bayesian.py | 10 +- 3 files changed, 103 insertions(+), 78 deletions(-) diff --git a/pixi.lock b/pixi.lock index 36a67a04..16dba96f 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,8 +1,21 @@ version: 7 platforms: - name: linux-64 -- name: osx-arm64 + virtual-packages: + - __unix=0=0 + - __linux=4.18 + - __glibc=2.28 + - __archspec=0=x86_64 +- name: p1 + subdir: osx-arm64 + virtual-packages: + - __osx=14.0 + - __unix=0=0 + - __archspec=0=m1 - name: win-64 + virtual-packages: + - __win=10.0 + - __archspec=0=x86_64 environments: default: channels: @@ -189,8 +202,7 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -310,6 +322,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -326,7 +339,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - osx-arm64: + p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -500,8 +513,7 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -625,6 +637,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -801,8 +814,7 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -928,6 +940,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -1124,8 +1137,7 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1246,6 +1258,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl @@ -1259,7 +1272,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - osx-arm64: + p1: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-0.1.4-pyhd8ed1ab_1.conda @@ -1429,8 +1442,7 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1553,6 +1565,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -1724,8 +1737,7 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -1849,6 +1861,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl @@ -2047,8 +2060,7 @@ environments: - 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/noarch/zipp-3.23.1-pyhcf101f3_0.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2168,6 +2180,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -2184,7 +2197,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - osx-arm64: + p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -2358,8 +2371,7 @@ environments: - 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/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2483,6 +2495,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -2659,8 +2672,7 @@ environments: - 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/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: . - - pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca + - pypi: ./ - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2786,6 +2798,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -3070,7 +3083,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f7/00/bbca25f8a2372465cdf93138c1e1e38dff045fb0afef1488f395d0afcb3b/ncrystal-4.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - osx-arm64: + p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -8297,11 +8310,11 @@ packages: purls: [] size: 388453 timestamp: 1764777142545 -- pypi: . +- pypi: ./ name: easyreflectometry requires_dist: - bumps - - easyscience @ git+https://github.com/easyscience/corelib.git@bayesian_extend + - easyscience - orsopy - plotly - pooch @@ -8345,55 +8358,6 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.11' -- pypi: git+https://github.com/easyscience/corelib.git?rev=bayesian_extend#8c83ab3cb6f7e598f16f31c70cc3935f326bf2ca - name: easyscience - version: 2.4.0+dev17 - requires_dist: - - asteval - - bumps - - dfo-ls - - lmfit - - numpy - - scipp - - build ; extra == 'dev' - - copier ; extra == 'dev' - - docstripy ; extra == 'dev' - - format-docstring ; extra == 'dev' - - gitpython ; extra == 'dev' - - interrogate ; extra == 'dev' - - ipykernel ; extra == 'dev' - - ipympl ; extra == 'dev' - - ipython ; extra == 'dev' - - ipywidgets ; extra == 'dev' - - jinja2 ; extra == 'dev' - - jupyterlab ; extra == 'dev' - - jupyterquiz ; extra == 'dev' - - jupytext ; extra == 'dev' - - matplotlib ; 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' - - pooch ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pydoclint ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - pytest-xdist ; extra == 'dev' - - pyyaml ; extra == 'dev' - - radon ; extra == 'dev' - - ruff ; extra == 'dev' - - spdx-headers ; extra == 'dev' - - validate-pyproject[all] ; extra == 'dev' - - versioningit ; extra == 'dev' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl name: backrefs version: '7.0' @@ -13173,6 +13137,56 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl + name: easyscience + version: 2.5.1 + sha256: e44c3efb10f8e040ba88523c6ba58e131e3174721f24801d036fdc5a24e622f2 + requires_dist: + - asteval + - bumps>=1.0.4 + - dfo-ls + - lmfit + - numpy + - scipp + - build ; extra == 'dev' + - copier ; extra == 'dev' + - docstripy ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - ipykernel ; extra == 'dev' + - ipympl ; extra == 'dev' + - ipython ; extra == 'dev' + - ipywidgets ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterlab ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - matplotlib ; 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' + - pooch ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' + - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl name: cycler version: 0.12.1 diff --git a/src/easyreflectometry/analysis/bayesian.py b/src/easyreflectometry/analysis/bayesian.py index 17ab252c..b5bdfb43 100644 --- a/src/easyreflectometry/analysis/bayesian.py +++ b/src/easyreflectometry/analysis/bayesian.py @@ -84,7 +84,14 @@ def _to_arviz_data(draws: np.ndarray, param_names: list[str]): for i, name in enumerate(param_names): posterior_dict[name] = draws[:, :, i] - return _arviz.from_dict({'posterior': posterior_dict}) + # arviz < 1.0 takes the posterior variables as a keyword argument; arviz + # >= 1.0 removed it in favour of a single {group: {var: array}} mapping. + # The 1.x call cannot go first: 0.x accepts the mapping without error but + # misreads it as one variable named 'posterior'. + try: + return _arviz.from_dict(posterior=posterior_dict) + except TypeError: + return _arviz.from_dict({'posterior': posterior_dict}) class PosteriorResults: diff --git a/tests/test_bayesian.py b/tests/test_bayesian.py index 1cb78d4f..6d0c3148 100644 --- a/tests/test_bayesian.py +++ b/tests/test_bayesian.py @@ -565,10 +565,14 @@ def test_2d_draws_become_single_chain(self, sample_draws): draws, param_names = sample_draws idata = _to_arviz_data(draws, param_names) posterior = idata.posterior + # Dimension naming varies across arviz versions, so assert on the + # contract itself: every sample survives conversion with its values + # intact, in a single chain. assert posterior.sizes['chain'] == 1 - assert posterior.sizes['draw'] == draws.shape[0] - for name in param_names: - assert name in posterior + for i, name in enumerate(param_names): + values = np.asarray(posterior[name].values).reshape(-1) + assert values.size == draws.shape[0] + assert np.allclose(values, draws[:, i]) # =================================================================== From f96fefd9124aa8d11bd633f4bdb79a5879c59aa1 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Thu, 27 Aug 2026 08:52:52 +0200 Subject: [PATCH 23/38] Polarized channels (#393) * initial version * added magnetic SLD profile * added magnetic parameters * code review comments addressed * code review fixes for Phase 2, added notebook * PR code review comments * fixed polarized file load issue * enable magnetic layers * new LayerMagnetism component * ruff * ruff on notebooks * bind calculator to model for performance * attempt at fixing package testing * package tests only on master * don't run ruff twice * code review fixes * added polarized fitting example/notebook * fixed default sample generation * improved wording in the magnetic fitting notebook * move the most expensive test to integration --- .github/workflows/pypi-test.yml | 10 +- .github/workflows/python-ci.yml | 11 - .github/workflows/test.yml | 5 +- CHANGELOG.md | 173 +++ docs/docs/api-reference/calculators.md | 29 + .../advancedfitting/bayesian_bumps.ipynb | 8 +- .../advancedfitting/polarized_fitting.ipynb | 526 +++++++ .../docs/tutorials/simulation/magnetism.ipynb | 417 ++++-- docs/mkdocs.yml | 1 + notebooks/polarized_demo_data/fe_on_si_dd.dat | 161 +++ notebooks/polarized_demo_data/fe_on_si_du.dat | 161 +++ notebooks/polarized_demo_data/fe_on_si_ud.dat | 161 +++ notebooks/polarized_demo_data/fe_on_si_uu.dat | 161 +++ notebooks/polarized_fitting.ipynb | 507 +++++++ pixi.toml | 9 +- src/easyreflectometry/calculators/__init__.py | 3 +- .../calculators/calculator_base.py | 95 ++ src/easyreflectometry/calculators/factory.py | 39 + .../calculators/polarization.py | 33 + .../calculators/refl1d/calculator.py | 2 + .../calculators/refl1d/wrapper.py | 379 +++++- .../calculators/refnx/wrapper.py | 16 - .../calculators/wrapper_base.py | 122 ++ src/easyreflectometry/data/__init__.py | 4 + src/easyreflectometry/data/polarized.py | 284 ++++ src/easyreflectometry/fitting.py | 408 ++++-- src/easyreflectometry/limits.py | 5 +- src/easyreflectometry/model/model.py | 5 + src/easyreflectometry/project.py | 767 ++++++++++- src/easyreflectometry/sample/__init__.py | 2 + .../sample/elements/layers/layer.py | 58 +- .../sample/elements/layers/layer_magnetism.py | 125 ++ src/easyreflectometry/summary/summary.py | 148 +- .../refl1d/test_refl1d_calculator.py | 37 + .../calculators/refl1d/test_refl1d_wrapper.py | 275 +++- tests/calculators/refnx/test_refnx_wrapper.py | 13 +- .../test_polarization_interface.py | 123 ++ tests/data/test_polarized.py | 191 +++ tests/integration/test_ort_file_fitting.py | 128 ++ .../elements/layers/test_layer_magnetism.py | 226 +++ tests/summary/test_summary.py | 123 ++ tests/test_limits.py | 51 + tests/test_ort_file.py | 97 -- tests/test_polarized_fitting.py | 1210 +++++++++++++++++ 44 files changed, 6826 insertions(+), 483 deletions(-) create mode 100644 docs/docs/api-reference/calculators.md create mode 100644 docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb create mode 100644 notebooks/polarized_demo_data/fe_on_si_dd.dat create mode 100644 notebooks/polarized_demo_data/fe_on_si_du.dat create mode 100644 notebooks/polarized_demo_data/fe_on_si_ud.dat create mode 100644 notebooks/polarized_demo_data/fe_on_si_uu.dat create mode 100644 notebooks/polarized_fitting.ipynb create mode 100644 src/easyreflectometry/calculators/polarization.py create mode 100644 src/easyreflectometry/data/polarized.py create mode 100644 src/easyreflectometry/sample/elements/layers/layer_magnetism.py create mode 100644 tests/calculators/test_polarization_interface.py create mode 100644 tests/data/test_polarized.py create mode 100644 tests/integration/test_ort_file_fitting.py create mode 100644 tests/sample/elements/layers/test_layer_magnetism.py create mode 100644 tests/test_polarized_fitting.py diff --git a/.github/workflows/pypi-test.yml b/.github/workflows/pypi-test.yml index 8bcb4b5e..f0c8f88a 100644 --- a/.github/workflows/pypi-test.yml +++ b/.github/workflows/pypi-test.yml @@ -24,6 +24,8 @@ env: jobs: # Job 1: Test installation from PyPI on multiple OS pypi-package-tests: + # Only run the package tests on the master branch + if: github.ref == 'refs/heads/master' strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] @@ -45,18 +47,10 @@ jobs: - name: Init pixi project run: pixi init easyreflectometry - - name: Set the minimum system requirements - working-directory: easyreflectometry - run: pixi project system-requirements add macos 14.0 - - name: Add Python 3.13 from Conda working-directory: easyreflectometry run: pixi add "python=3.13" - - name: Add other Conda dependencies - working-directory: easyreflectometry - run: pixi add gsl - - name: Add easyreflectometry (with dev dependencies) from PyPI working-directory: easyreflectometry run: pixi add --pypi "easyreflectometry[dev]" diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index ea40ee70..f8bfa049 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -13,17 +13,6 @@ name: CI using pip on: [push, pull_request] jobs: - Code_Consistency: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: chartboost/ruff-action@v1 - - name: Suggestion to fix issues - if: ${{ failure() }} - run: | - echo "::notice::In project root run 'python.exe -m ruff . --fix' and commit changes to fix issues." - exit 1 - Code_Testing: strategy: max-parallel: 4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 530c154f..51de8d5c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -174,6 +174,8 @@ jobs: # Job 3: Test the package package-test: needs: source-test # depend on previous job + # Only run the package tests on the master branch + if: github.ref == 'refs/heads/master' strategy: fail-fast: false @@ -217,9 +219,6 @@ jobs: echo "Adding Python $py_ver" pixi add "python=$py_ver" - echo "Setting macOS 14.0 as minimum required" - pixi project system-requirements add macos 14.0 - echo "Looking for wheel in ../dist/py$py_ver/" ls -l "../dist/py$py_ver/" diff --git a/CHANGELOG.md b/CHANGELOG.md index 938f27b5..1d9e5c6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,176 @@ +# Unreleased + +All four polarization channels (pp, pm, mp, mm) are now available from +the refl1d calculator. Previously only the non-spin-flip pp channel was +returned. + +- New `LayerMagnetism` sample element. `Layer` takes an optional + `magnetism` with fittable, serialized `Parameter`s `rho_m` (magnetic + SLD) and `theta_m` (in-plane moment angle). Adding a magnetic layer + turns on `include_magnetism` on the calculator, or raises + `NotImplementedError` if the backend cannot do magnetism. Removing the + last magnetic layer turns it off again. `Model.has_magnetism`, + `CalculatorBase.supports_magnetism` and + `Project.calculator_supports_magnetism` report the current state. +- New `PolarizedDataSet` groups per-channel `DataSet1D` objects (one + file per channel; NSF experiments use 'pp'/'mm' only, spin-flip + channels are optional) into one experiment that shares a single model. + `Project.load_polarized_experiment(paths)` loads from an explicit + channel-to-file mapping. + `Project.suggest_polarized_channel_assignment(paths)` fills that + mapping from the ORSO header polarization (`pp`/`mm`/`pm`/`mp` only). + Partially analysed observables such as `po`/`mo` (channel sums) and + `op`/`om`/`unpolarized` are left for the user. For plain text files + the mapping comes from filename tokens (`_uu`/`_up`/`_pp` → pp, + `_dd`/`_down`/`_mm` → mm, `_ud`/`_pm` → pm, `_du`/`_mp` → mp). +- Experiment and model accessors are channel-aware. + `Project.experimental_data_for_model_at_index(index, channel=...)` + returns the `DataSet1D` of one spin channel. `channel=None` (the + default) still returns the stored experiment. + `Project.model_data_for_model_at_index(index, q_range, channel=...)` + calculates one spin cross-section. + `Project.experiment_is_polarized_at_index(index)` and + `Project.experiment_channels_at_index(index)` report the polarization + state. A channel that was not measured raises `KeyError`. An unknown + channel, or any channel on an unpolarized experiment, raises + `ValueError`. +- Summary/report figures now plot one measured series per spin channel + of a polarized experiment, each in its channel colour, plus the + matching calculated cross-section. Channels that cannot be calculated + (for example spin-flip on a non-magnetic model) are shown without a + calculated overlay. Previously a polarized experiment made the report + figures fail on `PolarizedDataSet.x`. +- The summary experiments table lists one row per spin channel of a + polarized experiment, named ` ()`. It previously + raised + `AttributeError: 'PolarizedDataSet' object has no attribute 'x'` and + crashed anything that read the summary while a polarized experiment + was loaded. +- New `Project.calculators_supporting_magnetism` lists the available + calculators that can model magnetic samples, without switching the + active one. `Project.models_have_magnetism` reports whether any model + has a magnetic layer. Use these to pick a suitable engine, or to + refuse one that cannot carry the sample's magnetism, instead of + hitting an error inside the binding. +- New `Project.magnetic_sld_data_for_model_at_index(index)` returns the + depth profiles of a magnetic model as `DataSet1D`s keyed `'sld'`, + `'rho_m'`, `'theta_m'`, `'spin_up'` and `'spin_down'`. The last two + are the potentials each spin state sees, rho +/- rho_m\*cos(theta_m - + A). The guide-field angle A is the new module constant + `GUIDE_FIELD_ANGLE` (270 degrees, refl1d's default and the only value + the library can currently model). A non-magnetic model raises + `ValueError`. `Project.model_has_magnetism_at_index(index)` reports + whether the model is magnetic. +- The magnetic depth profile is now built by smoothing the two in-plane + components of the moment and converting back, rather than smoothing + magnitude and angle separately as refl1d does channel by channel. At + an interface where moments differ by a few degrees across 0/360, the + smoothed _angle_ used to take the long way around the circle, pass + through the guide-field direction, and report the full moment as + longitudinal. That produced a spurious spin-up/spin-down splitting + exactly at the interface (a 2-degree difference gave the full 2*rho_m + splitting; it is now the correct ~0.02*rho_m). Collinear samples are + unaffected. The reported `theta_m` profile is restricted to depths + that carry a moment (the angle of a zero-length vector is arbitrary) + and is made continuous within each magnetic region. A profile turning + from 359 to 1 degree is a 2 degree turn; the wrapped values would plot + as a full sweep. If the installed refl1d does not expose the microslab + data the component-safe profile needs, the calculator now raises + `NotImplementedError` instead of falling back to the angle-smoothed + profile. +- New `Project.spin_asymmetry_for_experiment_at_index(index)` returns + the measured spin asymmetry (R++ - R--)/(R++ + R--) of a polarized + experiment, the matching model curve when the model is magnetic, and + the number of points dropped. `ye` holds the SA **variance**, as + everywhere else in the library. Channels measured on different q grids + are interpolated onto the pp grid (values with the linear weights, + variances with their squares) only inside the q range both channels + cover. Outside that range `np.interp` would clamp to the edge value. + Dropped points are reported as `out_of_overlap_points`. Points where + R++ + R-- is not above `SPIN_ASYMMETRY_SIGNIFICANCE` (3) times its own + uncertainty are also dropped. A second, uncertainty-independent guard + drops points whose denominator is non-positive or smaller than + `SPIN_ASYMMETRY_CANCELLATION_FRACTION` (1e-3) of |R++| + |R--|. + Without it, a file with no uncertainties (two columns, or a malformed + uncertainty array) had no guard, and background-subtracted data could + put values of +/-1e3 on the axis. Points with a non-finite + reflectivity or a negative/non-finite variance are dropped rather than + treated as having no uncertainty. Dropped points are reported by + reason (`low_significance_points`, `small_denominator_points`, + `invalid_points`). +- Both channels of a spin asymmetry are validated before use. Empty, + length-mismatched, non-finite or duplicated q grids are rejected, and + `experiment_supports_spin_asymmetry_at_index` reports False for them. + A descending grid is sorted before pairing; `np.interp` silently + returns nonsense for one. + `Project.experiment_supports_spin_asymmetry_at_index(index)` reports + whether both non-spin-flip channels were measured. +- New `calculate_channel(q, model, channel)` on the wrapper (and + `reflectivity_profile_channel` on the calculator, + `fit_func_for_channel` on `CalculatorFactory`) evaluates one explicit + spin channel without touching the global `polarization_channel` state. +- New `MultiFitter.for_experiments(experiments)` builds a fitter with + one fit function per dataset (one per measured spin channel for a + polarized experiment, one for an ordinary one) across any number of + experiments and models, and returns without running the fit. + `fit_datasets` and `fit_channels` give the flat dataset list in + fit-function order, so an application can prepare the data arrays and + drive `easy_science_multi_fitter.fit(...)` from a worker thread. +- New `MultiFitter.record_fit_results(results)` adopts results from such + a caller-driven fit, so `chi2` and `reduced_chi` describe it instead + of reporting that no fit was performed. The classical metrics need the + original data arrays and stay None. +- `rho_m` now takes part in the project's default-limit policy. It is + created with `default_limits_pending`, and + `Project._sync_parameter_states` gives it the shared SLD window (-1 + to 10) unless an explicit `Parameter` with its own bounds was passed. + `theta_m` keeps its explicit 0-360 bounds. Previously both stayed + unbounded. +- New `MultiFitter.fit_polarized(data)` fits all measured channels of a + `PolarizedDataSet` simultaneously against the shared model: one fit + function per channel, common structural parameters, magnetic + parameters constrained by all channels at once. Returns per-channel + `FitResults`. +- The refl1d wrapper now caches the four polarized cross-sections per + model state and (q, dq) grid. They come from a single kernel + evaluation, so a simultaneous N-channel fit costs about one evaluation + per iteration instead of N. + +- New `polarized_reflectivity_profiles(x_array, model_id)` on the + calculator (and on `CalculatorFactory`) returns the reflectivity of + all four spin channels in one calculation as a dictionary keyed + `'pp'`, `'pm'`, `'mp'`, `'mm'` (in that order). Requires + `include_magnetism = True`. +- New `polarization_channel` property (accepts + `'pp'`/`'pm'`/`'mp'`/`'mm'` or the new `PolarizationChannel` enum) + selects which channel `reflectity_profile` (and therefore fitting) + returns, so fits can target spin-flip or mm data. Default `'pp'`; + disabling magnetism resets it to `'pp'`. The channel belongs to the + currently active calculator instance, not to a model or dataset. It + affects every subsequent calculation with that calculator. + `interface.switch(...)` constructs a fresh calculator and resets both + this and `include_magnetism`. +- New `magnetic_sld_profile(model_id)` on the calculator (and on + `CalculatorFactory`) returns the nuclear and magnetic scattering + length density profiles as a tuple `z`, `sld(z)`, `rhoM(z)` (magnetic + SLD) and `thetaM(z)` (magnetic angle). Requires + `include_magnetism = True`; refl1d only. +- Magnetic calculations now always build all four refl1d cross-sections, + so they may take somewhat longer than before. pp results are + unchanged. +- Bug fix: `include_magnetism = True` on a refnx-backed calculator now + raises `NotImplementedError`. Previously it was silently accepted (the + guard sat on a property the calculator never called) even though refnx + magnetism is not supported. +- Bug fix (pre-existing): disabling magnetism after layers were created + with it enabled used to leave refl1d `Magnetism` objects on the slabs, + and a later unpolarized calculation raised `AttributeError` inside + refl1d. Disabling magnetism now strips the magnetic state from + existing layers. Magnetic parameters (`rhoM`/`thetaM`) are kept in a + per-layer store inside the wrapper, so they survive a + disable/re-enable cycle and are re-attached when magnetism is enabled + again. `update_layer` also accepts the magnetism keys one at a time. + # Version 1.7.0 (1 Aug 2026) Restored the measured per-point resolution on data load (issue #368). diff --git a/docs/docs/api-reference/calculators.md b/docs/docs/api-reference/calculators.md new file mode 100644 index 00000000..7564c19a --- /dev/null +++ b/docs/docs/api-reference/calculators.md @@ -0,0 +1,29 @@ +# Calculators + +The calculator translates an EasyReflectometry model into a backend +engine (refl1d or refnx) and computes reflectivity. + +## Polarized reflectivity + +With the refl1d calculator and `include_magnetism` enabled, all four +spin channels are available: + +- `polarized_reflectivity_profiles(x_array, model_id)` returns the + reflectivity of all four channels as a dictionary keyed `'pp'`, + `'pm'`, `'mp'`, `'mm'` (in that order). +- `polarization_channel` selects which channel `reflectity_profile` — + and hence fitting — uses (default `'pp'`). +- `magnetic_sld_profile(model_id)` returns the nuclear and magnetic + scattering length density profiles as a tuple `z`, `sld(z)`, `rhoM(z)` + (magnetic SLD) and `thetaM(z)` (magnetic angle). + +Note that `polarization_channel` is state of the currently active +calculator instance, not of a model or dataset: it affects every +subsequent calculation using that calculator, and switching calculators +via the factory constructs a fresh instance, which resets the channel +(along with `include_magnetism`). Calculators without magnetism support +(refnx) raise `NotImplementedError` when magnetism is enabled. + +::: easyreflectometry.calculators.polarization + +::: easyreflectometry.calculators.calculator_base diff --git a/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb b/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb index dcaaa025..bf136533 100644 --- a/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb +++ b/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb @@ -217,12 +217,18 @@ "# We deliberately start with a *very* short run. It finishes in seconds but is\n", "# far too short to trust — which is exactly the situation the \"extend the chain\"\n", "# section below exists to fix. In production you would ask for 20 k+ samples.\n", + "#\n", + "# ``thin=1`` here (rather than the ``10`` used later in ``extend()``) works\n", + "# around a BUMPS DREAM bug: its outlier-chain replacement indexes the thinned\n", + "# sample buffer using the un-thinned generation counter, which overruns the\n", + "# buffer whenever outlier removal fires before enough thinned generations\n", + "# have accumulated — reliably the case for a run this short with thin > 1.\n", "\n", "posterior_dict = fitter.mcmc_sample(\n", " data,\n", " samples=500, # Deliberately too short — extended later in this notebook\n", " burn=100,\n", - " thin=10,\n", + " thin=1,\n", ")\n", "\n", "print('DREAM sampling complete.')\n", diff --git a/docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb b/docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb new file mode 100644 index 00000000..cb820cbf --- /dev/null +++ b/docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb @@ -0,0 +1,526 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Polarized Neutron Reflectometry: Channels, Depth Profiles & Simultaneous Fitting\n", + "\n", + "`magnetism.ipynb` (in the *Simulation* section) introduces magnetic layers and\n", + "how to select a single polarization channel. This tutorial picks up from\n", + "there and focuses on what is new for **polarization analysis**:\n", + "\n", + "1. Computing all four spin cross-sections (`pp`, `pm`, `mp`, `mm`) in\n", + " a single call.\n", + "2. Reading the spin-resolved depth profile. The potential each neutron\n", + " spin state sees.\n", + "3. Loading a polarized experiment from per-channel data files and forming the\n", + " **spin asymmetry**, with correct error propagation.\n", + "4. **Fitting multiple polarization channels simultaneously** against one\n", + " shared model with `MultiFitter.fit_polarized()`.\n", + " First, recovering the moment's magnitude from the two\n", + " non-spin-flip channels, then recovering the full magnetization vector\n", + " (magnitude *and* direction) from all four channels.\n", + "\n", + "Only the `refl1d` calculator supports magnetism.\n", + "All magnetism handling: enabling it on the calculator, computing channels,\n", + "fitting, requires `refl1d`.\n", + "\n", + "The reflectometry convention used throughout: with the default guide field,\n", + "a moment at ``theta_m = 270`` degrees is aligned with it (no spin-flip\n", + "scattering); ``theta_m = 90`` is anti-aligned. A **canted** moment away from\n", + "270/90 produces spin-flip scattering (`pm`, `mp`) alongside the\n", + "non-spin-flip channels (`pp`, `mm`). This is why the sample below uses\n", + "``theta_m = 45`` degrees rather than a value aligned with the guide field:\n", + "it is the only choice that makes all four channels visually distinct." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "import tempfile\n", + "from pathlib import Path\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from easyreflectometry.calculators import CalculatorFactory\n", + "from easyreflectometry.data import DataSet1D\n", + "from easyreflectometry.data import PolarizedDataSet\n", + "from easyreflectometry.fitting import MultiFitter\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import ModelCollection\n", + "from easyreflectometry.model import PercentageFwhm\n", + "from easyreflectometry.project import Project\n", + "from easyreflectometry.sample import Layer\n", + "from easyreflectometry.sample import LayerMagnetism\n", + "from easyreflectometry.sample import Material\n", + "from easyreflectometry.sample import Multilayer\n", + "from easyreflectometry.sample import Sample\n", + "\n", + "print('All libraries imported successfully.')" + ] + }, + { + "cell_type": "markdown", + "id": "2", + "metadata": { + "lines_to_next_cell": 2 + }, + "source": [ + "## 1. Build a magnetic sample\n", + "\n", + "A single magnetic layer between two non-magnetic media: a thin Fe film\n", + "(nuclear SLD 4, in units of $10^{-6}$ Å$^{-2}$) with an in-plane magnetic\n", + "moment, on a Si substrate below a vacuum superphase.\n", + "\n", + "Magnetism is defined with `Layer.magnetism = LayerMagnetism(rho_m=..., theta_m=...)`\n", + "(or passed directly to `Layer(..., magnetism=...)`, as below). This\n", + "enables magnetism on the calculator once the layer has an interface.\n", + "There is no separate \"turn magnetism on\" step required.\n", + "\n", + "`LayerMagnetism` exposes two standard, fittable `Parameter`s:\n", + "\n", + "| Parameter | Meaning | Unit | Default |\n", + "|-----------|-----------------------------------------|-----------------------------|---------|\n", + "| `rho_m` | Magnetic scattering length density | $10^{-6}$ Å$^{-2}$ | 0.0 |\n", + "| `theta_m` | In-plane moment angle vs. the beam | degree | 270.0 |\n", + "\n", + "We wrap sample construction in a function so the same recipe can be reused\n", + "below to build a \"truth\" model and, later, independent \"fit starting point\"\n", + "models." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "def build_magnetic_model(rho_m: float, theta_m: float, name: str) -> tuple[Model, Layer]:\n", + " \"\"\"Build a Vacuum / Fe(magnetic) / Si model and return it with the magnetic layer.\n", + "\n", + " :param rho_m: Magnetic SLD of the Fe film, in 1e-6/angstrom^2.\n", + " :param theta_m: In-plane moment angle of the Fe film, in degrees.\n", + " :param name: Name for the model.\n", + " :return: The model (interface already switched to refl1d) and the Fe layer,\n", + " so its ``.magnetism`` parameters can be reached directly for fitting.\n", + " \"\"\"\n", + " vacuum = Material(sld=0, isld=0, name='Vacuum')\n", + " iron = Material(sld=4.0, isld=0, name='Fe')\n", + " silicon = Material(sld=2.047, isld=0, name='Si')\n", + "\n", + " superphase = Layer(material=vacuum, thickness=0, roughness=0, name='Vacuum Superphase')\n", + " film = Layer(\n", + " material=iron,\n", + " thickness=100,\n", + " roughness=0,\n", + " magnetism=LayerMagnetism(rho_m=rho_m, theta_m=theta_m, name='Fe film moment'),\n", + " name='Fe Film',\n", + " )\n", + " subphase = Layer(material=silicon, thickness=0, roughness=0, name='Si Subphase')\n", + "\n", + " sample = Sample(Multilayer(superphase), Multilayer(film), Multilayer(subphase), name='Vacuum / Fe(magnetic) / Si')\n", + " model = Model(sample=sample, scale=1, background=0, name=name)\n", + " model.resolution_function = PercentageFwhm(0) # 0% resolution keeps this simulation clean\n", + "\n", + " interface = CalculatorFactory()\n", + " interface.switch('refl1d') # the only calculator that supports magnetism\n", + " model.interface = interface\n", + "\n", + " return model, film\n", + "\n", + "\n", + "RHO_M_TRUE = 2.5 # 1e-6 / angstrom^2\n", + "THETA_M_TRUE = 45.0 # degrees -- canted, so all four channels differ\n", + "\n", + "truth_model, truth_film = build_magnetic_model(RHO_M_TRUE, THETA_M_TRUE, name='Truth model')\n", + "\n", + "print(f'Fe film magnetism: rho_m = {truth_film.magnetism.rho_m.value}, theta_m = {truth_film.magnetism.theta_m.value}')" + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "## 2. All four polarization channels\n", + "\n", + "`model.interface.polarized_reflectivity_profiles(q, model_name)` returns a\n", + "dict with all four spin cross-sections at once:\n", + "\n", + "- `'pp'` — non-spin-flip, up-up\n", + "- `'mm'` — non-spin-flip, down-down\n", + "- `'pm'` — spin-flip, up-down\n", + "- `'mp'` — spin-flip, down-up\n", + "\n", + "Internally this is a single `refl1d` kernel evaluation shared by all four\n", + "channels (and cached per model state), so this costs about the same as\n", + "computing one channel. For a single explicit channel use\n", + "`reflectivity_profile_channel(q, model_name, channel)`instead.\n", + "Both are stateless, unlike setting `interface.polarization_channel`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "Q_PLOT = np.linspace(0.001, 0.3, 500)\n", + "\n", + "channels_truth = truth_model.interface.polarized_reflectivity_profiles(Q_PLOT, truth_model.unique_name)\n", + "\n", + "plt.figure(figsize=(8, 5))\n", + "plt.semilogy(Q_PLOT, channels_truth['pp'], '-k', label='pp (non-spin-flip)', linewidth=2)\n", + "plt.semilogy(Q_PLOT, channels_truth['mm'], '-r', label='mm (non-spin-flip)', linewidth=2)\n", + "plt.semilogy(Q_PLOT, channels_truth['pm'], ':k', label='pm (spin-flip)', linewidth=2)\n", + "plt.semilogy(Q_PLOT, channels_truth['mp'], ':r', label='mp (spin-flip)', linewidth=2)\n", + "plt.xlabel('Q / Å⁻¹')\n", + "plt.ylabel('Reflectivity')\n", + "plt.title(f'Four polarization channels (rho_m={RHO_M_TRUE}, theta_m={THETA_M_TRUE}°)')\n", + "plt.legend()\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "## 3. The spin-resolved depth profile\n", + "\n", + "`Project.magnetic_sld_data_for_model_at_index()` returns the nuclear SLD\n", + "profile alongside the two potentials a neutron in each spin state actually\n", + "experiences: `spin_up = sld + rho_m * cos(theta_m - guide_field_angle)` and\n", + "`spin_down = sld - rho_m * cos(...)`. With a canted moment (not aligned with\n", + "the guide field) the split between the two curves is reduced by that cosine\n", + "factor rather than being the full `rho_m`.\n", + "\n", + "This wraps the model in a `Project`, the same object a GUI application uses\n", + "to manage models, experiments and fitting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()\n", + "project.calculator = 'refl1d'\n", + "project.models = ModelCollection(truth_model)\n", + "\n", + "profiles = project.magnetic_sld_data_for_model_at_index(0)\n", + "\n", + "plt.figure(figsize=(8, 5))\n", + "plt.plot(profiles['sld'].x, profiles['sld'].y, '-k', label='Nuclear SLD', linewidth=2)\n", + "plt.plot(profiles['spin_up'].x, profiles['spin_up'].y, '-b', label='Spin-up potential', linewidth=2)\n", + "plt.plot(profiles['spin_down'].x, profiles['spin_down'].y, '-r', label='Spin-down potential', linewidth=2)\n", + "plt.xlabel('z / Å')\n", + "plt.ylabel('SLD / 10⁻⁶ Å⁻²')\n", + "plt.title('Nuclear SLD and the two spin-dependent potentials')\n", + "plt.legend()\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()\n", + "\n", + "# The moment magnitude and direction are also available on their own, restricted\n", + "# to the depths that actually carry a moment (an angle is meaningless at zero SLD).\n", + "print(f'Peak magnetic SLD in the film: {profiles[\"rho_m\"].y.max():.3f} (expected {RHO_M_TRUE})')\n", + "print(f'Moment angle inside the film: {profiles[\"theta_m\"].y.mean():.1f}° (expected {THETA_M_TRUE}°)')" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": { + "lines_to_next_cell": 2 + }, + "source": [ + "## 4. Loading a polarized experiment and forming the spin asymmetry\n", + "\n", + "A polarized measurement is typically serialized as one data file per spin channel\n", + "(e.g. `..._uu.dat` for up-up, `..._dd.dat` for down-down). Here we simulate\n", + "that by writing the truth model's `pp`/`mm` reflectivity, with 1% relative\n", + "noise, to two files, then loading them back exactly as a user would with\n", + "real instrument output.\n", + "\n", + "The **spin asymmetry** $SA = (R^{++} - R^{--}) / (R^{++} + R^{--})$ is a\n", + "common way to look at polarized data directly: it cancels the non-magnetic\n", + "(nuclear) part of the reflectivity and isolates the magnetic signal, with\n", + "`Project.spin_asymmetry_for_experiment_at_index()` handling the variance\n", + "propagation and dropping points where the denominator is too small to be\n", + "meaningful." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "def add_relative_noise(\n", + " reflectivity: np.ndarray, relative_sigma: float, rng: np.random.Generator\n", + ") -> tuple[np.ndarray, np.ndarray]:\n", + " \"\"\"Add reproducible Gaussian noise scaled to a fixed fraction of the signal.\n", + "\n", + " :param reflectivity: Noise-free reflectivity values.\n", + " :param relative_sigma: Standard deviation as a fraction of the signal (e.g. 0.01 for 1%).\n", + " :param rng: Seeded random number generator, for reproducible tutorial output.\n", + " :return: Noisy reflectivity and the per-point standard deviation (not variance).\n", + " \"\"\"\n", + " sigma = relative_sigma * np.abs(reflectivity)\n", + " noisy = rng.normal(loc=reflectivity, scale=sigma)\n", + " return np.clip(noisy, 1e-12, None), sigma\n", + "\n", + "\n", + "Q_DATA = np.linspace(0.01, 0.25, 60) # a more realistic, instrument-like grid\n", + "rng = np.random.default_rng(seed=42) # fixed seed: this tutorial's output is reproducible\n", + "\n", + "channels_data_grid = truth_model.interface.polarized_reflectivity_profiles(Q_DATA, truth_model.unique_name)\n", + "noisy_channels = {\n", + " channel: add_relative_noise(reflectivity, relative_sigma=0.01, rng=rng)\n", + " for channel, reflectivity in channels_data_grid.items()\n", + "}\n", + "\n", + "tmp_dir = Path(tempfile.mkdtemp(prefix='easyreflectometry_polarized_'))\n", + "pp_path = tmp_dir / 'fe_film_uu.txt'\n", + "mm_path = tmp_dir / 'fe_film_dd.txt'\n", + "np.savetxt(pp_path, np.column_stack([Q_DATA, noisy_channels['pp'][0], noisy_channels['pp'][1]]))\n", + "np.savetxt(mm_path, np.column_stack([Q_DATA, noisy_channels['mm'][0], noisy_channels['mm'][1]]))\n", + "\n", + "# The filename suffixes ('_uu', '_dd') are recognised automatically.\n", + "print(project.suggest_polarized_channel_assignment([pp_path, mm_path]))\n", + "\n", + "experiment_index = project.load_polarized_experiment({'pp': pp_path, 'mm': mm_path})\n", + "loaded_channels = project.experiment_channels_at_index(experiment_index)\n", + "print(f'Loaded polarized experiment at index {experiment_index}, channels: {loaded_channels}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "spin_asymmetry = project.spin_asymmetry_for_experiment_at_index(experiment_index)\n", + "measured, calculated = spin_asymmetry['measured'], spin_asymmetry['calculated']\n", + "\n", + "plt.figure(figsize=(8, 5))\n", + "plt.errorbar(\n", + " measured.x,\n", + " measured.y,\n", + " yerr=np.sqrt(measured.ye),\n", + " fmt='o',\n", + " color='0.3',\n", + " markersize=4,\n", + " alpha=0.6,\n", + " label='Measured (loaded files)',\n", + ")\n", + "plt.plot(calculated.x, calculated.y, '-r', linewidth=2, label='Calculated (truth model)')\n", + "plt.xlabel('Q / Å⁻¹')\n", + "plt.ylabel('Spin asymmetry')\n", + "plt.title('Spin asymmetry: loaded data vs. the model it was generated from')\n", + "plt.legend()\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()\n", + "\n", + "print(\n", + " f'{measured.x.size} of {Q_DATA.size} points kept '\n", + " f'({spin_asymmetry[\"masked_points\"]} masked: '\n", + " f'{spin_asymmetry[\"low_significance_points\"]} low-significance, '\n", + " f'{spin_asymmetry[\"small_denominator_points\"]} small-denominator).'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## 5. Fitting: recovering the moment magnitude from two channels\n", + "\n", + "The most common polarized experiment measures only the two non-spin-flip\n", + "channels (`pp`, `mm`). If the moment's *direction* is already known from\n", + "other means (sample geometry, prior characterization), that alone is enough\n", + "to recover its *magnitude*. This is the standard polarized-fitting case.\n", + "\n", + "`MultiFitter.fit_polarized()` takes a `PolarizedDataSet` and fits every\n", + "channel it contains **simultaneously against one shared model**: any\n", + "structural parameter (thickness, roughness, nuclear SLD, scale, background)\n", + "is constrained jointly by all measured channels, and so is `rho_m`/`theta_m`.\n", + "Internally `refl1d` still evaluates all cross-sections from a single kernel\n", + "call, so fitting N channels together costs about as much as fitting one.\n", + "\n", + "We start from a deliberately wrong `rho_m` guess and fit against the two\n", + "noisy channels loaded above; `theta_m` stays fixed at its (assumed known)\n", + "true value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "fit_model_2ch, fit_film_2ch = build_magnetic_model(rho_m=1.0, theta_m=THETA_M_TRUE, name='Fit: two channels (rho_m only)')\n", + "\n", + "fit_film_2ch.magnetism.rho_m.fixed = False\n", + "fit_film_2ch.magnetism.rho_m.bounds = (0.0, 5.0)\n", + "fit_film_2ch.magnetism.theta_m.fixed = True # moment direction assumed known\n", + "\n", + "initial_channels_2ch = fit_model_2ch.interface.polarized_reflectivity_profiles(Q_PLOT, fit_model_2ch.unique_name)\n", + "\n", + "fit_data_2ch = PolarizedDataSet(\n", + " name='Fe film (pp, mm)',\n", + " channels={\n", + " 'pp': DataSet1D(name='pp', x=Q_DATA, y=noisy_channels['pp'][0], ye=noisy_channels['pp'][1] ** 2),\n", + " 'mm': DataSet1D(name='mm', x=Q_DATA, y=noisy_channels['mm'][0], ye=noisy_channels['mm'][1] ** 2),\n", + " },\n", + " model=fit_model_2ch, # PolarizedDataSet.model must be the model the fitter is constructed with\n", + ")\n", + "\n", + "fitter_2ch = MultiFitter(fit_model_2ch)\n", + "results_2ch = fitter_2ch.fit_polarized(fit_data_2ch)\n", + "\n", + "print(f'Channels fitted: {list(results_2ch.keys())}, all successful: {all(r.success for r in results_2ch.values())}')\n", + "print(f'rho_m: {fit_film_2ch.magnetism.rho_m.value:.3f} (started at 1.0, true value {RHO_M_TRUE})')\n", + "print(f'Reduced chi^2: {fitter_2ch.reduced_chi:.3f}')\n", + "\n", + "fitted_channels_2ch = fit_model_2ch.interface.polarized_reflectivity_profiles(Q_PLOT, fit_model_2ch.unique_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure(figsize=(8, 5))\n", + "plt.errorbar(\n", + " Q_DATA,\n", + " noisy_channels['pp'][0],\n", + " yerr=noisy_channels['pp'][1],\n", + " fmt='o',\n", + " color='0.3',\n", + " markersize=4,\n", + " alpha=0.5,\n", + " label='pp (data)',\n", + ")\n", + "plt.errorbar(\n", + " Q_DATA,\n", + " noisy_channels['mm'][0],\n", + " yerr=noisy_channels['mm'][1],\n", + " fmt='s',\n", + " color='0.6',\n", + " markersize=4,\n", + " alpha=0.5,\n", + " label='mm (data)',\n", + ")\n", + "plt.semilogy(Q_PLOT, initial_channels_2ch['pp'], '--k', linewidth=1, alpha=0.6, label='pp (initial guess)')\n", + "plt.semilogy(Q_PLOT, initial_channels_2ch['mm'], '--r', linewidth=1, alpha=0.6, label='mm (initial guess)')\n", + "plt.semilogy(Q_PLOT, fitted_channels_2ch['pp'], '-k', linewidth=2, label='pp (fitted)')\n", + "plt.semilogy(Q_PLOT, fitted_channels_2ch['mm'], '-r', linewidth=2, label='mm (fitted)')\n", + "plt.yscale('log')\n", + "plt.xlabel('Q / Å⁻¹')\n", + "plt.ylabel('Reflectivity')\n", + "plt.title('Two-channel fit: rho_m recovered from pp and mm together')\n", + "plt.legend(fontsize=8)\n", + "plt.grid(True, alpha=0.3)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": {}, + "source": [ + "## 6. Fitting: recovering the full magnetization vector from four channels\n", + "\n", + "When the spin-flip channels (`pm`, `mp`) are also measured, `fit_polarized`\n", + "can determine the moment's *direction* as well as its magnitude. Both\n", + "`rho_m` and `theta_m` are freed and constrained jointly by all four\n", + "channels. This is the distinguishing capability of full polarization\n", + "analysis over a non-spin-flip-only measurement.\n", + "\n", + "We start from wrong guesses for **both** parameters this time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "fit_model_4ch, fit_film_4ch = build_magnetic_model(rho_m=1.5, theta_m=60.0, name='Fit: four channels (rho_m and theta_m)')\n", + "\n", + "fit_film_4ch.magnetism.rho_m.fixed = False\n", + "fit_film_4ch.magnetism.rho_m.bounds = (0.0, 5.0)\n", + "fit_film_4ch.magnetism.theta_m.fixed = False\n", + "fit_film_4ch.magnetism.theta_m.bounds = (0.0, 90.0)\n", + "\n", + "fit_data_4ch = PolarizedDataSet(\n", + " name='Fe film (pp, pm, mp, mm)',\n", + " channels={\n", + " channel: DataSet1D(name=channel, x=Q_DATA, y=values[0], ye=values[1] ** 2) for channel, values in noisy_channels.items()\n", + " },\n", + " model=fit_model_4ch,\n", + ")\n", + "\n", + "fitter_4ch = MultiFitter(fit_model_4ch)\n", + "results_4ch = fitter_4ch.fit_polarized(fit_data_4ch)\n", + "\n", + "print(f'Channels fitted: {list(results_4ch.keys())}, all successful: {all(r.success for r in results_4ch.values())}')\n", + "print(f'rho_m: {fit_film_4ch.magnetism.rho_m.value:.3f} (started at 1.5, true value {RHO_M_TRUE})')\n", + "print(f'theta_m: {fit_film_4ch.magnetism.theta_m.value:.1f}° (started at 60.0°, true value {THETA_M_TRUE}°)')\n", + "print(f'Reduced chi^2: {fitter_4ch.reduced_chi:.3f}')" + ] + }, + { + "cell_type": "markdown", + "id": "16", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "See `docs/docs/tutorials/simulation/magnetism.ipynb` for the basics of\n", + "building magnetic samples and selecting a single channel." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/tutorials/simulation/magnetism.ipynb b/docs/docs/tutorials/simulation/magnetism.ipynb index 8efdb9e4..63e49538 100644 --- a/docs/docs/tutorials/simulation/magnetism.ipynb +++ b/docs/docs/tutorials/simulation/magnetism.ipynb @@ -4,27 +4,28 @@ "cell_type": "markdown", "id": "a60117e3-d089-4375-ac7c-12a52ed47271", "metadata": {}, - "source": [ - "# Magnetism\n", - "\n", - "Magntism is only available in Refl1d and it does not support RepeatingMultilayer\n", - "\n" - ] + "source": "# Magnetism\n\nMagnetism is only available in Refl1d, and refl1d itself does not support magnetic layers inside a `RepeatingMultilayer` (it raises `NotImplementedError` rather than silently producing a wrong profile).\n\nWhen magnetism is enabled (`include_magnetism = True`) all four polarization channels are available: the non-spin-flip channels (`pp`, `mm`) and the spin-flip channels (`pm`, `mp`)." }, { "cell_type": "markdown", "id": "f5d0bd58", "metadata": {}, "source": [ - "## Setup\n", - "First configure matplotlib to place figures in notebook and import needed modules" + "## Setup" ] }, { "cell_type": "code", "execution_count": null, "id": "644e53e3", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:34.890141Z", + "iopub.status.busy": "2026-08-11T12:33:34.890141Z", + "iopub.status.idle": "2026-08-11T12:33:35.320450Z", + "shell.execute_reply": "2026-08-11T12:33:35.320450Z" + } + }, "outputs": [], "source": [ "%matplotlib inline" @@ -34,7 +35,14 @@ "cell_type": "code", "execution_count": null, "id": "29d5d62d-af4a-416d-bbe2-1338d32b30f5", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:35.320450Z", + "iopub.status.busy": "2026-08-11T12:33:35.320450Z", + "iopub.status.idle": "2026-08-11T12:33:38.194648Z", + "shell.execute_reply": "2026-08-11T12:33:38.194648Z" + } + }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", @@ -49,6 +57,7 @@ "from easyreflectometry.model import Model\n", "from easyreflectometry.model import PercentageFwhm\n", "from easyreflectometry.sample import Layer\n", + "from easyreflectometry.sample import LayerMagnetism\n", "from easyreflectometry.sample import Material\n", "from easyreflectometry.sample import Multilayer\n", "from easyreflectometry.sample import Sample" @@ -66,7 +75,14 @@ "cell_type": "code", "execution_count": null, "id": "549734c1-bbd9-41f3-8a20-d7a8ded37802", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.194648Z", + "iopub.status.busy": "2026-08-11T12:33:38.194648Z", + "iopub.status.idle": "2026-08-11T12:33:38.202401Z", + "shell.execute_reply": "2026-08-11T12:33:38.202401Z" + } + }, "outputs": [], "source": [ "print(f'numpy: {np.__version__}')\n", @@ -101,7 +117,14 @@ "cell_type": "code", "execution_count": null, "id": "0f95d620-35b7-4b47-a3b4-9e33d5525b50", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.202401Z", + "iopub.status.busy": "2026-08-11T12:33:38.202401Z", + "iopub.status.idle": "2026-08-11T12:33:38.215900Z", + "shell.execute_reply": "2026-08-11T12:33:38.214943Z" + } + }, "outputs": [], "source": [ "sld_4 = Material(sld=4.0, isld=0, name='Sld 4')\n", @@ -129,7 +152,14 @@ "cell_type": "code", "execution_count": null, "id": "2af8c30b", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.215900Z", + "iopub.status.busy": "2026-08-11T12:33:38.215900Z", + "iopub.status.idle": "2026-08-11T12:33:38.223629Z", + "shell.execute_reply": "2026-08-11T12:33:38.223629Z" + } + }, "outputs": [], "source": [ "two_layers = Multilayer([sld_4_layer, sld_8_layer], name='SLD 4/8 Layer')\n", @@ -154,7 +184,14 @@ "cell_type": "code", "execution_count": null, "id": "b0259cd0", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.223629Z", + "iopub.status.busy": "2026-08-11T12:33:38.223629Z", + "iopub.status.idle": "2026-08-11T12:33:38.229743Z", + "shell.execute_reply": "2026-08-11T12:33:38.229743Z" + } + }, "outputs": [], "source": [ "refl1d_sld_4 = refl1d.names.SLD(name='Sld 4', rho=4.0, irho=0)\n", @@ -180,7 +217,14 @@ "cell_type": "code", "execution_count": null, "id": "f1500603-d85d-4e16-b697-e1bf16502991", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.232600Z", + "iopub.status.busy": "2026-08-11T12:33:38.232600Z", + "iopub.status.idle": "2026-08-11T12:33:38.237388Z", + "shell.execute_reply": "2026-08-11T12:33:38.237388Z" + } + }, "outputs": [], "source": [ "interface = CalculatorFactory()" @@ -199,7 +243,14 @@ "cell_type": "code", "execution_count": null, "id": "18010202", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.237388Z", + "iopub.status.busy": "2026-08-11T12:33:38.237388Z", + "iopub.status.idle": "2026-08-11T12:33:38.244064Z", + "shell.execute_reply": "2026-08-11T12:33:38.244064Z" + } + }, "outputs": [], "source": [ "model_coords = np.linspace(\n", @@ -231,7 +282,14 @@ "cell_type": "code", "execution_count": null, "id": "cdf959c8", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.244064Z", + "iopub.status.busy": "2026-08-11T12:33:38.244064Z", + "iopub.status.idle": "2026-08-11T12:33:38.717693Z", + "shell.execute_reply": "2026-08-11T12:33:38.717693Z" + } + }, "outputs": [], "source": [ "# Refl1d\n", @@ -251,7 +309,7 @@ "model.interface = interface\n", "model.resolution_function = PercentageFwhm(0)\n", "model_interface = model.interface()\n", - "model_interface.magnetism = False\n", + "model_interface.include_magnetism = False\n", "model_data_no_magnetism_ref1d_easy = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", @@ -276,7 +334,14 @@ "cell_type": "code", "execution_count": null, "id": "bf311973", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.717693Z", + "iopub.status.busy": "2026-08-11T12:33:38.717693Z", + "iopub.status.idle": "2026-08-11T12:33:38.962554Z", + "shell.execute_reply": "2026-08-11T12:33:38.962554Z" + } + }, "outputs": [], "source": [ "# Without magnetic layers\n", @@ -293,14 +358,9 @@ "# With magnetic layers\n", "interface.switch('refl1d')\n", "model.interface = interface\n", + "sld_4_layer.magnetism = LayerMagnetism(rho_m=10, theta_m=70, name='Sld 4 moment')\n", + "sld_8_layer.magnetism = LayerMagnetism(rho_m=5, theta_m=175, name='Sld 8 moment')\n", "model_interface = model.interface()\n", - "model_interface.include_magnetism = True\n", - "model_interface._wrapper.update_layer(\n", - " list(model_interface._wrapper.storage['layer'].keys())[1], magnetism_rhoM=10, magnetism_thetaM=70\n", - ")\n", - "model_interface._wrapper.update_layer(\n", - " list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175\n", - ")\n", "model_data_magnetism_layer_1 = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", @@ -331,7 +391,14 @@ "cell_type": "code", "execution_count": null, "id": "18cb7037", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:38.965319Z", + "iopub.status.busy": "2026-08-11T12:33:38.965319Z", + "iopub.status.idle": "2026-08-11T12:33:39.181473Z", + "shell.execute_reply": "2026-08-11T12:33:39.181473Z" + } + }, "outputs": [], "source": [ "# Refl1d model is inverted as compared to EasyReflectometry, so the order of the layers is reversed\n", @@ -343,8 +410,9 @@ ")\n", "model_name = model.unique_name\n", "storage = {'model': {model_name: {}}}\n", - "storage['model'][model_name]['scale'] = 10.0\n", - "storage['model'][model_name]['bkg'] = 20.0\n", + "# Match the EasyReflectometry model: scale=1, background=0\n", + "storage['model'][model_name]['scale'] = 1.0\n", + "storage['model'][model_name]['bkg'] = 0.0\n", "\n", "polarized_probe = _get_polarized_probe(\n", " q_array=model_coords, dq_array=np.zeros(len(model_coords)), model_name=model_name, storage=storage\n", @@ -357,14 +425,9 @@ "# EasyReflectometry\n", "interface.switch('refl1d')\n", "model.interface = interface\n", + "sld_4_layer.magnetism = LayerMagnetism(rho_m=10, theta_m=70, name='Sld 4 moment')\n", + "sld_8_layer.magnetism = LayerMagnetism(rho_m=5, theta_m=175, name='Sld 8 moment')\n", "model_interface = model.interface()\n", - "model_interface.include_magnetism = True\n", - "model_interface._wrapper.update_layer(\n", - " list(model_interface._wrapper.storage['layer'].keys())[1], magnetism_rhoM=10, magnetism_thetaM=70\n", - ")\n", - "model_interface._wrapper.update_layer(\n", - " list(model_interface._wrapper.storage['layer'].keys())[2], magnetism_rhoM=5, magnetism_thetaM=175\n", - ")\n", "model_data_magnetism_easy = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", @@ -386,7 +449,14 @@ "cell_type": "code", "execution_count": null, "id": "7033f755", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:39.183451Z", + "iopub.status.busy": "2026-08-11T12:33:39.183451Z", + "iopub.status.idle": "2026-08-11T12:33:39.188618Z", + "shell.execute_reply": "2026-08-11T12:33:39.188618Z" + } + }, "outputs": [], "source": [ "print(max(abs(model_data_magnetism_easy - model_data_magnetism_ref1d)))" @@ -406,22 +476,29 @@ "id": "7af84a69", "metadata": {}, "source": [ - "## Refl1d polarized probe for a single layer sample\n", - " This study is done with magnetism to show the results for polarized probe." + "## All polarization channels for a single layer sample\n", + "\n", + "This study is done with magnetism to show the reflectivity of all four spin cross-sections. First we compute the reference directly in Refl1d for a single magnetic layer on a silicon subphase." ] }, { "cell_type": "code", "execution_count": null, "id": "352c35e9", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:39.188618Z", + "iopub.status.busy": "2026-08-11T12:33:39.188618Z", + "iopub.status.idle": "2026-08-11T12:33:39.424010Z", + "shell.execute_reply": "2026-08-11T12:33:39.424010Z" + } + }, "outputs": [], "source": [ - "# The magnetism is set to 8.\n", - "# This would double (pp) and cancel out (mm) the magnitude of the reflectivity oscillations when its angle is set to 90.\n", - "# This would give the strongest spin-flipping (pm and mp) when its angle is set to 0.\n", - "# However we set the angle to 45, so the reflectivity oscillations are not doubled or cancelled out,\n", - "# and the spin-flipping is not maximized.\n", + "# The magnetic SLD is set to 8 and its angle to 45 degrees.\n", + "# An angle of 90 would double (pp) and cancel out (mm) the magnitude of the reflectivity oscillations,\n", + "# while an angle of 0 would give the strongest spin-flipping (pm and mp).\n", + "# At 45 degrees neither effect is maximized, so all four channels are distinct.\n", "refl1d_sample = (\n", " refl1d_si(0, 0) | refl1d_sld_8(150, 0, magnetism=refl1d.names.Magnetism(rhoM=8, thetaM=45)) | refl1d_vacuum(0, 0)\n", ")\n", @@ -432,69 +509,215 @@ "storage['model'][model_name]['bkg'] = 0.0\n", "\n", "polarized_probe = _get_polarized_probe(\n", - " q_array=model_coords, dq_array=np.zeros(len(model_coords)), model_name=model_name, storage=storage, all_polarizations=True\n", + " q_array=model_coords, dq_array=np.zeros(len(model_coords)), model_name=model_name, storage=storage\n", ")\n", "\n", - "experiment = refl1d.names.Experiment(probe=polarized_probe, sample=refl1d_sample)" + "experiment = refl1d.names.Experiment(probe=polarized_probe, sample=refl1d_sample)\n", + "# One reflectivity() call returns all four cross-sections, in the order pp, pm, mp, mm\n", + "raw_reflectivities = experiment.reflectivity()\n", + "raw_channels = {key: reflectivity for key, (_, reflectivity) in zip(('pp', 'pm', 'mp', 'mm'), raw_reflectivities)}\n", + "\n", + "plt.plot(model_coords, raw_channels['pp'], '-k', label='Refl1d pp', linewidth=4)\n", + "plt.plot(model_coords, raw_channels['mm'], '-r', label='Refl1d mm', linewidth=2)\n", + "plt.plot(model_coords, raw_channels['pm'], ':k', label='Refl1d pm', linewidth=4)\n", + "plt.plot(model_coords, raw_channels['mp'], ':r', label='Refl1d mp', linewidth=2)\n", + "\n", + "plot_apply_makeup()" + ] + }, + { + "cell_type": "markdown", + "id": "0b4b4d3f", + "metadata": {}, + "source": [ + "### All polarization channels in EasyReflectometry\n", + "\n", + "The same four channels are available through the EasyReflectometry API via `polarized_reflectivity_profiles`, which returns a dictionary keyed `pp`, `pm`, `mp`, `mm`. We build the equivalent single layer model and attach a `LayerMagnetism` to its layer, which makes it magnetic and enables `include_magnetism` automatically." ] }, { "cell_type": "code", "execution_count": null, "id": "239e2a04", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:39.424010Z", + "iopub.status.busy": "2026-08-11T12:33:39.424010Z", + "iopub.status.idle": "2026-08-11T12:33:39.723594Z", + "shell.execute_reply": "2026-08-11T12:33:39.723594Z" + } + }, "outputs": [], "source": [ - "model_data_magnetism_ref1d_raw_pp = experiment.reflectivity()[0][1]\n", - "model_data_magnetism_ref1d_raw_pm = experiment.reflectivity()[1][1]\n", - "model_data_magnetism_ref1d_raw_mp = experiment.reflectivity()[2][1]\n", - "model_data_magnetism_ref1d_raw_mm = experiment.reflectivity()[3][1]\n", + "# The single layer model matching the raw Refl1d sample above\n", + "vacuum_single = Material(sld=0, isld=0, name='Vacuum')\n", + "sld_8_single = Material(sld=8.0, isld=0, name='Sld 8')\n", + "si_single = Material(sld=2.047, isld=0, name='Si')\n", + "superphase_single = Layer(material=vacuum_single, thickness=0, roughness=0, name='Vacuum Superphase')\n", + "magnetic_layer = Layer(material=sld_8_single, thickness=150, roughness=0, name='Magnetic Layer')\n", + "subphase_single = Layer(material=si_single, thickness=0, roughness=0, name='Si Subphase')\n", + "single_layer_model = Model(\n", + " sample=Sample(\n", + " Multilayer(superphase_single),\n", + " Multilayer(magnetic_layer),\n", + " Multilayer(subphase_single),\n", + " name='Single Layer Sample',\n", + " ),\n", + " scale=1,\n", + " background=0,\n", + " name='Single Layer Model',\n", + ")\n", "\n", - "plt.plot(model_coords, model_data_magnetism_ref1d_raw_pp, '-k', label='Refl1d pp', linewidth=4)\n", - "plt.plot(model_coords, model_data_magnetism_ref1d_raw_mm, '-r', label='Refl1d mm', linewidth=2)\n", - "plt.plot(model_coords, model_data_magnetism_ref1d_raw_pm, ':k', label='Refl1d pm', linewidth=4)\n", - "plt.plot(model_coords, model_data_magnetism_ref1d_raw_mp, ':r', label='Refl1d mp', linewidth=2)\n", + "interface.switch('refl1d')\n", + "single_layer_model.interface = interface\n", + "single_layer_model.resolution_function = PercentageFwhm(0)\n", + "magnetic_layer.magnetism = LayerMagnetism(rho_m=8, theta_m=45, name='Magnetic layer moment')\n", + "model_interface = single_layer_model.interface()\n", + "\n", + "channels = single_layer_model.interface.polarized_reflectivity_profiles(\n", + " model_coords,\n", + " single_layer_model.unique_name,\n", + ")\n", + "\n", + "plt.plot(model_coords, channels['pp'], '-k', label='EasyReflectometry pp', linewidth=4)\n", + "plt.plot(model_coords, channels['mm'], '-r', label='EasyReflectometry mm', linewidth=2)\n", + "plt.plot(model_coords, channels['pm'], ':k', label='EasyReflectometry pm', linewidth=4)\n", + "plt.plot(model_coords, channels['mp'], ':r', label='EasyReflectometry mp', linewidth=2)\n", "\n", "plot_apply_makeup()" ] }, { "cell_type": "markdown", - "id": "ac52936c", + "id": "48fca800", "metadata": {}, "source": [ - "## Refl1 and Refnx in EasyReflectometry.\n", - "This study is done without magnetism as Refnx does not support this yet." + "The two models agree for every polarization channel." ] }, { "cell_type": "code", "execution_count": null, - "id": "e59d3153-f0da-4fce-a4f0-a424010acbec", + "id": "f70f3a34", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:39.723594Z", + "iopub.status.busy": "2026-08-11T12:33:39.723594Z", + "iopub.status.idle": "2026-08-11T12:33:39.729761Z", + "shell.execute_reply": "2026-08-11T12:33:39.729761Z" + } + }, + "outputs": [], + "source": [ + "for key in channels:\n", + " print(f'{key}: {max(abs(channels[key] - raw_channels[key]))}')" + ] + }, + { + "cell_type": "markdown", + "id": "30a07896", "metadata": {}, + "source": [ + "### Selecting a single channel\n", + "\n", + "`reflectity_profile` — the function used when fitting — returns the channel selected by `polarization_channel` (default `pp`). To fit against, say, `mm` data, select the `mm` channel.\n", + "\n", + "Note that the selected channel is state of the currently active calculator: it affects every model and fit using that calculator until it is changed, and `interface.switch(...)` constructs a fresh calculator, which resets the channel (along with `include_magnetism`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23beb5a6", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:39.729761Z", + "iopub.status.busy": "2026-08-11T12:33:39.729761Z", + "iopub.status.idle": "2026-08-11T12:33:39.739748Z", + "shell.execute_reply": "2026-08-11T12:33:39.739748Z" + } + }, "outputs": [], "source": [ - "# Refnx\n", - "interface.switch('refnx')\n", - "model.interface = interface\n", - "model_interface = model.interface()\n", - "model_data_no_magnetism_refnx = model.interface().reflectity_profile(\n", + "model_interface.polarization_channel = 'mm'\n", + "reflectivity_mm = single_layer_model.interface().reflectity_profile(\n", " model_coords,\n", - " model.unique_name,\n", + " single_layer_model.unique_name,\n", ")\n", - "plt.plot(model_coords, model_data_no_magnetism_refnx, 'k-', label=f'EasyReflectometry ({model_interface.name})', linewidth=5)\n", + "print(f'mm channel reproduced: {max(abs(reflectivity_mm - channels[\"mm\"]))}')\n", "\n", - "# Refl1d\n", - "interface.switch('refl1d')\n", - "model.interface = interface\n", - "model_interface = model.interface()\n", - "model_data_no_magnetism_ref1d = model.interface().reflectity_profile(\n", - " model_coords,\n", - " model.unique_name,\n", - ")\n", - "plt.plot(model_coords, model_data_no_magnetism_ref1d, 'r-', label=f'EasyReflectometry ({model_interface.name})', linewidth=2)\n", + "# Reset to the default channel so later cells are unaffected\n", + "model_interface.polarization_channel = 'pp'" + ] + }, + { + "cell_type": "markdown", + "id": "a2d2d869", + "metadata": {}, + "source": [ + "### Magnetic SLD profile\n", "\n", - "plot_apply_makeup()" + "Alongside the nuclear SLD profile (`sld_profile`), the magnetic components are available through `magnetic_sld_profile`, which returns `z`, the nuclear SLD, the magnetic SLD (`rhoM`) and the magnetic angle (`thetaM`). Like the polarized reflectivities it requires `include_magnetism` to be enabled and is only available for the Refl1d calculator.\n", + "\n", + "Below we display the profiles for the single magnetic layer sample used above. The nuclear and magnetic SLD share the same unit ($10^{-6}$ Å$^{-2}$) so they are shown on one axis; the magnetic angle is constant (45 degrees) inside the layer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6975f54", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:39.739748Z", + "iopub.status.busy": "2026-08-11T12:33:39.739748Z", + "iopub.status.idle": "2026-08-11T12:33:39.834787Z", + "shell.execute_reply": "2026-08-11T12:33:39.834787Z" + } + }, + "outputs": [], + "source": [ + "z, sld, sld_magnetic, theta_magnetic = single_layer_model.interface.magnetic_sld_profile(single_layer_model.unique_name)\n", + "\n", + "# In this sample the nuclear and magnetic SLD are both 8 inside the layer, so the curves overlap there\n", + "plt.plot(z, sld, '-k', label='Nuclear SLD', linewidth=4)\n", + "plt.plot(z, sld_magnetic, '-r', label='Magnetic SLD', linewidth=2)\n", + "plt.xlabel(r'$z$ / Å')\n", + "plt.ylabel(r'SLD / $10^{-6}$ Å$^{-2}$')\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "inside_layer = (z > 25) & (z < 125)\n", + "print(f'Magnetic angle inside the layer: {theta_magnetic[inside_layer].mean():.1f} degrees')" + ] + }, + { + "cell_type": "markdown", + "id": "f0223d02", + "metadata": {}, + "source": [ + "The profiles reproduce the ones determined directly by Refl1d for the equivalent sample (`magnetic_smooth_profile`). Since the Refl1d sample is built in the reverse order, its profiles are flipped before comparing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6327bf82", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:39.834787Z", + "iopub.status.busy": "2026-08-11T12:33:39.834787Z", + "iopub.status.idle": "2026-08-11T12:33:39.843123Z", + "shell.execute_reply": "2026-08-11T12:33:39.843123Z" + } + }, + "outputs": [], + "source": [ + "# `experiment` is the raw Refl1d experiment for the single magnetic layer sample defined above\n", + "raw_z, raw_sld, _, raw_sld_magnetic, raw_theta_magnetic = experiment.magnetic_smooth_profile()\n", + "\n", + "print(f'sld: {max(abs(sld - raw_sld[::-1]))}')\n", + "print(f'magnetic sld: {max(abs(sld_magnetic - raw_sld_magnetic[::-1]))}')\n", + "print(f'magnetic angle: {max(abs(theta_magnetic - raw_theta_magnetic[::-1]))}')" ] }, { @@ -510,25 +733,40 @@ "cell_type": "code", "execution_count": null, "id": "b087e848", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:39.843123Z", + "iopub.status.busy": "2026-08-11T12:33:39.843123Z", + "iopub.status.idle": "2026-08-11T12:33:40.044974Z", + "shell.execute_reply": "2026-08-11T12:33:40.044974Z" + } + }, "outputs": [], "source": [ - "# With Magnitism\n", + "# The magnetic layers attached earlier in this tutorial live on the `Layer`\n", + "# objects themselves (unlike the old wrapper-storage workaround, which was\n", + "# scoped to one calculator instance and reset on every `interface.switch(...)`).\n", + "# Detach them so this section demonstrates its own point: a sample with no\n", + "# magnetic layers at all.\n", + "sld_4_layer.magnetism = None\n", + "sld_8_layer.magnetism = None\n", + "\n", + "# With Magnetism\n", "interface.switch('refl1d')\n", "model.interface = interface\n", "model_interface = model.interface()\n", - "model_interface.magnetism = True\n", + "model_interface.include_magnetism = True\n", "model_data_magnetism = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", ")\n", "plt.plot(model_coords, model_data_magnetism, '-k', label=f'With magnetism ({model_interface.name})', linewidth=4)\n", "\n", - "# Without Magnitism\n", + "# Without Magnetism\n", "interface.switch('refl1d')\n", "model.interface = interface\n", "model_interface = model.interface()\n", - "model_interface.magnetism = False\n", + "model_interface.include_magnetism = False\n", "model_data_no_magnetism = model.interface().reflectity_profile(\n", " model_coords,\n", " model.unique_name,\n", @@ -543,14 +781,21 @@ "id": "d1b41ed2", "metadata": {}, "source": [ - "We don't see any significant change in the determined reflectivity when enabling the ability to account for magnetism. However, there is a small difference, which is due to the fact that we are using `PolarizedQProbe` (Refl1d) when handling magnetic samples whereas non-magnetic samples are handled with a `QProbe` (Refl1d)." + "We don't see any change in the determined reflectivity when enabling the ability to account for magnetism for a sample without any magnetic layers, even though magnetic samples are handled with a `PolarizedQProbe` (Refl1d) whereas non-magnetic samples are handled with a `QProbe` (Refl1d)." ] }, { "cell_type": "code", "execution_count": null, "id": "00c25554", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-11T12:33:40.044974Z", + "iopub.status.busy": "2026-08-11T12:33:40.044974Z", + "iopub.status.idle": "2026-08-11T12:33:40.052467Z", + "shell.execute_reply": "2026-08-11T12:33:40.051582Z" + } + }, "outputs": [], "source": [ "print(max(abs(model_data_no_magnetism - model_data_magnetism)))" @@ -573,9 +818,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.12" + "version": "3.12.12" } }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 1cd19cde..f3ac980d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -194,6 +194,7 @@ nav: - Solvated Material Fitting: tutorials/fitting/material_solvated.ipynb - Advanced Fitting: - Multi-Contrast Fitting: tutorials/advancedfitting/multi_contrast.ipynb + - Polarized Fitting: tutorials/advancedfitting/polarized_fitting.ipynb - API Reference: - API Reference: api-reference/index.md - Model: api-reference/model.md diff --git a/notebooks/polarized_demo_data/fe_on_si_dd.dat b/notebooks/polarized_demo_data/fe_on_si_dd.dat new file mode 100644 index 00000000..94cb89ab --- /dev/null +++ b/notebooks/polarized_demo_data/fe_on_si_dd.dat @@ -0,0 +1,161 @@ +# Qz (1/angstrom) R sR +8.000000000000000167e-03 9.075912642881344139e-01 2.704673059762681755e-02 +9.333333333333334106e-03 8.757161125297344695e-01 2.518846900075938366e-02 +1.066666666666666631e-02 6.977199840304085798e-01 2.087428696703839612e-02 +1.200000000000000025e-02 6.872509831174377082e-01 2.026445554463356807e-02 +1.333333333333333245e-02 6.631355988679124014e-01 1.992801525696327389e-02 +1.466666666666666639e-02 6.501014136246322517e-01 1.960325872581101386e-02 +1.600000000000000033e-02 6.261877501539732105e-01 1.923544318530388472e-02 +1.733333333333333254e-02 6.343907439684939309e-01 1.878917213001779843e-02 +1.866666666666666474e-02 5.922151008251805848e-01 1.823221530598403625e-02 +2.000000000000000042e-02 5.960631236889448736e-01 1.753182597463828069e-02 +2.133333333333333262e-02 5.732409575569683557e-01 1.665496617619377159e-02 +2.266666666666666829e-02 5.247578820550162781e-01 1.557151300442079396e-02 +2.400000000000000050e-02 4.712753742667411538e-01 1.426072465920071378e-02 +2.533333333333333270e-02 4.298037665936796370e-01 1.272086791267182382e-02 +2.666666666666666491e-02 3.626175654261608594e-01 1.098020577277717021e-02 +2.800000000000000058e-02 3.120130601042385710e-01 9.104851279847682097e-03 +2.933333333333333279e-02 2.267220914996334880e-01 7.197086378452600963e-03 +3.066666666666666499e-02 1.775178148014185664e-01 5.379698437054512838e-03 +3.200000000000000067e-02 1.181310173097377125e-01 3.769033640753254900e-03 +3.333333333333333287e-02 7.792836377261480596e-02 2.447631659821120943e-03 +3.466666666666666508e-02 5.027360606248993530e-02 1.448924192475278426e-03 +3.600000000000000422e-02 2.598520932519453666e-02 7.591683953662536856e-04 +3.733333333333332948e-02 1.082782187122047213e-02 3.320006942449972034e-04 +3.866666666666666863e-02 3.390612178886910372e-03 1.065197543374674642e-04 +4.000000000000000083e-02 6.634734704890265719e-04 2.184720411327092837e-05 +4.133333333333333304e-02 8.402822722116700099e-04 2.562630155983095458e-05 +4.266666666666666524e-02 2.761189837369125693e-03 7.722798210359995330e-05 +4.399999999999999745e-02 4.984147983628604318e-03 1.475987877702071994e-04 +4.533333333333332965e-02 7.130190399400800419e-03 2.175578930081850085e-04 +4.666666666666666879e-02 9.315547059324427726e-03 2.756208378691675140e-04 +4.800000000000000100e-02 1.003920070040243209e-02 3.159726242251264801e-04 +4.933333333333333320e-02 1.112594162420929046e-02 3.367822456192096385e-04 +5.066666666666666541e-02 1.132996142293247153e-02 3.388874925461203276e-04 +5.199999999999999761e-02 1.079869901323585180e-02 3.247999349804731625e-04 +5.333333333333332982e-02 1.016770659568656983e-02 2.979622858794108331e-04 +5.466666666666666202e-02 8.830283975467438998e-03 2.621975650898551008e-04 +5.600000000000000117e-02 7.524710540696799392e-03 2.213042152714181170e-04 +5.733333333333333337e-02 5.835794375082871931e-03 1.787655487150832450e-04 +5.866666666666666557e-02 4.708583433713307148e-03 1.375525977593421471e-04 +5.999999999999999778e-02 3.496438325611750386e-03 1.000060435868242991e-04 +6.133333333333332998e-02 2.193770931717122993e-03 6.778600474993069151e-05 +6.266666666666666219e-02 1.358795672471345200e-03 4.187914831486427934e-05 +6.400000000000000133e-02 7.853249876843387258e-04 2.265200387178431808e-05 +6.533333333333332660e-02 3.293824670891950385e-04 9.938524318625599184e-06 +6.666666666666666574e-02 1.094068554305717308e-04 3.149562947669204988e-06 +6.800000000000000488e-02 4.579681042216983595e-05 1.392389778723863959e-06 +6.933333333333333015e-02 1.248796610637395508e-04 3.589694748014370890e-06 +7.066666666666665542e-02 2.874470694658368109e-04 8.589530042765663588e-06 +7.200000000000000844e-02 5.126077964958570014e-04 1.526001661087636688e-05 +7.333333333333333370e-02 7.874764261774640402e-04 2.256505436841721351e-05 +7.466666666666665897e-02 9.766001106681053163e-04 2.961946677919368234e-05 +7.599999999999998423e-02 1.157173229345322303e-03 3.572381091442601147e-05 +7.733333333333333726e-02 1.327900995494995596e-03 4.038042628291313278e-05 +7.866666666666666252e-02 1.462689364936814581e-03 4.329319153718547912e-05 +7.999999999999998779e-02 1.409018005772228778e-03 4.435396905627936038e-05 +8.133333333333334081e-02 1.481769978748375717e-03 4.361892548833955793e-05 +8.266666666666666607e-02 1.353690733781721641e-03 4.127790251118508715e-05 +8.399999999999999134e-02 1.297175308564354644e-03 3.761984328443019373e-05 +8.533333333333334436e-02 1.020887830362373315e-03 3.299671678670470522e-05 +8.666666666666666963e-02 9.044813985473879655e-04 2.779020672111869435e-05 +8.799999999999999489e-02 7.081969582030287706e-04 2.237810772498295098e-05 +8.933333333333332016e-02 5.561639284949144524e-04 1.710899656797894980e-05 +9.066666666666667318e-02 4.124016110169756878e-04 1.228080225182807722e-05 +9.199999999999999845e-02 2.694131913742567504e-04 8.126088081576757726e-06 +9.333333333333332371e-02 1.589095847446595053e-04 4.803802818737303555e-06 +9.466666666666667673e-02 7.952535288150432656e-05 2.397208566565326355e-06 +9.600000000000000200e-02 3.076423342320826502e-05 9.173297799956004235e-07 +9.733333333333332726e-02 1.005251023256088463e-05 3.109844786323962683e-07 +9.866666666666665253e-02 1.607610631621909667e-05 4.722685047958857686e-07 +1.000000000000000056e-01 4.270820470822345427e-05 1.256271490261844000e-06 +1.013333333333333308e-01 8.408706070466543899e-05 2.493805529005849394e-06 +1.026666666666666561e-01 1.357632565588043198e-04 4.006013285794612694e-06 +1.040000000000000091e-01 1.889277283032915380e-04 5.617874673711491538e-06 +1.053333333333333344e-01 2.535854776939441531e-04 7.169830416826538728e-06 +1.066666666666666596e-01 2.834895567475302354e-04 8.526966173941271989e-06 +1.079999999999999849e-01 3.165709134255915796e-04 9.585433172802807031e-06 +1.093333333333333379e-01 3.347902142037292557e-04 1.027600399898480948e-05 +1.106666666666666632e-01 3.412563648594390620e-04 1.056486233606250258e-05 +1.119999999999999885e-01 3.353893732424267221e-04 1.045189380634267694e-05 +1.133333333333333415e-01 3.233706619271815397e-04 9.966875831629191658e-06 +1.146666666666666667e-01 3.048207765620222862e-04 9.164054863718112327e-06 +1.159999999999999920e-01 2.732346614974670118e-04 8.115649185306674919e-06 +1.173333333333333173e-01 2.305140128431734333e-04 6.904826573947082877e-06 +1.186666666666666703e-01 1.829880995447076856e-04 5.618681964024802284e-06 +1.199999999999999956e-01 1.486311764967542036e-04 4.341685740344124731e-06 +1.213333333333333208e-01 1.073289623471351128e-04 3.149994494995711424e-06 +1.226666666666666738e-01 6.989429649780126480e-05 2.106919880718955435e-06 +1.239999999999999991e-01 4.116899314943323328e-05 1.259745030541038548e-06 +1.253333333333333244e-01 2.161552831842888031e-05 6.379694419153882662e-07 +1.266666666666666496e-01 8.479532071820358884e-06 2.529594711760443070e-07 +1.280000000000000027e-01 3.160320019205485887e-06 9.911555885444282819e-08 +1.293333333333333279e-01 5.156191795148754930e-06 1.550018658500189904e-07 +1.306666666666666532e-01 1.302114699187069443e-05 3.875875551631081219e-07 +1.320000000000000062e-01 2.448191181775688001e-05 7.548308766651755472e-07 +1.333333333333333315e-01 4.055071110271158921e-05 1.209632113147993333e-06 +1.346666666666666567e-01 5.430723368147500208e-05 1.703568712487504400e-06 +1.360000000000000098e-01 7.593907561589344907e-05 2.190370009653334060e-06 +1.373333333333333350e-01 9.091137562687272972e-05 2.628918504551843803e-06 +1.386666666666666603e-01 9.876665112112765978e-05 2.985617136047146022e-06 +1.400000000000000133e-01 1.090434830349768235e-04 3.236020124501763433e-06 +1.413333333333333386e-01 1.040784406922520204e-04 3.365684317789212350e-06 +1.426666666666666639e-01 1.084446320665948850e-04 3.370254540572536249e-06 +1.439999999999999891e-01 1.075386877360002690e-04 3.254846793366482747e-06 +1.453333333333333421e-01 9.784288464121572589e-05 3.032834603029489718e-06 +1.466666666666666674e-01 9.275195865909300664e-05 2.724174538521047095e-06 +1.479999999999999927e-01 8.314801822003993546e-05 2.353425867014385280e-06 +1.493333333333333457e-01 6.262927242161476637e-05 1.947626347500982468e-06 +1.506666666666666710e-01 4.985457261801826958e-05 1.534181812229199650e-06 +1.519999999999999962e-01 3.823191202299757656e-05 1.138912706537873486e-06 +1.533333333333333492e-01 2.740965478464539993e-05 7.843779252809585293e-07 +1.546666666666666745e-01 1.568836390366752764e-05 4.885672798968271187e-07 +1.559999999999999998e-01 8.866456616526149662e-06 2.640211742127980506e-07 +1.573333333333333250e-01 4.127225828926081602e-06 1.174020483931366809e-07 +1.586666666666666781e-01 1.568531262286780794e-06 4.950925901345495624e-08 +1.600000000000000033e-01 1.785293006053637639e-06 5.569943511660690089e-08 +1.613333333333333286e-01 4.168008252489919221e-06 1.266497375405589417e-07 +1.626666666666666816e-01 8.182944871264921272e-06 2.493831250965557545e-07 +1.640000000000000069e-01 1.394736579666988838e-05 4.084634364659220295e-07 +1.653333333333333321e-01 1.971738563115660661e-05 5.872640271270070076e-07 +1.666666666666666574e-01 2.427522111972123093e-05 7.692165387495262632e-07 +1.680000000000000104e-01 3.178245932529219920e-05 9.389553589627007831e-07 +1.693333333333333357e-01 3.548210427269819290e-05 1.083231375481271260e-06 +1.706666666666666610e-01 4.124756671870089794e-05 1.191858190928503467e-06 +1.720000000000000140e-01 4.114124414311627793e-05 1.257920990354555467e-06 +1.733333333333333393e-01 4.179113020179383182e-05 1.278144495652170089e-06 +1.746666666666666645e-01 4.243796202477311701e-05 1.252800954782821035e-06 +1.759999999999999898e-01 4.041810378176311592e-05 1.185411655127856387e-06 +1.773333333333333428e-01 3.655984369932937427e-05 1.082246689385805984e-06 +1.786666666666666681e-01 3.011829789651725348e-05 9.516730637963951386e-07 +1.799999999999999933e-01 2.721251947756813565e-05 8.034077531126447617e-07 +1.813333333333333464e-01 2.092119218494401328e-05 6.477344750345810442e-07 +1.826666666666666716e-01 1.660776611688476398e-05 4.947409616157709699e-07 +1.839999999999999969e-01 1.128412102902365682e-05 3.536277928505755650e-07 +1.853333333333333222e-01 7.841308266496928731e-06 2.321310905890118204e-07 +1.866666666666666752e-01 4.426575290143259573e-06 1.360903689794665034e-07 +1.880000000000000004e-01 2.217283777652154650e-06 6.918051302336152221e-08 +1.893333333333333257e-01 1.117228285514961176e-06 3.281414660715856888e-08 +1.906666666666666787e-01 8.799482043854008228e-07 2.620845226422373283e-08 +1.920000000000000040e-01 1.524709408323732203e-06 4.659961472149735373e-08 +1.933333333333333293e-01 3.115966333801529127e-06 8.957912985717266193e-08 +1.946666666666666545e-01 4.918104415699437457e-06 1.495197192619391863e-07 +1.960000000000000075e-01 7.342224987678191212e-06 2.200547862104041078e-07 +1.973333333333333328e-01 9.898358472380025657e-06 2.945743044174480836e-07 +1.986666666666666581e-01 1.199615466869351067e-05 3.667016150254416437e-07 +2.000000000000000111e-01 1.456024485260493697e-05 4.307195176666933625e-07 +2.013333333333333364e-01 1.580691336393115994e-05 4.819198389956592358e-07 +2.026666666666666616e-01 1.701583522311326881e-05 5.168578111597016410e-07 +2.039999999999999869e-01 1.851032070732363719e-05 5.335005051206870393e-07 +2.053333333333333399e-01 1.715605943587205967e-05 5.312666428372068491e-07 +2.066666666666666652e-01 1.579925214859981974e-05 5.109627938210401376e-07 +2.079999999999999905e-01 1.658552037802851100e-05 4.746277493330595449e-07 +2.093333333333333435e-01 1.526098059118070652e-05 4.253023617610078228e-07 +2.106666666666666687e-01 1.207623756549564588e-05 3.667460540832117272e-07 +2.119999999999999940e-01 9.516911054610977533e-06 3.031201720409783612e-07 +2.133333333333333470e-01 7.883129891417216319e-06 2.387174348937093517e-07 +2.146666666666666723e-01 5.874845517182401667e-06 1.777718369021740767e-07 +2.159999999999999976e-01 4.134533427932239587e-06 1.247467744346401067e-07 +2.173333333333333228e-01 2.702962831977833632e-06 8.389098142566779552e-08 +2.186666666666666758e-01 1.919475662810552952e-06 5.660017207259385942e-08 +2.200000000000000011e-01 1.362130841063948462e-06 4.023088338049808278e-08 diff --git a/notebooks/polarized_demo_data/fe_on_si_du.dat b/notebooks/polarized_demo_data/fe_on_si_du.dat new file mode 100644 index 00000000..310d49b0 --- /dev/null +++ b/notebooks/polarized_demo_data/fe_on_si_du.dat @@ -0,0 +1,161 @@ +# Qz (1/angstrom) R sR +8.000000000000000167e-03 1.007528046687608952e-01 2.953269400266327581e-03 +9.333333333333334106e-03 1.572583517379290974e-01 4.744889095853950176e-03 +1.066666666666666631e-02 2.190450883612067301e-01 6.348290158922448474e-03 +1.200000000000000025e-02 1.817726548209979698e-01 5.333047055155122271e-03 +1.333333333333333245e-02 1.700993783671900794e-01 4.838694817206525695e-03 +1.466666666666666639e-02 1.539094991606667073e-01 4.518230715159094954e-03 +1.600000000000000033e-02 1.354415289972342729e-01 4.264358150216949334e-03 +1.733333333333333254e-02 1.342962108592117287e-01 4.036995014532265835e-03 +1.866666666666666474e-02 1.227038410099842297e-01 3.815260616836199312e-03 +2.000000000000000042e-02 1.176659451520821187e-01 3.585730713023881338e-03 +2.133333333333333262e-02 1.163308261281947587e-01 3.338564763918748599e-03 +2.266666666666666829e-02 1.041684027537248536e-01 3.066403995854867417e-03 +2.400000000000000050e-02 9.022380436698591855e-02 2.764683779577930840e-03 +2.533333333333333270e-02 7.862819405015539220e-02 2.432831941156086541e-03 +2.666666666666666491e-02 6.926198949118184123e-02 2.075818194789780333e-03 +2.800000000000000058e-02 5.476494393947105993e-02 1.705182012707162978e-03 +2.933333333333333279e-02 4.371604781302679960e-02 1.338429655907041925e-03 +3.066666666666666499e-02 3.351430919901606659e-02 9.961054459564167105e-04 +3.200000000000000067e-02 2.404199231537985579e-02 6.970987714316889813e-04 +3.333333333333333287e-02 1.541282094899675166e-02 4.540916239051047713e-04 +3.466666666666666508e-02 8.419121830050425859e-03 2.712167368097655575e-04 +3.600000000000000422e-02 4.866438123017920343e-03 1.446721420904415584e-04 +3.733333333333332948e-02 2.184334737089637605e-03 6.538873658233344843e-05 +3.866666666666666863e-02 7.480584982595504936e-04 2.216651973245298166e-05 +4.000000000000000083e-02 1.437357498433520478e-04 4.112664642903217370e-06 +4.133333333333333304e-02 6.269007326684950790e-05 2.004793178707047923e-06 +4.266666666666666524e-02 2.865889630817411481e-04 8.752884696178654547e-06 +4.399999999999999745e-02 6.550303833826847787e-04 1.930862386371530147e-05 +4.533333333333332965e-02 9.631658244066027561e-04 3.033427008241416770e-05 +4.666666666666666879e-02 1.385728562476485660e-03 3.980916777581890430e-05 +4.800000000000000100e-02 1.573018053011269015e-03 4.667475297322238204e-05 +4.933333333333333320e-02 1.727633637372023986e-03 5.054528427436336515e-05 +5.066666666666666541e-02 1.686791702169595239e-03 5.148561259012480502e-05 +5.199999999999999761e-02 1.702067580113456003e-03 4.984516191675345741e-05 +5.333333333333332982e-02 1.587135771138159556e-03 4.613524715474374467e-05 +5.466666666666666202e-02 1.374157060934677209e-03 4.093870012507680326e-05 +5.600000000000000117e-02 1.169622308953436338e-03 3.484364780409830557e-05 +5.733333333333333337e-02 9.542071375089862875e-04 2.839591481744609854e-05 +5.866666666666666557e-02 7.164976311395233151e-04 2.206645809742515836e-05 +5.999999999999999778e-02 5.386531942509326619e-04 1.623143388812568893e-05 +6.133333333333332998e-02 3.703997764791592249e-04 1.116307189423245952e-05 +6.266666666666666219e-02 2.370178599298105031e-04 7.029681693010564393e-06 +6.400000000000000133e-02 1.340037966314343190e-04 3.903043182621346832e-06 +6.533333333333332660e-02 5.716856891883270394e-05 1.771306833428139153e-06 +6.666666666666666574e-02 1.844625167704147395e-05 5.554707159785059965e-07 +6.800000000000000488e-02 4.441357711528022514e-06 1.275710080193205838e-07 +6.933333333333333015e-02 1.071971812064922420e-05 3.289291796014142583e-07 +7.066666666666665542e-02 3.209209197047745474e-05 9.871123536089981223e-07 +7.200000000000000844e-02 6.474504632760995197e-05 1.930634013376320455e-06 +7.333333333333333370e-02 1.025604519999324173e-04 3.000798661515613774e-06 +7.466666666666665897e-02 1.353942129095416121e-04 4.060434544764981782e-06 +7.599999999999998423e-02 1.732954268357779178e-04 4.999537169031312049e-06 +7.733333333333333726e-02 1.961847617845852187e-04 5.738052975700706681e-06 +7.866666666666666252e-02 2.127803051638275087e-04 6.226169716693550195e-06 +7.999999999999998779e-02 2.183218588359995497e-04 6.442557935201045315e-06 +8.133333333333334081e-02 2.279107827402499539e-04 6.391039775753467422e-06 +8.266666666666666607e-02 2.019546328883078804e-04 6.096159940720307629e-06 +8.399999999999999134e-02 1.753877097453329192e-04 5.598109401755303868e-06 +8.533333333333334436e-02 1.728490010012067517e-04 4.947365070275968397e-06 +8.666666666666666963e-02 1.380682774156448791e-04 4.199714530999939949e-06 +8.799999999999999489e-02 1.140730814567600827e-04 3.411152544137300506e-06 +8.933333333333332016e-02 9.124993726152226591e-05 2.634016764123891206e-06 +9.066666666666667318e-02 6.072257135519556961e-05 1.913662668736402492e-06 +9.199999999999999845e-02 4.126117502474644250e-05 1.286128630253563915e-06 +9.333333333333332371e-02 2.464822992743742941e-05 7.767612157382617825e-07 +9.466666666666667673e-02 1.300805868990023634e-05 3.997658169802781140e-07 +9.600000000000000200e-02 5.356127149208341696e-06 1.585921273381317201e-07 +9.733333333333332726e-02 1.591982395813902045e-06 4.702005169047973194e-08 +9.866666666666665253e-02 1.706746190254171456e-06 5.078149721359232735e-08 +1.000000000000000056e-01 4.773339088185987279e-06 1.495380398780969620e-07 +1.013333333333333308e-01 9.897442493022866688e-06 3.190332704228019704e-07 +1.026666666666666561e-01 1.780398721376930520e-05 5.332500947762536672e-07 +1.040000000000000091e-01 2.518591650492778427e-05 7.664249224476511593e-07 +1.053333333333333344e-01 3.361698178729349720e-05 9.947995508378553946e-07 +1.066666666666666596e-01 4.077510951086607538e-05 1.198024554907400912e-06 +1.079999999999999849e-01 4.552677179032260055e-05 1.360162230472498603e-06 +1.093333333333333379e-01 5.012660576403459684e-05 1.470270143505473408e-06 +1.106666666666666632e-01 5.110153064570546782e-05 1.522576166114266844e-06 +1.119999999999999885e-01 5.134643196822854741e-05 1.516281145477262508e-06 +1.133333333333333415e-01 4.747617465830521113e-05 1.455045182847769484e-06 +1.146666666666666667e-01 4.463245108405789895e-05 1.346227467082781511e-06 +1.159999999999999920e-01 4.023471123264161690e-05 1.199957647518279284e-06 +1.173333333333333173e-01 3.511423690670630504e-05 1.028119077817123801e-06 +1.186666666666666703e-01 2.777866275678767680e-05 8.433213933226984112e-07 +1.199999999999999956e-01 2.227397560266068137e-05 6.579324820953016552e-07 +1.213333333333333208e-01 1.597916664254661170e-05 4.832288283145328242e-07 +1.226666666666666738e-01 1.091834395770022714e-05 3.287094353144487512e-07 +1.239999999999999991e-01 6.887338736991865434e-06 2.016031524920293201e-07 +1.253333333333333244e-01 3.340351128440268230e-06 1.065833450927718424e-07 +1.266666666666666496e-01 1.463717126803408988e-06 4.568853130034033272e-08 +1.280000000000000027e-01 5.881997961615234001e-07 1.846714590461363399e-08 +1.293333333333333279e-01 6.857057984571196680e-07 2.211974366523686281e-08 +1.306666666666666532e-01 1.701367205018803992e-06 5.210116722106288193e-08 +1.320000000000000062e-01 3.492999864241714603e-06 1.024858058137199515e-07 +1.333333333333333315e-01 5.503821082625335375e-06 1.665379527543910645e-07 +1.346666666666666567e-01 7.955450596612713394e-06 2.372557130903818383e-07 +1.360000000000000098e-01 1.059807236400316402e-05 3.078816679312774017e-07 +1.373333333333333350e-01 1.290597782263722626e-05 3.723484773852549928e-07 +1.386666666666666603e-01 1.415840904620301583e-05 4.256350973333881385e-07 +1.400000000000000133e-01 1.609534352654533548e-05 4.640176725731832458e-07 +1.413333333333333386e-01 1.621829397454471562e-05 4.852078020329178800e-07 +1.426666666666666639e-01 1.587033743462086622e-05 4.883791582282670671e-07 +1.439999999999999891e-01 1.552122944192228855e-05 4.740908767816654892e-07 +1.453333333333333421e-01 1.414653781401564062e-05 4.441223134538681079e-07 +1.466666666666666674e-01 1.301826040420639851e-05 4.012384147119141744e-07 +1.479999999999999927e-01 1.150534861508465464e-05 3.489079040197437945e-07 +1.493333333333333457e-01 9.933765042361007673e-06 2.909977092754138739e-07 +1.506666666666666710e-01 8.113854683736707309e-06 2.314666173503457221e-07 +1.519999999999999962e-01 5.562031195432281614e-06 1.740792077582149680e-07 +1.533333333333333492e-01 4.119918280027462889e-06 1.221579385885165474e-07 +1.546666666666666745e-01 2.531339545498620342e-06 7.838714440552982691e-08 +1.559999999999999998e-01 1.510475390185554071e-06 4.467800612282128617e-08 +1.573333333333333250e-01 7.337241119796921424e-07 2.209862841490182638e-08 +1.586666666666666781e-01 3.423860559455051005e-07 1.086856102959510645e-08 +1.600000000000000033e-01 3.567575149171467956e-07 1.041274193708904071e-08 +1.613333333333333286e-01 6.370957478418122976e-07 1.946618409280882712e-08 +1.626666666666666816e-01 1.187928772811707295e-06 3.621796661461974208e-08 +1.640000000000000069e-01 1.886817482226232735e-06 5.848134113771266685e-08 +1.653333333333333321e-01 2.740990010100866872e-06 8.387606143231342599e-08 +1.666666666666666574e-01 3.714050285203545527e-06 1.100093500775490443e-07 +1.680000000000000104e-01 4.462623715355420957e-06 1.346431249439114636e-07 +1.693333333333333357e-01 5.245524653001244682e-06 1.558292846488293914e-07 +1.706666666666666610e-01 5.797230945146113261e-06 1.720488808566032506e-07 +1.720000000000000140e-01 6.315639180347299995e-06 1.822485165816315026e-07 +1.733333333333333393e-01 6.132828576201715253e-06 1.858965400689284567e-07 +1.746666666666666645e-01 5.828917138659435816e-06 1.829743260720297752e-07 +1.759999999999999898e-01 5.983528676165092459e-06 1.739369770554233248e-07 +1.773333333333333428e-01 5.268554844138802376e-06 1.596442507501743892e-07 +1.786666666666666681e-01 4.866416974192439597e-06 1.412687952699017713e-07 +1.799999999999999933e-01 4.052403967000443886e-06 1.201897786982718147e-07 +1.813333333333333464e-01 3.249843487696617775e-06 9.788037836311576685e-08 +1.826666666666666716e-01 2.553015230252052794e-06 7.579736806318378599e-08 +1.839999999999999969e-01 1.950528170525877562e-06 5.528027007827940618e-08 +1.853333333333333222e-01 1.326693994938500607e-06 3.746631560777318907e-08 +1.866666666666666752e-01 7.758080648631726058e-07 2.322589876926246457e-08 +1.880000000000000004e-01 4.394834729298315398e-07 1.312144622023900073e-08 +1.893333333333333257e-01 2.543140075460072141e-07 7.390792205372861249e-09 +1.906666666666666787e-01 1.934438757758805036e-07 5.954377832495878448e-09 +1.920000000000000040e-01 2.842791322772257605e-07 8.444000575771938985e-09 +1.933333333333333293e-01 4.746155226669125089e-07 1.424952167638332013e-08 +1.946666666666666545e-01 7.597199115755520296e-07 2.257896566540885911e-08 +1.960000000000000075e-01 1.057121768979611590e-06 3.252685916284303909e-08 +1.973333333333333328e-01 1.369599608430002247e-06 4.314546721810592838e-08 +1.986666666666666581e-01 1.672859432810189042e-06 5.351377826236124305e-08 +2.000000000000000111e-01 2.023149424947413852e-06 6.279962173735473534e-08 +2.013333333333333364e-01 2.311453826628649454e-06 7.031111390500286278e-08 +2.026666666666666616e-01 2.495675246732672360e-06 7.553464103958034328e-08 +2.039999999999999869e-01 2.756666934061640622e-06 7.815772084942974035e-08 +2.053333333333333399e-01 2.688893360609558629e-06 7.807624648935259530e-08 +2.066666666666666652e-01 2.440362129845528739e-06 7.538673102154169124e-08 +2.079999999999999905e-01 2.369971933634048958e-06 7.036516117011379262e-08 +2.093333333333333435e-01 2.088673000388599980e-06 6.343487876882732770e-08 +2.106666666666666687e-01 1.821873819654762058e-06 5.512649403226413222e-08 +2.119999999999999940e-01 1.542950070958765300e-06 4.603257164174993808e-08 +2.133333333333333470e-01 1.248412959336401762e-06 3.676939240707617536e-08 +2.146666666666666723e-01 9.221627784440944102e-07 2.794934492486567134e-08 +2.159999999999999976e-01 6.957246227612103214e-07 2.022620820483283792e-08 +2.173333333333333228e-01 4.582678415428967050e-07 1.423572479592624386e-08 +2.186666666666666758e-01 3.403128237926930089e-07 1.020744354514502879e-08 +2.200000000000000011e-01 2.793881066986614425e-07 7.775682608413154104e-09 diff --git a/notebooks/polarized_demo_data/fe_on_si_ud.dat b/notebooks/polarized_demo_data/fe_on_si_ud.dat new file mode 100644 index 00000000..83bbb25e --- /dev/null +++ b/notebooks/polarized_demo_data/fe_on_si_ud.dat @@ -0,0 +1,161 @@ +# Qz (1/angstrom) R sR +8.000000000000000167e-03 9.559206354062578515e-02 2.953269400266322810e-03 +9.333333333333334106e-03 1.547218526161534269e-01 4.744889095853945839e-03 +1.066666666666666631e-02 2.251218154200637367e-01 6.348290158922438933e-03 +1.200000000000000025e-02 1.733877413610561136e-01 5.333047055155121403e-03 +1.333333333333333245e-02 1.653470206046200042e-01 4.838694817206520490e-03 +1.466666666666666639e-02 1.465280571957182643e-01 4.518230715159088882e-03 +1.600000000000000033e-02 1.461178326438707653e-01 4.264358150216949334e-03 +1.733333333333333254e-02 1.361205456154175597e-01 4.036995014532264968e-03 +1.866666666666666474e-02 1.265777394925009791e-01 3.815260616836197143e-03 +2.000000000000000042e-02 1.193781936588917819e-01 3.585730713023881338e-03 +2.133333333333333262e-02 1.090994410028168771e-01 3.338564763918749900e-03 +2.266666666666666829e-02 1.035813041095600406e-01 3.066403995854868284e-03 +2.400000000000000050e-02 9.089824053785679203e-02 2.764683779577925635e-03 +2.533333333333333270e-02 7.811270518917826089e-02 2.432831941156086541e-03 +2.666666666666666491e-02 6.654117375774777632e-02 2.075818194789778599e-03 +2.800000000000000058e-02 5.713369423648240186e-02 1.705182012707161677e-03 +2.933333333333333279e-02 4.672782443053131313e-02 1.338429655907042142e-03 +3.066666666666666499e-02 3.336288338280176541e-02 9.961054459564173610e-04 +3.200000000000000067e-02 2.315392308301371729e-02 6.970987714316887645e-04 +3.333333333333333287e-02 1.526617871939014472e-02 4.540916239051049339e-04 +3.466666666666666508e-02 9.394767424312444604e-03 2.712167368097654491e-04 +3.600000000000000422e-02 4.854143272757511021e-03 1.446721420904412874e-04 +3.733333333333332948e-02 2.152754540292944711e-03 6.538873658233317738e-05 +3.866666666666666863e-02 7.634065616031009398e-04 2.216651973245304942e-05 +4.000000000000000083e-02 1.388521528749857737e-04 4.112664642903227534e-06 +4.133333333333333304e-02 6.990531242714679162e-05 2.004793178707042841e-06 +4.266666666666666524e-02 2.933666531073274254e-04 8.752884696178646077e-06 +4.399999999999999745e-02 6.199779834909185505e-04 1.930862386371532180e-05 +4.533333333333332965e-02 9.696402254148140049e-04 3.033427008241414060e-05 +4.666666666666666879e-02 1.392694326233942948e-03 3.980916777581890430e-05 +4.800000000000000100e-02 1.636276770833382027e-03 4.667475297322231428e-05 +4.933333333333333320e-02 1.675768959477367138e-03 5.054528427436338548e-05 +5.066666666666666541e-02 1.696458452372038997e-03 5.148561259012481858e-05 +5.199999999999999761e-02 1.734351324604243086e-03 4.984516191675345741e-05 +5.333333333333332982e-02 1.486767745672122306e-03 4.613524715474371079e-05 +5.466666666666666202e-02 1.327994376379723054e-03 4.093870012507678293e-05 +5.600000000000000117e-02 1.183870779060362073e-03 3.484364780409832590e-05 +5.733333333333333337e-02 9.353253204596205170e-04 2.839591481744607821e-05 +5.866666666666666557e-02 7.354355817901460021e-04 2.206645809742515158e-05 +5.999999999999999778e-02 5.383948836689723960e-04 1.623143388812569909e-05 +6.133333333333332998e-02 3.758707654323206531e-04 1.116307189423245105e-05 +6.266666666666666219e-02 2.442168725740734964e-04 7.029681693010561005e-06 +6.400000000000000133e-02 1.304549962240428025e-04 3.903043182621345985e-06 +6.533333333333332660e-02 6.018417429911210201e-05 1.771306833428140000e-06 +6.666666666666666574e-02 1.737687996777799413e-05 5.554707159785055730e-07 +6.800000000000000488e-02 4.246151878334042932e-06 1.275710080193207426e-07 +6.933333333333333015e-02 1.068694294569129868e-05 3.289291796014143113e-07 +7.066666666666665542e-02 3.170063969161577715e-05 9.871123536089979105e-07 +7.200000000000000844e-02 6.265907628402426100e-05 1.930634013376314526e-06 +7.333333333333333370e-02 9.902398487688450469e-05 3.000798661515614621e-06 +7.466666666666665897e-02 1.390667804814529310e-04 4.060434544764975005e-06 +7.599999999999998423e-02 1.600198892746388583e-04 4.999537169031307814e-06 +7.733333333333333726e-02 1.914441976505883657e-04 5.738052975700704140e-06 +7.866666666666666252e-02 2.045244694928812943e-04 6.226169716693544266e-06 +7.999999999999998779e-02 2.126408782791344753e-04 6.442557935201040233e-06 +8.133333333333334081e-02 2.194433243387519748e-04 6.391039775753465728e-06 +8.266666666666666607e-02 2.064857691278901329e-04 6.096159940720307629e-06 +8.399999999999999134e-02 1.940905476443417473e-04 5.598109401755303021e-06 +8.533333333333334436e-02 1.641477730085187799e-04 4.947365070275969244e-06 +8.666666666666666963e-02 1.370677240676891823e-04 4.199714530999940796e-06 +8.799999999999999489e-02 1.129414682318471226e-04 3.411152544137297965e-06 +8.933333333333332016e-02 8.843929941758425740e-05 2.634016764123887818e-06 +9.066666666666667318e-02 6.412665746891291934e-05 1.913662668736399103e-06 +9.199999999999999845e-02 4.147629179578066075e-05 1.286128630253563915e-06 +9.333333333333332371e-02 2.596232947739787901e-05 7.767612157382616766e-07 +9.466666666666667673e-02 1.341676511753195322e-05 3.997658169802778493e-07 +9.600000000000000200e-02 5.685655807735410875e-06 1.585921273381317201e-07 +9.733333333333332726e-02 1.655584386986792729e-06 4.702005169047989737e-08 +9.866666666666665253e-02 1.649387598957539986e-06 5.078149721359380304e-08 +1.000000000000000056e-01 4.941626584690286240e-06 1.495380398780978620e-07 +1.013333333333333308e-01 1.016755565950663672e-05 3.190332704228036644e-07 +1.026666666666666561e-01 1.746000858802738620e-05 5.332500947762522907e-07 +1.040000000000000091e-01 2.578938495532255237e-05 7.664249224476520064e-07 +1.053333333333333344e-01 3.435956766836428144e-05 9.947995508378539123e-07 +1.066666666666666596e-01 3.906069149004365998e-05 1.198024554907398794e-06 +1.079999999999999849e-01 4.444899573477161902e-05 1.360162230472496274e-06 +1.093333333333333379e-01 4.585190983361416169e-05 1.470270143505472138e-06 +1.106666666666666632e-01 5.050486761681483483e-05 1.522576166114266420e-06 +1.119999999999999885e-01 4.893178590785611683e-05 1.516281145477262508e-06 +1.133333333333333415e-01 4.773114780652696064e-05 1.455045182847769273e-06 +1.146666666666666667e-01 4.369379483838258795e-05 1.346227467082781299e-06 +1.159999999999999920e-01 3.988547717775933039e-05 1.199957647518278861e-06 +1.173333333333333173e-01 3.246348183446290359e-05 1.028119077817123801e-06 +1.186666666666666703e-01 2.687352247034604499e-05 8.433213933226984112e-07 +1.199999999999999956e-01 2.333198357392108141e-05 6.579324820953014434e-07 +1.213333333333333208e-01 1.548550790498974200e-05 4.832288283145338830e-07 +1.226666666666666738e-01 1.059645740899342626e-05 3.287094353144484335e-07 +1.239999999999999991e-01 7.090432641232453841e-06 2.016031524920286319e-07 +1.253333333333333244e-01 3.862409946375891984e-06 1.065833450927718027e-07 +1.266666666666666496e-01 1.469423884753160196e-06 4.568853130034043198e-08 +1.280000000000000027e-01 6.087710229395623162e-07 1.846714590461365715e-08 +1.293333333333333279e-01 7.448799100754689694e-07 2.211974366523678340e-08 +1.306666666666666532e-01 1.826772739081254099e-06 5.210116722106263709e-08 +1.320000000000000062e-01 3.315054684218319539e-06 1.024858058137203220e-07 +1.333333333333333315e-01 5.510417021493823192e-06 1.665379527543908528e-07 +1.346666666666666567e-01 8.092951550599856943e-06 2.372557130903813883e-07 +1.360000000000000098e-01 1.039657876854264040e-05 3.078816679312777193e-07 +1.373333333333333350e-01 1.227155477246007717e-05 3.723484773852547281e-07 +1.386666666666666603e-01 1.413087682725235949e-05 4.256350973333876091e-07 +1.400000000000000133e-01 1.482927979940897518e-05 4.640176725731831399e-07 +1.413333333333333386e-01 1.605802964228135723e-05 4.852078020329175624e-07 +1.426666666666666639e-01 1.614920717614244647e-05 4.883791582282668553e-07 +1.439999999999999891e-01 1.591309885259072651e-05 4.740908767816653833e-07 +1.453333333333333421e-01 1.455744390598313682e-05 4.441223134538681608e-07 +1.466666666666666674e-01 1.356381319299208061e-05 4.012384147119141214e-07 +1.479999999999999927e-01 1.198360802068795317e-05 3.489079040197438474e-07 +1.493333333333333457e-01 9.745153220819537010e-06 2.909977092754138739e-07 +1.506666666666666710e-01 7.796973777661658211e-06 2.314666173503456162e-07 +1.519999999999999962e-01 5.811893499401644837e-06 1.740792077582153121e-07 +1.533333333333333492e-01 4.071941595569973699e-06 1.221579385885168385e-07 +1.546666666666666745e-01 2.556343939745368904e-06 7.838714440552985338e-08 +1.559999999999999998e-01 1.503407203321582893e-06 4.467800612282114720e-08 +1.573333333333333250e-01 7.344710467753042064e-07 2.209862841490180983e-08 +1.586666666666666781e-01 3.850350951639976028e-07 1.086856102959512961e-08 +1.600000000000000033e-01 3.634743364778997648e-07 1.041274193708913005e-08 +1.613333333333333286e-01 6.563837631172683734e-07 1.946618409280883705e-08 +1.626666666666666816e-01 1.179629173274889455e-06 3.621796661461978179e-08 +1.640000000000000069e-01 1.884322723145190400e-06 5.848134113771273303e-08 +1.653333333333333321e-01 2.895777093918394305e-06 8.387606143231335982e-08 +1.666666666666666574e-01 3.695883207410113677e-06 1.100093500775489120e-07 +1.680000000000000104e-01 4.552752173050893864e-06 1.346431249439112783e-07 +1.693333333333333357e-01 4.922451901935751810e-06 1.558292846488293385e-07 +1.706666666666666610e-01 5.894527448035536099e-06 1.720488808566031183e-07 +1.720000000000000140e-01 6.157767985265139900e-06 1.822485165816314762e-07 +1.733333333333333393e-01 5.990126113411925469e-06 1.858965400689284567e-07 +1.746666666666666645e-01 6.012867268531592707e-06 1.829743260720296958e-07 +1.759999999999999898e-01 5.843769408504052207e-06 1.739369770554231395e-07 +1.773333333333333428e-01 5.329851047718660423e-06 1.596442507501745216e-07 +1.786666666666666681e-01 4.667685170899017475e-06 1.412687952699019301e-07 +1.799999999999999933e-01 3.993887724572344332e-06 1.201897786982717883e-07 +1.813333333333333464e-01 3.238015637652685508e-06 9.788037836311563450e-08 +1.826666666666666716e-01 2.538142772321931427e-06 7.579736806318370658e-08 +1.839999999999999969e-01 1.924020142961066166e-06 5.528027007827952529e-08 +1.853333333333333222e-01 1.152713951720483779e-06 3.746631560777311627e-08 +1.866666666666666752e-01 7.686955653751071116e-07 2.322589876926246126e-08 +1.880000000000000004e-01 4.396976389188759605e-07 1.312144622023899743e-08 +1.893333333333333257e-01 2.485473702509119916e-07 7.390792205372861249e-09 +1.906666666666666787e-01 1.962647411445939018e-07 5.954377832495861077e-09 +1.920000000000000040e-01 2.666329261163428065e-07 8.444000575771889354e-09 +1.933333333333333293e-01 4.796578346343829695e-07 1.424952167638328705e-08 +1.946666666666666545e-01 7.916339700247115016e-07 2.257896566540885249e-08 +1.960000000000000075e-01 1.034336944868405880e-06 3.252685916284300600e-08 +1.973333333333333328e-01 1.475452503850628252e-06 4.314546721810594161e-08 +1.986666666666666581e-01 1.766211982801583463e-06 5.351377826236124305e-08 +2.000000000000000111e-01 2.089469578857149948e-06 6.279962173735476181e-08 +2.013333333333333364e-01 2.269673329716914896e-06 7.031111390500284955e-08 +2.026666666666666616e-01 2.492558341063638476e-06 7.553464103958034328e-08 +2.039999999999999869e-01 2.706865884171947466e-06 7.815772084942968741e-08 +2.053333333333333399e-01 2.648033083936830869e-06 7.807624648935255560e-08 +2.066666666666666652e-01 2.643484343093922649e-06 7.538673102154163830e-08 +2.079999999999999905e-01 2.428354149668647728e-06 7.036516117011372644e-08 +2.093333333333333435e-01 2.142349369207815779e-06 6.343487876882734094e-08 +2.106666666666666687e-01 1.933686797324920648e-06 5.512649403226417192e-08 +2.119999999999999940e-01 1.554627038754858763e-06 4.603257164174999102e-08 +2.133333333333333470e-01 1.256091035750277766e-06 3.676939240707620845e-08 +2.146666666666666723e-01 9.233558669538192546e-07 2.794934492486571105e-08 +2.159999999999999976e-01 6.755529096878243186e-07 2.022620820483286770e-08 +2.173333333333333228e-01 4.645958262495849456e-07 1.423572479592631334e-08 +2.186666666666666758e-01 3.503492403170606910e-07 1.020744354514503706e-08 +2.200000000000000011e-01 2.500273052907539463e-07 7.775682608413152450e-09 diff --git a/notebooks/polarized_demo_data/fe_on_si_uu.dat b/notebooks/polarized_demo_data/fe_on_si_uu.dat new file mode 100644 index 00000000..3694bfb7 --- /dev/null +++ b/notebooks/polarized_demo_data/fe_on_si_uu.dat @@ -0,0 +1,161 @@ +# Qz (1/angstrom) R sR +8.000000000000000167e-03 9.097992872869360426e-01 2.704673059568740018e-02 +9.333333333333334106e-03 8.063090430971723288e-01 2.496826939277983076e-02 +1.066666666666666631e-02 3.993410666786681995e-01 1.171645321068302131e-02 +1.200000000000000025e-02 2.208085349552587062e-01 6.442469270198266686e-03 +1.333333333333333245e-02 1.378701987041681676e-01 4.393247367307369713e-03 +1.466666666666666639e-02 1.054462848413706411e-01 3.291991459709087391e-03 +1.600000000000000033e-02 8.744568904510718887e-02 2.613347927772132363e-03 +1.733333333333333254e-02 7.146167917255358271e-02 2.164384492054474089e-03 +1.866666666666666474e-02 6.167976374855279470e-02 1.851326045070834578e-03 +2.000000000000000042e-02 5.265218748124480402e-02 1.621050441499697877e-03 +2.133333333333333262e-02 4.927121530985689724e-02 1.440142702025466563e-03 +2.266666666666666829e-02 4.386922365138993390e-02 1.286067912029972274e-03 +2.400000000000000050e-02 3.818426600138049437e-02 1.143263265913084135e-03 +2.533333333333333270e-02 3.451548358949688838e-02 1.001593387513934411e-03 +2.666666666666666491e-02 2.893314831493903547e-02 8.559889643176760762e-04 +2.800000000000000058e-02 2.294049681346108441e-02 7.064256926010059525e-04 +2.933333333333333279e-02 1.878720553741572083e-02 5.574493693613232197e-04 +3.066666666666666499e-02 1.348984789683120925e-02 4.166819078504779708e-04 +3.200000000000000067e-02 1.000274871208850849e-02 2.923772937100064802e-04 +3.333333333333333287e-02 6.352269739014006905e-03 1.908539488883274199e-04 +3.466666666666666508e-02 3.807788233433779135e-03 1.148707051044449136e-04 +3.600000000000000422e-02 2.072462443473301660e-03 6.347044024293114052e-05 +3.733333333333332948e-02 1.139804540655666408e-03 3.298439271106011055e-05 +3.866666666666666863e-02 6.122598816790504953e-04 1.845334402119248518e-05 +4.000000000000000083e-02 4.883437676771125490e-04 1.484101765341856499e-05 +4.133333333333333304e-02 5.863780344992955938e-04 1.777916019914761169e-05 +4.266666666666666524e-02 8.085021003458838429e-04 2.387381547227360713e-05 +4.399999999999999745e-02 1.035202554519308550e-03 3.071929115713385912e-05 +4.533333333333332965e-02 1.240744044437433122e-03 3.676707224072342880e-05 +4.666666666666666879e-02 1.389209491691400242e-03 4.114450721474634341e-05 +4.800000000000000100e-02 1.542409592178596968e-03 4.347879975014890382e-05 +4.933333333333333320e-02 1.440437539146308685e-03 4.374650324943592949e-05 +5.066666666666666541e-02 1.383520769361035446e-03 4.215340635820877485e-05 +5.199999999999999761e-02 1.269632015624745126e-03 3.904210241469862578e-05 +5.333333333333332982e-02 1.182193676818206300e-03 3.482231541233326162e-05 +5.466666666666666202e-02 1.031084451500815938e-03 2.991919527059426961e-05 +5.600000000000000117e-02 8.217067419050427812e-04 2.473575956461779602e-05 +5.733333333333333337e-02 6.377339302697241285e-04 1.962670295639886505e-05 +5.866666666666666557e-02 4.837841769535204824e-04 1.488161363565887598e-05 +5.999999999999999778e-02 3.641768060699149896e-04 1.071614871017455281e-05 +6.133333333333332998e-02 2.477354188367224784e-04 7.269959728324917905e-06 +6.266666666666666219e-02 1.561776233511654095e-04 4.610207090761423770e-06 +6.400000000000000133e-02 8.949197401578911281e-05 2.739453201419914626e-06 +6.533333333333332660e-02 5.392847547534247797e-05 1.606664106320856537e-06 +6.666666666666666574e-02 3.746532653419778743e-05 1.120039016259490813e-06 +6.800000000000000488e-02 3.893568137509112090e-05 1.160457079345345069e-06 +6.933333333333333015e-02 5.453891258501781982e-05 1.594483025713826729e-06 +7.066666666666665542e-02 7.671411705147658197e-05 2.286088733596215601e-06 +7.200000000000000844e-02 1.056589203629337842e-04 3.106496336003633004e-06 +7.333333333333333370e-02 1.316596805769652617e-04 3.941798924206651489e-06 +7.466666666666665897e-02 1.579664076472516462e-04 4.698241645434700220e-06 +7.599999999999998423e-02 1.801902185616797894e-04 5.305232630010590067e-06 +7.733333333333333726e-02 1.822135625501344306e-04 5.716292754145070896e-06 +7.866666666666666252e-02 1.950528509916157188e-04 5.908246419330654096e-06 +7.999999999999998779e-02 1.932016051436307032e-04 5.879007890693535743e-06 +8.133333333333334081e-02 1.845385281838792435e-04 5.644337103825948520e-06 +8.266666666666666607e-02 1.730243643665857608e-04 5.233933216032450874e-06 +8.399999999999999134e-02 1.632474535192117589e-04 4.687210468655069082e-06 +8.533333333333334436e-02 1.314622827741316344e-04 4.049042081922340294e-06 +8.666666666666666963e-02 1.154554884671698140e-04 3.365891070005132427e-06 +8.799999999999999489e-02 8.488985829504271097e-05 2.682104738513855658e-06 +8.933333333333332016e-02 6.722305804620891541e-05 2.037158154475688395e-06 +9.066666666666667318e-02 4.901979405596596531e-05 1.463448400295844109e-06 +9.199999999999999845e-02 3.340711008125167076e-05 9.848923261699929418e-07 +9.333333333333332371e-02 2.098114612862189604e-05 6.162848391097110831e-07 +9.466666666666667673e-02 1.240039510890430205e-05 3.633636469272716737e-07 +9.600000000000000200e-02 7.371654825392529470e-06 2.234877178786924464e-07 +9.733333333333332726e-02 6.140559884840828304e-06 1.868079259283322616e-07 +9.866666666666665253e-02 8.130369333425144199e-06 2.377905239719707620e-07 +1.000000000000000056e-01 1.182996255883061676e-05 3.569474444613453580e-07 +1.013333333333333308e-01 1.675432993250500145e-05 5.226313062615320515e-07 +1.026666666666666561e-01 2.295109736382246668e-05 7.127659779979260574e-07 +1.040000000000000091e-01 2.938005921282587575e-05 9.064036228776255802e-07 +1.053333333333333344e-01 3.670689922491348158e-05 1.085024036035259375e-06 +1.066666666666666596e-01 4.129299961414677103e-05 1.233519440994174777e-06 +1.079999999999999849e-01 4.562034593524948124e-05 1.340835558594794967e-06 +1.093333333333333379e-01 4.607726060783903278e-05 1.400265836765147427e-06 +1.106666666666666632e-01 4.720407447129864551e-05 1.409418769648823861e-06 +1.119999999999999885e-01 4.652023459077186561e-05 1.369897201817758128e-06 +1.133333333333333415e-01 4.249337645689370844e-05 1.286742776469133279e-06 +1.146666666666666667e-01 3.945697934704982322e-05 1.167707977743549753e-06 +1.159999999999999920e-01 3.340398457858895855e-05 1.022422578174446118e-06 +1.173333333333333173e-01 2.840458916012842668e-05 8.615210305247138080e-07 +1.186666666666666703e-01 2.292748762504616899e-05 6.957929447530528394e-07 +1.199999999999999956e-01 1.720676560194368978e-05 5.354109368057387761e-07 +1.213333333333333208e-01 1.316555588048205136e-05 3.892796225096771491e-07 +1.226666666666666738e-01 8.693733480536175017e-06 2.645372363504957658e-07 +1.239999999999999991e-01 5.543016807589842190e-06 1.662281979824507759e-07 +1.253333333333333244e-01 3.285099840968646500e-06 9.715183266031837250e-08 +1.266666666666666496e-01 1.955185778289120519e-06 5.788021375121773621e-08 +1.280000000000000027e-01 1.596394802918315076e-06 4.695455817334131952e-08 +1.293333333333333279e-01 2.029656599321841208e-06 6.107013363064531437e-08 +1.306666666666666532e-01 3.147121394412769539e-06 9.562801718007675268e-08 +1.320000000000000062e-01 4.824223022243863525e-06 1.450736410006232978e-07 +1.333333333333333315e-01 6.436506611609275528e-06 2.033908498851756146e-07 +1.346666666666666567e-01 8.435753307531895936e-06 2.645579526131113161e-07 +1.360000000000000098e-01 1.033821735151448561e-05 3.229619707509598506e-07 +1.373333333333333350e-01 1.208550851253621602e-05 3.737467893770618719e-07 +1.386666666666666603e-01 1.393439253998442856e-05 4.130776423481388130e-07 +1.400000000000000133e-01 1.421350564839384544e-05 4.383116294576547520e-07 +1.413333333333333386e-01 1.476626395892329756e-05 4.480712316077902185e-07 +1.426666666666666639e-01 1.531537018309152517e-05 4.422245846616717551e-07 +1.439999999999999891e-01 1.390914065522243100e-05 4.217821936334787589e-07 +1.453333333333333421e-01 1.324417411875621121e-05 3.887245121754610337e-07 +1.466666666666666674e-01 1.120311462801779919e-05 3.457781774349806621e-07 +1.479999999999999927e-01 9.811176893421107628e-06 2.961605820037383467e-07 +1.493333333333333457e-01 7.879277007894731297e-06 2.433128885477734744e-07 +1.506666666666666710e-01 6.290054724570724669e-06 1.906406462781018071e-07 +1.519999999999999962e-01 4.828018842308375617e-06 1.412790277686688167e-07 +1.533333333333333492e-01 3.094121452750714761e-06 9.789660788748038203e-08 +1.546666666666666745e-01 2.112100091210456856e-06 6.254783498520832551e-08 +1.559999999999999998e-01 1.228036474086856133e-06 3.658020173382685675e-08 +1.573333333333333250e-01 6.743588996379143178e-07 2.059791448856219756e-08 +1.586666666666666781e-01 4.617236887443765433e-07 1.447987267050306567e-08 +1.600000000000000033e-01 5.828001903164824309e-07 1.744625401808121305e-08 +1.613333333333333286e-01 9.239253352866243629e-07 2.816515742376424879e-08 +1.626666666666666816e-01 1.506735250009409562e-06 4.488872137164296450e-08 +1.640000000000000069e-01 2.188333174891102094e-06 6.560698564480311472e-08 +1.653333333333333321e-01 3.081539047557577154e-06 8.820750389297125160e-08 +1.666666666666666574e-01 3.661163273165275461e-06 1.106292904922382127e-07 +1.680000000000000104e-01 4.232620483364079970e-06 1.310009900462210656e-07 +1.693333333333333357e-01 4.951403813595259923e-06 1.477474886625623296e-07 +1.706666666666666610e-01 5.358535328271501864e-06 1.597020422565462717e-07 +1.720000000000000140e-01 5.763097368670237389e-06 1.661193023089299000e-07 +1.733333333333333393e-01 5.696373066655557468e-06 1.667144389158185545e-07 +1.746666666666666645e-01 5.445754992892647173e-06 1.616420882892517069e-07 +1.759999999999999898e-01 5.269992715381009275e-06 1.514512115875856919e-07 +1.773333333333333428e-01 4.404327904642388854e-06 1.370162321789783880e-07 +1.786666666666666681e-01 3.905281055242455666e-06 1.194510004765750695e-07 +1.799999999999999933e-01 3.241090765491239341e-06 1.000128067826545866e-07 +1.813333333333333464e-01 2.635606843178464082e-06 8.000379318150786771e-08 +1.826666666666666716e-01 1.939025085671843059e-06 6.067673728096377354e-08 +1.839999999999999969e-01 1.465786188839188880e-06 4.315135690925841093e-08 +1.853333333333333222e-01 9.385709943918117380e-07 2.834610426400807751e-08 +1.866666666666666752e-01 5.394002925749825609e-07 1.692898657779461566e-08 +1.880000000000000004e-01 3.002120132500959102e-07 9.289383504435447816e-09 +1.893333333333333257e-01 1.861089710946391928e-07 5.531245471371540921e-09 +1.906666666666666787e-01 1.874838176711437345e-07 5.486561540334066774e-09 +1.920000000000000040e-01 3.090206001193436630e-07 8.746675300421817014e-09 +1.933333333333333293e-01 5.331229924230900979e-07 1.470797893923666382e-08 +1.946666666666666545e-01 7.636365505684721927e-07 2.262778153340758893e-08 +1.960000000000000075e-01 1.024837574750060220e-06 3.168575511907128065e-08 +1.973333333333333328e-01 1.280697801903912735e-06 4.104631324165549513e-08 +1.986666666666666581e-01 1.677281826504772928e-06 4.991754978763511508e-08 +2.000000000000000111e-01 1.873269516152690271e-06 5.760291888369212628e-08 +2.013333333333333364e-01 2.091694161306209310e-06 6.354261138831364574e-08 +2.026666666666666616e-01 2.203530231180347108e-06 6.734251099808780579e-08 +2.039999999999999869e-01 2.283302344461021660e-06 6.878961887636610838e-08 +2.053333333333333399e-01 2.334125714454877571e-06 6.785384569096191238e-08 +2.066666666666666652e-01 2.166057926540760182e-06 6.467701482049679655e-08 +2.079999999999999905e-01 1.975577859553661420e-06 5.955074044679917747e-08 +2.093333333333333435e-01 1.708078682226702826e-06 5.288549222147748138e-08 +2.106666666666666687e-01 1.430135275616249324e-06 4.517360209801992675e-08 +2.119999999999999940e-01 1.213667744507251196e-06 3.694909139659465115e-08 +2.133333333333333470e-01 9.568654434433844830e-07 2.875235455239735603e-08 +2.146666666666666723e-01 7.408982845384479317e-07 2.110745357871421160e-08 +2.159999999999999976e-01 4.869702574935956964e-07 1.455223416454345105e-08 +2.173333333333333228e-01 3.285896769109876361e-07 9.575386973950528065e-09 +2.186666666666666758e-01 2.068206441979765097e-07 6.298970777474893842e-09 +2.200000000000000011e-01 1.401986602342771183e-07 4.360985483968211197e-09 diff --git a/notebooks/polarized_fitting.ipynb b/notebooks/polarized_fitting.ipynb new file mode 100644 index 00000000..83c4cedc --- /dev/null +++ b/notebooks/polarized_fitting.ipynb @@ -0,0 +1,507 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "intro-title", + "metadata": {}, + "source": [ + "# Polarized Neutron Reflectometry — Magnetic Models and Simultaneous Multi-Channel Fitting\n", + "\n", + "This notebook demonstrates the polarized (PNR) functionality of `easyreflectometry`:\n", + "\n", + "| Feature | API |\n", + "|---|---|\n", + "| Magnetic layers as first-class model parameters | `LayerMagnetism(rho_m, theta_m)` on `Layer` |\n", + "| All four spin channels in one calculation | `interface.polarized_reflectivity_profiles(q, model_id)` |\n", + "| Nuclear + magnetic SLD profile | `interface.magnetic_sld_profile(model_id)` |\n", + "| Per-file spin-channel detection | `detect_polarization_channel(path)` |\n", + "| One experiment = one dataset per channel | `PolarizedDataSet` |\n", + "| Simultaneous fit of all measured channels | `MultiFitter.fit_polarized(data)` |\n", + "\n", + "The magnetic parameters `rho_m` (magnetic SLD, in 10⁻⁶ Å⁻²) and `theta_m` (in-plane moment\n", + "angle, degrees) are ordinary `easyscience` `Parameter`s: they can be fixed or freed, bounded,\n", + "serialized, and fitted: together with the structural parameters, against **all measured spin\n", + "channels at once**. The refl1d backend computes the four spin cross-sections in a single kernel\n", + "evaluation and caches them per iteration, so an N-channel fit costs about as much as a\n", + "single-channel one.\n", + "\n", + "**Convention** (refl1d): with the default guide field (`Aguide = 270°`), a moment at\n", + "`theta_m = 270°` is *aligned* with the field: the non-spin-flip channels see\n", + "ρ ± ρ_M and the spin-flip channels vanish. Any other angle cants the moment and\n", + "produces spin-flip scattering. Polarized calculations require the **refl1d** calculator." + ] + }, + { + "cell_type": "markdown", + "id": "imports-md", + "metadata": {}, + "source": [ + "## 1. Imports" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "imports-code", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from easyreflectometry.calculators import CalculatorFactory\n", + "from easyreflectometry.data import PolarizedDataSet\n", + "from easyreflectometry.data import detect_polarization_channel\n", + "from easyreflectometry.data import load_as_dataset\n", + "from easyreflectometry.fitting import MultiFitter\n", + "from easyreflectometry.model import Model\n", + "from easyreflectometry.model import PercentageFwhm\n", + "from easyreflectometry.sample import Layer\n", + "from easyreflectometry.sample import LayerMagnetism\n", + "from easyreflectometry.sample import Material\n", + "from easyreflectometry.sample import Multilayer\n", + "from easyreflectometry.sample import Sample\n", + "\n", + "%matplotlib inline\n", + "\n", + "rng = np.random.default_rng(42)\n", + "\n", + "CHANNEL_LABELS = {'pp': 'R++ (up-up)', 'pm': 'R+- (up-down)', 'mp': 'R-+ (down-up)', 'mm': 'R-- (down-down)'}\n", + "CHANNEL_COLORS = {'pp': 'C0', 'pm': 'C2', 'mp': 'C3', 'mm': 'C1'}" + ] + }, + { + "cell_type": "markdown", + "id": "sample-md", + "metadata": {}, + "source": [ + "## 2. Build a Magnetic Sample\n", + "\n", + "A single ferromagnetic iron film on silicon, measured in vacuum:\n", + "\n", + "- **Vacuum** superphase\n", + "- **Fe film**, 200 Å: nuclear SLD 8.02·10⁻⁶ Å⁻², magnetic SLD `rho_m = 5.0`·10⁻⁶ Å⁻²\n", + " (bulk Fe), moment canted at `theta_m = 40°` so that all four channels are non-trivial\n", + "- **Si** substrate\n", + "\n", + "Attaching a `LayerMagnetism` to a layer is all that is needed: when the model is given a\n", + "calculator interface, magnetism is switched on automatically (and removing the last magnetic\n", + "layer switches it off again)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "sample-code", + "metadata": {}, + "outputs": [], + "source": [ + "TRUTH = {'thickness': 200.0, 'rho_m': 5.0, 'theta_m': 40.0}\n", + "\n", + "\n", + "def build_model(thickness=TRUTH['thickness'], rho_m=TRUTH['rho_m'], theta_m=TRUTH['theta_m'], name='PNR Model'):\n", + " \"\"\"Vacuum | Fe film (magnetic) | Si substrate, with a fresh refl1d calculator.\"\"\"\n", + " vacuum = Material(0.0, 0.0, 'Vacuum')\n", + " iron = Material(8.02, 0.0, 'Fe')\n", + " silicon = Material(2.07, 0.0, 'Si')\n", + "\n", + " superphase = Layer(vacuum, 0, 0, 'Vacuum superphase')\n", + " film = Layer(\n", + " iron, thickness, 5, 'Fe film',\n", + " magnetism=LayerMagnetism(rho_m=rho_m, theta_m=theta_m, name='Fe moment'),\n", + " )\n", + " substrate = Layer(silicon, 0, 3, 'Si substrate')\n", + "\n", + " sample = Sample(Multilayer(superphase), Multilayer(film), Multilayer(substrate), name='Fe on Si')\n", + " model = Model(sample, 1.0, 0.0, PercentageFwhm(2.0), name)\n", + "\n", + " interface = CalculatorFactory()\n", + " interface.switch('refl1d') # magnetism requires the refl1d backend\n", + " model.interface = interface\n", + " return model\n", + "\n", + "\n", + "truth_model = build_model()\n", + "print(truth_model)\n", + "print(f'has_magnetism : {truth_model.has_magnetism}')\n", + "print(f'calculator magnetism flag: {truth_model.interface().include_magnetism} (enabled automatically)')" + ] + }, + { + "cell_type": "markdown", + "id": "simulate-md", + "metadata": {}, + "source": [ + "## 3. Simulate All Four Spin Channels\n", + "\n", + "`polarized_reflectivity_profiles` returns a dictionary keyed `'pp'`, `'pm'`, `'mp'`, `'mm'`.\n", + "All four cross-sections come from one refl1d kernel evaluation.\n", + "\n", + "With the moment canted at 40° the non-spin-flip channels split (they see the moment's\n", + "projection on the field) and the spin-flip channels pick up the perpendicular component.\n", + "For a non-chiral, non-absorptive sample `pm` and `mp` coincide by symmetry." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "simulate-code", + "metadata": {}, + "outputs": [], + "source": [ + "q = np.linspace(0.008, 0.22, 160)\n", + "channels_truth = truth_model.interface.polarized_reflectivity_profiles(q, truth_model.unique_name)\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 6))\n", + "for channel, reflectivity in channels_truth.items():\n", + " ax.plot(q, reflectivity, color=CHANNEL_COLORS[channel], lw=1.5, label=CHANNEL_LABELS[channel])\n", + "ax.set_yscale('log')\n", + "ax.set_xlabel('Q (Å⁻¹)')\n", + "ax.set_ylabel('Reflectivity')\n", + "ax.set_title('Simulated spin channels — Fe film, moment canted 40°')\n", + "ax.legend()\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "sld-md", + "metadata": {}, + "source": [ + "## 4. Nuclear and Magnetic SLD Profile\n", + "\n", + "`magnetic_sld_profile` returns `z`, nuclear ρ(z), magnetic ρ_M(z) and the moment angle θ_M(z).\n", + "The most intuitive PNR view adds the **spin-dependent potentials**: what each neutron spin\n", + "state actually \"sees\":\n", + "\n", + "$$\\rho_\\pm(z) = \\rho(z) \\pm \\rho_M(z)\\,\\cos\\bigl(\\theta_M(z) - A_\\text{guide}\\bigr)$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "sld-code", + "metadata": {}, + "outputs": [], + "source": [ + "z, sld, rho_m_profile, theta_m_profile = truth_model.interface.magnetic_sld_profile(truth_model.unique_name)\n", + "\n", + "AGUIDE = 270.0 # refl1d default guide-field angle\n", + "projection = rho_m_profile * np.cos(np.radians(theta_m_profile - AGUIDE))\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 6))\n", + "ax.plot(z, sld, 'k-', lw=2, label='nuclear ρ(z)')\n", + "ax.plot(z, rho_m_profile, 'C4-', lw=2, label='magnetic ρ$_M$(z)')\n", + "ax.plot(z, sld + projection, 'C0--', lw=1.5, label='spin-up potential ρ + ρ$_M$cos(θ$_M$−A)')\n", + "ax.plot(z, sld - projection, 'C1--', lw=1.5, label='spin-down potential ρ − ρ$_M$cos(θ$_M$−A)')\n", + "ax.set_xlabel('z (Å)')\n", + "ax.set_ylabel('SLD (10⁻⁶ Å⁻²)')\n", + "ax.set_title('Nuclear and magnetic SLD profile')\n", + "ax.legend(loc='upper right', fontsize=9)\n", + "\n", + "ax2 = ax.twinx()\n", + "ax2.plot(z, theta_m_profile, 'C7:', lw=1.5)\n", + "ax2.set_ylabel('θ$_M$ (deg)', color='C7')\n", + "ax2.tick_params(axis='y', colors='C7')\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "sa-md", + "metadata": {}, + "source": [ + "## 5. Spin Asymmetry\n", + "\n", + "The spin asymmetry\n", + "\n", + "$$SA = \\frac{R^{++} - R^{--}}{R^{++} + R^{--}}$$\n", + "\n", + "removes most of the structural (nuclear) contribution and is visually far more sensitive to\n", + "weak magnetism than the raw reflectivities: the standard first look at any PNR measurement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "sa-code", + "metadata": {}, + "outputs": [], + "source": [ + "spin_asymmetry = (channels_truth['pp'] - channels_truth['mm']) / (channels_truth['pp'] + channels_truth['mm'])\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 4))\n", + "ax.plot(q, spin_asymmetry, 'C5-', lw=1.5)\n", + "ax.axhline(0, color='k', lw=0.5)\n", + "ax.set_xlabel('Q (Å⁻¹)')\n", + "ax.set_ylabel('(R⁺⁺ − R⁻⁻) / (R⁺⁺ + R⁻⁻)')\n", + "ax.set_title('Spin asymmetry')\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "files-md", + "metadata": {}, + "source": [ + "## 6. A Synthetic Experiment: One File per Channel\n", + "\n", + "Polarized measurements typically arrive as **one file per spin channel**. We simulate that:\n", + "3% relative noise on each channel, written to four separate files whose names carry the\n", + "conventional channel suffixes (`_uu`, `_dd`, `_ud`, `_du`).\n", + "\n", + "`detect_polarization_channel` identifies the channel of each file: from the ORSO header\n", + "(`instrument_settings.polarization`) when present, otherwise from filename tokens. Only the\n", + "four fully-analysed cross-sections `pp`/`pm`/`mp`/`mm` are ever assigned; partially-analysed\n", + "observables (`po`, `mo`, …) measure *channel sums* and are left for the user to decide.\n", + "(For GUI workflows, `Project.suggest_polarized_channel_assignment(paths)` wraps this per-file\n", + "and `Project.load_polarized_experiment({channel: path})` performs the load.)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "files-code", + "metadata": {}, + "outputs": [], + "source": [ + "DATA_DIR = 'polarized_demo_data'\n", + "os.makedirs(DATA_DIR, exist_ok=True)\n", + "\n", + "FILE_SUFFIX = {'pp': 'uu', 'pm': 'ud', 'mp': 'du', 'mm': 'dd'}\n", + "NOISE = 0.03\n", + "\n", + "file_paths = []\n", + "for channel, reflectivity in channels_truth.items():\n", + " sigma = NOISE * reflectivity\n", + " noisy = np.clip(reflectivity + sigma * rng.standard_normal(len(q)), 1e-12, None)\n", + " path = os.path.join(DATA_DIR, f'fe_on_si_{FILE_SUFFIX[channel]}.dat')\n", + " np.savetxt(path, np.column_stack([q, noisy, sigma]), header='Qz (1/angstrom) R sR')\n", + " file_paths.append(path)\n", + "\n", + "print('Automatic channel detection:')\n", + "for path in file_paths:\n", + " detected = detect_polarization_channel(path)\n", + " print(f' {os.path.basename(path):24s} -> {detected.value if detected else \"(user must assign)\"}')" + ] + }, + { + "cell_type": "markdown", + "id": "dataset-md", + "metadata": {}, + "source": [ + "## 7. Group the Channels into a `PolarizedDataSet`\n", + "\n", + "A `PolarizedDataSet` holds one `DataSet1D` per measured channel (any subset of the four -\n", + "an NSF-only experiment would just have `pp` and `mm`) and one **shared model**. Channels are\n", + "kept in canonical order (pp, pm, mp, mm); the `channels` mapping is read-only, with validated\n", + "`set_channel` / `remove_channel` methods for editing.\n", + "\n", + "> **Note** — `DataSet1D.ye` stores **variances** (σ²), following the scipp convention. The\n", + "> text-file loader squares the error column for you.\n", + "\n", + "We start the fit model from deliberately wrong values: thickness 180 Å (truth 200),\n", + "`rho_m` 3.0 (truth 5.0), `theta_m` 60° (truth 40°)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dataset-code", + "metadata": {}, + "outputs": [], + "source": [ + "fit_model = build_model(thickness=180.0, rho_m=3.0, theta_m=60.0, name='PNR Fit Model')\n", + "\n", + "data = PolarizedDataSet(\n", + " name='Fe on Si (synthetic PNR)',\n", + " channels={detect_polarization_channel(path): load_as_dataset(path) for path in file_paths},\n", + " model=fit_model,\n", + ")\n", + "print(data)\n", + "print(f'channels: {[channel.value for channel in data.available_channels]}')\n", + "print(f\"points per channel: {len(data['pp'].x)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "fit-md", + "metadata": {}, + "source": [ + "## 8. Simultaneous Multi-Channel Fit\n", + "\n", + "`MultiFitter.fit_polarized(data)` fits **all measured channels at once** against the one\n", + "shared model:\n", + "\n", + "- structural parameters (thickness, roughness, nuclear SLD, scale, background) are common to\n", + " every channel automatically;\n", + "- the magnetic parameters shape the channels through the spin-dependent kernel: the\n", + " non-spin-flip splitting pins `rho_m·cos θ_m` while the spin-flip channels pin the\n", + " perpendicular component, so `rho_m` and `theta_m` are individually well-determined;\n", + "- each iteration costs a single refl1d kernel evaluation thanks to the four-channel cache.\n", + "\n", + "It returns one `FitResults` per channel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fit-code", + "metadata": {}, + "outputs": [], + "source": [ + "film_layer = fit_model.sample[1].layers[0]\n", + "\n", + "film_layer.thickness.fixed = False\n", + "film_layer.thickness.bounds = (150, 250)\n", + "film_layer.magnetism.rho_m.fixed = False\n", + "film_layer.magnetism.rho_m.bounds = (0, 8)\n", + "film_layer.magnetism.theta_m.fixed = False\n", + "film_layer.magnetism.theta_m.bounds = (0, 90)\n", + "\n", + "print('Free parameters (start values):')\n", + "for parameter in fit_model.get_fit_parameters():\n", + " print(f' {parameter.name:12s} = {float(parameter.value):8.3f} bounds={parameter.bounds}')\n", + "\n", + "fitter = MultiFitter(fit_model)\n", + "results = fitter.fit_polarized(data)\n", + "\n", + "print(f'\\nsuccess: {all(result.success for result in results.values())}')\n", + "print(f'reduced chi² (all channels): {fitter.reduced_chi:.3f}')\n", + "\n", + "print(f'\\n{\"Parameter\":12s} {\"truth\":>10s} {\"start\":>10s} {\"fitted\":>10s}')\n", + "print('-' * 46)\n", + "starts = {'thickness': 180.0, 'rho_m': 3.0, 'theta_m': 60.0}\n", + "fitted = {\n", + " 'thickness': float(film_layer.thickness.value),\n", + " 'rho_m': float(film_layer.magnetism.rho_m.value),\n", + " 'theta_m': float(film_layer.magnetism.theta_m.value),\n", + "}\n", + "for key in TRUTH:\n", + " print(f'{key:12s} {TRUTH[key]:>10.3f} {starts[key]:>10.3f} {fitted[key]:>10.3f}')" + ] + }, + { + "cell_type": "markdown", + "id": "plots-md", + "metadata": {}, + "source": [ + "## 9. Fitted Curves per Channel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "plots-code", + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 2, figsize=(12, 8), sharex=True)\n", + "\n", + "for ax, channel in zip(axes.flat, data.available_channels):\n", + " dataset = data[channel]\n", + " fitted_curve = fit_model.interface.reflectivity_profile_channel(dataset.x, fit_model.unique_name, channel)\n", + " ax.errorbar(\n", + " dataset.x, dataset.y, yerr=np.sqrt(dataset.ye), # ye holds variances\n", + " fmt='o', ms=2.5, alpha=0.45, color=CHANNEL_COLORS[channel.value], label='synthetic data',\n", + " )\n", + " ax.plot(dataset.x, fitted_curve, 'k-', lw=1.5, label='fit')\n", + " ax.set_yscale('log')\n", + " ax.set_title(CHANNEL_LABELS[channel.value])\n", + " ax.legend(fontsize=9)\n", + "\n", + "for ax in axes[1]:\n", + " ax.set_xlabel('Q (Å⁻¹)')\n", + "for ax in axes[:, 0]:\n", + " ax.set_ylabel('Reflectivity')\n", + "\n", + "fig.suptitle('Simultaneous four-channel fit — all channels share one model', y=1.0)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "sa-fit-md", + "metadata": {}, + "source": [ + "### Spin asymmetry: data vs fit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "sa-fit-code", + "metadata": {}, + "outputs": [], + "source": [ + "fitted_channels = fit_model.interface.polarized_reflectivity_profiles(q, fit_model.unique_name)\n", + "\n", + "sa_data = (data['pp'].y - data['mm'].y) / (data['pp'].y + data['mm'].y)\n", + "sa_fit = (fitted_channels['pp'] - fitted_channels['mm']) / (fitted_channels['pp'] + fitted_channels['mm'])\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 4))\n", + "ax.plot(q, sa_data, 'o', ms=3, alpha=0.5, color='C5', label='synthetic data')\n", + "ax.plot(q, sa_fit, 'k-', lw=1.5, label='fit')\n", + "ax.axhline(0, color='k', lw=0.5)\n", + "ax.set_xlabel('Q (Å⁻¹)')\n", + "ax.set_ylabel('(R⁺⁺ − R⁻⁻) / (R⁺⁺ + R⁻⁻)')\n", + "ax.set_title('Spin asymmetry — data vs simultaneous fit')\n", + "ax.legend()\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "summary-md", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "- **`LayerMagnetism(rho_m, theta_m)`** makes a layer magnetic; both are fittable, bounded,\n", + " serializable `Parameter`s. Magnetism is enabled on the calculator automatically (refl1d\n", + " only).\n", + "- **`polarized_reflectivity_profiles`** / **`magnetic_sld_profile`** give the four spin\n", + " channels and the nuclear + magnetic depth profile in one call each.\n", + "- **`detect_polarization_channel`** assigns spin channels from ORSO headers or filename\n", + " tokens; partially-analysed observables (`po`/`mo` = channel sums) are never auto-assigned.\n", + "- **`PolarizedDataSet`** groups per-channel datasets (2-channel NSF-only works the same way:\n", + " provide just `pp` and `mm`) under one shared model; `ye` holds variances.\n", + "- **`MultiFitter.fit_polarized`** fits every measured channel simultaneously and recovered\n", + " thickness, `rho_m` and `theta_m` here to within a fraction of a percent, at roughly the\n", + " cost of a single-channel fit (four-channel cache: one kernel evaluation per iteration).\n", + "\n", + "For file-based / GUI workflows the same functionality is reachable through\n", + "`Project.suggest_polarized_channel_assignment(paths)` and\n", + "`Project.load_polarized_experiment({channel: path})`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pixi.toml b/pixi.toml index f0023fba..70a2a773 100644 --- a/pixi.toml +++ b/pixi.toml @@ -5,7 +5,7 @@ [workspace] # Supported platforms for the lock file (pixi.lock) -platforms = ['win-64', 'linux-64', 'osx-arm64'] +platforms = ['win-64', 'linux-64', { platform = 'osx-arm64', macos = '14.0' }] # Channels for fetching packages channels = ['nodefaults', 'conda-forge'] @@ -19,13 +19,6 @@ channels = ['nodefaults', 'conda-forge'] [activation.env] PYTHONIOENCODING = 'utf-8' -[system-requirements] -# Set minimum supported version for macOS to be 14.0 to ensure packages -# like `scipp` that only have wheels for macOS 14.0+ (macosx_14_0_arm64) -# are used instead of building from source. This is a workaround for -# Pixi, see https://github.com/prefix-dev/pixi/issues/5667 -macos = '14.0' - # Non-default features: # Set specific Python versions to be used in CI testing. diff --git a/src/easyreflectometry/calculators/__init__.py b/src/easyreflectometry/calculators/__init__.py index 9f7bde45..1282928d 100644 --- a/src/easyreflectometry/calculators/__init__.py +++ b/src/easyreflectometry/calculators/__init__.py @@ -5,6 +5,7 @@ from .calculator_base import CalculatorBase from .factory import CalculatorFactory +from .polarization import PolarizationChannel imported_calculators = [] @@ -31,4 +32,4 @@ traceback.print_exc() print('Warning: refl1d is not installed') -__all__ = ['CalculatorBase', 'CalculatorFactory'] + [c.__name__ for c in imported_calculators] +__all__ = ['CalculatorBase', 'CalculatorFactory', 'PolarizationChannel'] + [c.__name__ for c in imported_calculators] diff --git a/src/easyreflectometry/calculators/calculator_base.py b/src/easyreflectometry/calculators/calculator_base.py index e2a92804..dda986b1 100644 --- a/src/easyreflectometry/calculators/calculator_base.py +++ b/src/easyreflectometry/calculators/calculator_base.py @@ -201,6 +201,48 @@ def reflectity_profile(self, x_array: np.ndarray, model_id: str) -> np.ndarray: """ return self._wrapper.calculate(x_array, model_id) + def reflectivity_profile_channel(self, x_array: np.ndarray, model_id: str, channel) -> np.ndarray: + """Determine the reflectivity profile of one explicit spin channel. + + Unlike `polarization_channel` (global calculator state), the channel is an + argument, so several channels can be evaluated against the same model — + one per dataset in a simultaneous multi-channel fit. + + Parameters + ---------- + x_array : np.ndarray + Points to be calculated at. + model_id : str + The model id. + channel : PolarizationChannel | str + One of 'pp', 'pm', 'mp', 'mm' (or the corresponding enum member). + + Returns + ------- + np.ndarray + Reflectivity of the requested channel at q. + """ + return self._wrapper.calculate_channel(x_array, model_id, channel) + + def polarized_reflectivity_profiles(self, x_array: np.ndarray, model_id: str) -> dict[str, np.ndarray]: + """Determines the reflectivity profiles of all four spin channels for the given range and model. + + Requires `include_magnetism` to be enabled and a calculator that supports it (refl1d). + + Parameters + ---------- + x_array : np.ndarray + Points to be calculated at. + model_id : str + The model id. + + Returns + ------- + dict[str, np.ndarray] + Reflectivity per spin channel, keyed 'pp', 'pm', 'mp', 'mm' (in that order). + """ + return self._wrapper.calculate_polarized(x_array, model_id) + def sld_profile(self, model_id: str) -> tuple[np.ndarray, np.ndarray]: """Return the scattering length density profile. @@ -216,10 +258,43 @@ def sld_profile(self, model_id: str) -> tuple[np.ndarray, np.ndarray]: """ return self._wrapper.sld_profile(model_id) + def magnetic_sld_profile(self, model_id: str) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """Return the nuclear and magnetic scattering length density profiles. + + Requires `include_magnetism` to be enabled and a calculator that supports it (refl1d). + + Parameters + ---------- + model_id : str + The model id. + + Returns + ------- + tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray] + z, sld(z), magnetic sld rhoM(z) and magnetic angle thetaM(z). + """ + return self._wrapper.magnetic_sld_profile(model_id) + def set_resolution_function(self, resolution_function: Callable[[np.array], np.array]) -> None: """Set resolution function.""" return self._wrapper.set_resolution_function(resolution_function) + @property + def supports_magnetism(self) -> bool: + """Whether this calculator backend can model magnetic samples.""" + return self._wrapper.supports_magnetism + + def remove_layer_magnetism(self, layer_id: str) -> None: + """Remove the magnetic state of one layer; disables `include_magnetism` + when no magnetic layer is left. + + Parameters + ---------- + layer_id : str + The layer id. + """ + self._wrapper.remove_layer_magnetism(layer_id) + @property def include_magnetism(self): """Include magnetism.""" @@ -235,3 +310,23 @@ def include_magnetism(self, magnetism: bool): True if the calculator should include magnetism. """ self._wrapper.magnetism = magnetism + + @property + def polarization_channel(self): + """The spin channel ('pp', 'pm', 'mp' or 'mm') used by `reflectity_profile` when magnetism is enabled. + + Note: this state belongs to the currently-active calculator instance; switching + calculators via the factory constructs a fresh instance and resets it. + """ + return self._wrapper.polarization_channel + + @polarization_channel.setter + def polarization_channel(self, channel) -> None: + """Set the spin channel for reflectivity calculations. + + Parameters + ---------- + channel : PolarizationChannel | str + One of 'pp', 'pm', 'mp', 'mm' (or the corresponding enum member). + """ + self._wrapper.polarization_channel = channel diff --git a/src/easyreflectometry/calculators/factory.py b/src/easyreflectometry/calculators/factory.py index c3e1479c..b39f1daa 100644 --- a/src/easyreflectometry/calculators/factory.py +++ b/src/easyreflectometry/calculators/factory.py @@ -8,6 +8,8 @@ from easyreflectometry.calculators import CalculatorBase +from .polarization import PolarizationChannel + class CalculatorFactory(InterfaceFactoryTemplate): def __init__(self): @@ -22,6 +24,18 @@ def sld_profile(self, model_id: str) -> tuple: """Sld profile.""" return self().sld_profile(model_id) + def polarized_reflectivity_profiles(self, x_array, model_id: str) -> dict: + """Reflectivity profiles of all four spin channels ('pp', 'pm', 'mp', 'mm').""" + return self().polarized_reflectivity_profiles(x_array, model_id) + + def reflectivity_profile_channel(self, x_array, model_id: str, channel: PolarizationChannel | str): + """Reflectivity profile of one explicit spin channel ('pp', 'pm', 'mp' or 'mm').""" + return self().reflectivity_profile_channel(x_array, model_id, channel) + + def magnetic_sld_profile(self, model_id: str) -> tuple: + """Nuclear and magnetic sld profiles: z, sld(z), rhoM(z) and thetaM(z).""" + return self().magnetic_sld_profile(model_id) + @property def fit_func(self) -> Callable: """Fit func.""" @@ -43,3 +57,28 @@ def __fit_func(*args, **kwargs): return self().reflectity_profile(*args, **kwargs) return __fit_func + + def fit_func_for_channel(self, channel: PolarizationChannel | str) -> Callable: + """A fit function evaluating one explicit spin channel. + + Used for simultaneous multi-channel fitting: each channel dataset gets its + own fit function while all of them share the same model (and hence the + same parameters). + + Parameters + ---------- + channel : PolarizationChannel | str + One of 'pp', 'pm', 'mp', 'mm' (or the corresponding enum member). + + Returns + ------- + Callable + Function of (x_array, model_id) returning the channel reflectivity. + """ + channel = PolarizationChannel(channel) + + def __fit_func(x_array, model_id): + """Fit func for one spin channel.""" + return self().reflectivity_profile_channel(x_array, model_id, channel) + + return __fit_func diff --git a/src/easyreflectometry/calculators/polarization.py b/src/easyreflectometry/calculators/polarization.py new file mode 100644 index 00000000..16717bd2 --- /dev/null +++ b/src/easyreflectometry/calculators/polarization.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +from enum import Enum + + +class PolarizationChannel(str, Enum): + """Spin cross-section channels for polarized neutron reflectometry. + + The accepted spellings are exactly the enum values ('pp', 'pm', 'mp', 'mm'); + uppercase strings are rejected. + """ + + PP = 'pp' # non-spin-flip, up-up + PM = 'pm' # spin-flip, up-down + MP = 'mp' # spin-flip, down-up + MM = 'mm' # non-spin-flip, down-down + + +# Mapping to refl1d cross-section indices. refl1d returns the polarized +# cross-sections in the order of `PolarizedNeutronProbe._xs_names`, which is +# ['mm', 'mp', 'pm', 'pp'] (refl1d/probe/probe.py); `magnetic_amplitude` returns +# (--, -+, +-, ++) accordingly (refl1d/sample/reflectivity.py). The pp/mm +# assignment is pinned by a physics test (moment aligned with the guide field: +# pp must see rho + rhoM); pm vs mp rests on the refl1d source alone (they are +# identical by symmetry for non-chiral, non-absorptive samples). +# The key order (pp, pm, mp, mm) is the canonical channel order used throughout. +POLARIZATION_CHANNEL_TO_INDEX = { + PolarizationChannel.PP: 3, + PolarizationChannel.PM: 2, + PolarizationChannel.MP: 1, + PolarizationChannel.MM: 0, +} diff --git a/src/easyreflectometry/calculators/refl1d/calculator.py b/src/easyreflectometry/calculators/refl1d/calculator.py index 2f5068de..ac7214e2 100644 --- a/src/easyreflectometry/calculators/refl1d/calculator.py +++ b/src/easyreflectometry/calculators/refl1d/calculator.py @@ -19,6 +19,8 @@ class Refl1d(CalculatorBase): _layer_link = { 'thickness': 'thickness', 'roughness': 'interface', + 'rho_m': 'magnetism_rhoM', + 'theta_m': 'magnetism_thetaM', } _item_link = { diff --git a/src/easyreflectometry/calculators/refl1d/wrapper.py b/src/easyreflectometry/calculators/refl1d/wrapper.py index 6985d47c..9400bdf4 100644 --- a/src/easyreflectometry/calculators/refl1d/wrapper.py +++ b/src/easyreflectometry/calculators/refl1d/wrapper.py @@ -2,20 +2,49 @@ # SPDX-License-Identifier: BSD-3-Clause +import logging from typing import Tuple import numpy as np from refl1d import names +from refl1d.profile import build_profile from refl1d.sample.layers import Repeat +from ..polarization import POLARIZATION_CHANNEL_TO_INDEX from ..wrapper_base import WrapperBase +logger = logging.getLogger(__name__) + RESOLUTION_PADDING = 3.5 OVERSAMPLING_FACTOR = 21 -ALL_POLARIZATIONS = False + +# refl1d convention: with the default guide field (Aguide = 270 deg) a moment at +# thetaM = 270 deg is aligned with the field, i.e. produces no spin-flip. +DEFAULT_THETA_M = 270.0 class Refl1dWrapper(WrapperBase): + supports_magnetism = True + + def __init__(self): + """Constructor.""" + super().__init__() + # Magnetic values per layer name, kept outside the slabs so they survive + # magnetism being toggled off/on and can be set before it is enabled. + self._layer_magnetism: dict[str, dict[str, float]] = {} + # Per-model cache of polarized reflectivities: all four cross-sections come + # from a single kernel evaluation, so a simultaneous multi-channel fit costs + # one evaluation per iteration instead of one per channel. Keyed on a token + # of every model input; entries per (q, dq) grid, so channels measured on + # different grids coexist within one iteration. + self._polarized_cache: dict[str, dict] = {} + + def reset_storage(self): + """Reset the storage area (including stored magnetic values) to blank.""" + super().reset_storage() + self._layer_magnetism = {} + self._polarized_cache = {} + def create_material(self, name: str): """Create a material using SLD. @@ -35,7 +64,11 @@ def create_layer(self, name: str): The name of the layer. """ if self._magnetism: - magnetism = names.Magnetism(rhoM=0.0, thetaM=0.0) + values = self._layer_magnetism.get(name, {}) + magnetism = names.Magnetism( + rhoM=values.get('rhoM', 0.0), + thetaM=values.get('thetaM', DEFAULT_THETA_M), + ) else: magnetism = None self.storage['layer'][name] = names.Slab(name=str(name), magnetism=magnetism) @@ -54,17 +87,24 @@ def create_item(self, name: str): def update_layer(self, name: str, **kwargs): """Update a layer in a given item. + Magnetic keys (`magnetism_rhoM`, `magnetism_thetaM`) may be passed alone or + together; values are stored per layer and attached to the slab when + magnetism is enabled. + Parameters ---------- name : str The layer name. **kwargs : """ - kwargs_no_magnetism = {k: v for k, v in kwargs.items() if k != 'magnetism_rhoM' and k != 'magnetism_thetaM'} + magnetic_values = {k.removeprefix('magnetism_'): v for k, v in kwargs.items() if k.startswith('magnetism_')} + kwargs_no_magnetism = {k: v for k, v in kwargs.items() if not k.startswith('magnetism_')} super().update_layer(name, **kwargs_no_magnetism) - if any(item.startswith('magnetism') for item in kwargs.keys()): - magnetism = names.Magnetism(rhoM=kwargs['magnetism_rhoM'], thetaM=kwargs['magnetism_thetaM']) - self.storage['layer'][name].magnetism = magnetism + if magnetic_values: + stored = self._layer_magnetism.setdefault(name, {'rhoM': 0.0, 'thetaM': DEFAULT_THETA_M}) + stored.update(magnetic_values) + if self._magnetism: + self._apply_magnetism_to_layer(name) def get_layer_value(self, name: str, key: str) -> float: """A function to get a given layer value. @@ -77,11 +117,59 @@ def get_layer_value(self, name: str, key: str) -> float: The given value keys. """ if key in ['magnetism_rhoM', 'magnetism_thetaM']: - return getattr( - self.storage['layer'][name].magnetism, key.split('_')[-1] - ).value # TODO: check if we want to return the raw value or the full Parameter # noqa: E501 + defaults = {'rhoM': 0.0, 'thetaM': DEFAULT_THETA_M} + magnetic_key = key.removeprefix('magnetism_') + return self._layer_magnetism.get(name, defaults).get(magnetic_key, defaults[magnetic_key]) return super().get_layer_value(name, key) + def _remove_magnetism_from_layers(self) -> None: + """Detach Magnetism objects from all slabs. + + Called when magnetism is disabled: slabs carrying Magnetism objects would + crash refl1d's plain (unpolarized) QProbe path. The magnetic values remain + stored and are re-attached when magnetism is re-enabled. + """ + for layer in self.storage['layer'].values(): + layer.magnetism = None + + def _apply_magnetism_to_layers(self) -> None: + """Attach stored magnetic values to all slabs (called when magnetism is enabled).""" + for name in self.storage['layer']: + self._apply_magnetism_to_layer(name) + + def _apply_magnetism_to_layer(self, name: str) -> None: + """Attach the stored magnetic values (or defaults) of one layer to its slab.""" + values = self._layer_magnetism.get(name, {}) + slab = self.storage['layer'][name] + slab.magnetism = names.Magnetism( + rhoM=values.get('rhoM', 0.0), + thetaM=values.get('thetaM', DEFAULT_THETA_M), + ) + + def remove_layer_magnetism(self, name: str) -> None: + """Remove the magnetic state of one layer; disable magnetism when none is left. + + Keeps `magnetism` (the calculator flag) in sync with the model: once no + layer holds magnetic values any more, the polarized calculation path is + switched off entirely. + + Parameters + ---------- + name : str + The layer name. + """ + self._layer_magnetism.pop(name, None) + slab = self.storage['layer'].get(name) + if slab is not None: + # A non-magnetic slab is fine inside a polarized calculation. + slab.magnetism = None + if self._magnetism and not self._layer_magnetism: + # Goes through the `magnetism` property setter, which calls + # `_remove_magnetism_from_layers()` again (a no-op here since + # `_layer_magnetism` is already empty) and, more importantly, + # resets `_polarization_channel` back to PP. + self.magnetism = False + def create_model(self, name: str): """Create a model for analysis. @@ -202,44 +290,120 @@ def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: np.ndarray Reflectivity calculated at q. """ + if self._magnetism: + reflectivities = self._polarized_reflectivities(q_array, model_name) + # Copy: the arrays live in the polarized cache and must not be mutated. + return reflectivities[POLARIZATION_CHANNEL_TO_INDEX[self._polarization_channel]].copy() + sample = _build_sample(self.storage, model_name) # smearing() returns sigma, which is exactly what refl1d's probe.dQ expects. dq_array = self._resolution_function.smearing(q_array) + probe = _get_probe( + q_array=q_array, + dq_array=dq_array, + model_name=model_name, + storage=self.storage, + oversampling_factor=OVERSAMPLING_FACTOR, + ) + # returns q, reflectivity + _, reflectivity = names.Experiment(probe=probe, sample=sample).reflectivity() + return reflectivity + + def calculate_polarized(self, q_array: np.ndarray, model_name: str) -> dict[str, np.ndarray]: + """For a given q array calculate the reflectivity of all four spin channels. + + Parameters + ---------- + q_array : np.ndarray + Array of data points to be calculated. + model_name : str + The model name. + Returns + ------- + dict[str, np.ndarray] + Reflectivity per spin channel, keyed 'pp', 'pm', 'mp', 'mm' (in that order). + """ if not self._magnetism: - probe = _get_probe( - q_array=q_array, - dq_array=dq_array, - model_name=model_name, - storage=self.storage, - oversampling_factor=OVERSAMPLING_FACTOR, + raise ValueError( + 'Polarized reflectivity requires magnetism: enable it on this calculator first ' + '(`include_magnetism = True` on the calculator / `magnetism = True` on the wrapper).' ) - # returns q, reflectivity - _, reflectivity = names.Experiment(probe=probe, sample=sample).reflectivity() + reflectivities = self._polarized_reflectivities(q_array, model_name) + # Copies: the arrays live in the polarized cache and must not be mutated. + return {channel.value: reflectivities[index].copy() for channel, index in POLARIZATION_CHANNEL_TO_INDEX.items()} + + def _model_state_token(self, model_name: str) -> tuple: + """A token of every model input that affects the reflectivity. + + Two calls with equal tokens (and equal q/dq grids) are guaranteed to + produce the same reflectivity, so cached cross-sections can be reused. + The resolution function needs no entry here — it enters through the dq + part of the per-grid cache key. Must be extended whenever a new slab or + material attribute starts reaching the kernel: a forgotten kernel input + would silently serve stale reflectivities whenever only that input + changes, since the token would compare equal. + """ + model = self.storage['model'][model_name] + values: list = [model['scale'], model['bkg']] + for item in model['items']: + values.append(item.repeat.value) + for slab in item.stack: + values.extend(( + slab.thickness.value, + slab.interface.value, + slab.material.rho.value, + slab.material.irho.value, + )) + if slab.magnetism is None: + values.append(None) + else: + values.extend((slab.magnetism.rhoM.value, slab.magnetism.thetaM.value)) + return tuple(values) + + def _polarized_reflectivities(self, q_array: np.ndarray, model_name: str) -> list: + """Reflectivity of the four spin cross-sections, in refl1d order (mm, mp, pm, pp). + + The list follows `PolarizedNeutronProbe._xs_names`; use + `POLARIZATION_CHANNEL_TO_INDEX` to pick a channel out of it. + Results are cached per model state and (q, dq) grid; see `_polarized_cache`. + """ + # Normalized dtype plus explicit shape in the key: raw bytes alone do not + # uniquely identify an ndarray (equal bytes can encode different + # dtype/shape combinations), which could return a wrong-length hit. + q_array = np.asarray(q_array, dtype=np.float64) + dq_array = np.asarray(self._resolution_function.smearing(q_array), dtype=np.float64) + + token = self._model_state_token(model_name) + grid_key = (q_array.shape, q_array.tobytes(), dq_array.shape, dq_array.tobytes()) + cache = self._polarized_cache.get(model_name) + if cache is not None and cache['token'] == token: + cached = cache['entries'].get(grid_key) + if cached is not None: + return cached else: - polarized_probe = _get_polarized_probe( - q_array=q_array, - dq_array=dq_array, - model_name=model_name, - storage=self.storage, - oversampling_factor=OVERSAMPLING_FACTOR, - all_polarizations=ALL_POLARIZATIONS, - ) - polarized_reflectivity = names.Experiment(probe=polarized_probe, sample=sample).reflectivity() - - if ALL_POLARIZATIONS: - raise NotImplementedError('Polarized reflectivity not yet implemented') - # returns q, reflectivity - # _, reflectivity_pp = polarized_reflectivity[0] - # _, reflectivity_pm = polarized_reflectivity[1] - # _, reflectivity_mp = polarized_reflectivity[2] - # _, reflectivity_mm = polarized_reflectivity[3] - else: - # Only pick the pp reflectivity - # returns q, reflectivity - _, reflectivity = polarized_reflectivity[0] + cache = {'token': token, 'entries': {}} + self._polarized_cache[model_name] = cache - return reflectivity + sample = _build_sample(self.storage, model_name) + polarized_probe = _get_polarized_probe( + q_array=q_array, + dq_array=dq_array, + model_name=model_name, + storage=self.storage, + oversampling_factor=OVERSAMPLING_FACTOR, + ) + polarized_reflectivity = names.Experiment(probe=polarized_probe, sample=sample).reflectivity() + + # returns (q, reflectivity) per cross-section + reflectivities = [reflectivity for _, reflectivity in polarized_reflectivity] + if len(reflectivities) != 4: + raise RuntimeError(f'refl1d returned {len(reflectivities)} polarized cross-sections; expected 4.') + for channel, index in POLARIZATION_CHANNEL_TO_INDEX.items(): + if len(reflectivities[index]) != len(q_array) or not np.all(np.isfinite(reflectivities[index])): + raise RuntimeError(f'refl1d returned a malformed {channel.value} cross-section.') + cache['entries'][grid_key] = reflectivities + return reflectivities def sld_profile(self, model_name: str) -> Tuple[np.ndarray, np.ndarray]: """Return the scattering length density profile. @@ -265,6 +429,93 @@ def sld_profile(self, model_name: str) -> Tuple[np.ndarray, np.ndarray]: # -1 to reverse the order return z, sld[::-1] + def magnetic_sld_profile(self, model_name: str) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """Return the nuclear and magnetic scattering length density profiles. + + The magnetic profile is built by smoothing the two in-plane components + of the moment and converting back, not by smoothing its magnitude and + angle separately — see :meth:`_smoothed_magnetic_vector`. + + Parameters + ---------- + model_name : str + The model name. + + Returns + ------- + + z, sld(z), magnetic sld rhoM(z) and magnetic angle thetaM(z). + """ + if not self._magnetism: + raise ValueError( + 'The magnetic sld profile requires magnetism: enable it on this calculator first ' + '(`include_magnetism = True` on the calculator / `magnetism = True` on the wrapper).' + ) + sample = _build_sample(self.storage, model_name) + # Plain (non-polarized) probe: unlike `_polarized_reflectivities`, this + # only renders slabs for `magnetic_smooth_profile()`/`_render_slabs()`, + # never computes a per-channel reflectivity, so the `theta_offset` that + # `magnetism=True` would add (needed by `PolarizedQProbe`) is not required. + probe = _get_probe( + q_array=np.array([1]), # dummy value + dq_array=np.array([1]), # dummy value + model_name=model_name, + storage=self.storage, + ) + experiment = names.Experiment(probe=probe, sample=sample) + z, sld, _, _, _ = experiment.magnetic_smooth_profile() + sld_magnetic, theta_magnetic = self._smoothed_magnetic_vector(experiment, z) + # -1 to reverse the order + return z, sld[::-1], sld_magnetic[::-1], theta_magnetic[::-1] + + @staticmethod + def _smoothed_magnetic_vector(experiment, z: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: + """Magnitude and angle of the smoothed in-plane moment. + + refl1d smooths the magnetic microslabs channel by channel, so the + magnitude |rhoM| and the angle thetaM are interpolated independently + across an interface. For two layers whose moments differ by a couple of + degrees around 0/360 (e.g. 359 and 1) the angle then takes the long way + round the circle, passing through the guide-field direction: the profile + reports the *full* moment as longitudinal exactly where it is almost + entirely transverse, which shows up as a spurious spin-up/spin-down + splitting at the interface. + + Smoothing the Cartesian components instead and converting back is + interpolation of the moment as a vector, which is what the physics does. + The reference angle used for the decomposition cancels out. + + Raises + ------ + NotImplementedError + The installed refl1d does not expose the microslab data this needs. + Falling back to its angle-smoothed profile is deliberately *not* + done: that output is wrong in a way a user cannot see, and a silent + change of results after a dependency update is worse than no + profile at all. + """ + try: + slabs = experiment._render_slabs() + offsets = np.cumsum(slabs.w[:-1]) + slabs._z_offset + roughness = slabs.sigma + rho_m = np.asarray(slabs.rhoM, dtype=float) + theta_m = np.asarray(slabs.thetaM, dtype=float) + except (AttributeError, IndexError, TypeError) as exception: # pragma: no cover - refl1d internals + raise NotImplementedError( + 'The installed refl1d does not provide the microslab data needed for a ' + f'component-safe magnetic profile ({exception}). The magnetic depth profile is ' + "unavailable; refl1d's own profile smooths the moment angle separately, which " + 'misreports the spin-up/spin-down splitting at interfaces between differently ' + 'oriented moments.' + ) from exception + + relative_angle = np.radians(theta_m - DEFAULT_THETA_M) + parallel = build_profile(z, offsets, roughness, rho_m * np.cos(relative_angle)) + perpendicular = build_profile(z, offsets, roughness, rho_m * np.sin(relative_angle)) + magnitude = np.hypot(parallel, perpendicular) + angle = (DEFAULT_THETA_M + np.degrees(np.arctan2(perpendicular, parallel))) % 360.0 + return magnitude, angle + def _get_oversampling_q(q_array: np.ndarray, dq_array: np.ndarray, oversampling_factor: int) -> np.ndarray: """Get oversampling q.""" @@ -294,7 +545,10 @@ def _get_probe( ) # Add theta_offset attribute if magnetism is enabled - # This is required for PolarizedQProbe to work correctly + # This is required for PolarizedQProbe to work correctly: refl1d's + # `PolarizedNeutronQProbe.__init__` -> `_calculate_union` reads `theta_offset` + # off each constituent probe, so a QProbe destined for a PolarizedQProbe must + # carry it even though the plain (unpolarized) QProbe path never touches it. if magnetism: probe.theta_offset = names.Parameter.default(0, name='theta_offset') @@ -309,28 +563,31 @@ def _get_polarized_probe( model_name: str, storage: dict, oversampling_factor: int = 1, - all_polarizations: bool = False, ) -> names.PolarizedNeutronQProbe: - """Get polarized probe.""" - four_probes = [] - for i in range(4): - if i == 0 or all_polarizations: - probe = _get_probe( - q_array=q_array, - dq_array=dq_array, - model_name=model_name, - storage=storage, - oversampling_factor=oversampling_factor, - magnetism=True, # Enable magnetism for polarized probes - ) - else: - probe = None - four_probes.append(probe) - - # Create polarized probe and work around initialization bug - polarized_probe = names.PolarizedNeutronQProbe.__new__(names.PolarizedNeutronQProbe) - polarized_probe._union_cache_key = None # Initialize missing attribute - polarized_probe.__init__(xs=four_probes, name='polarized') + """Get polarized probe with all four cross-sections (pp, pm, mp, mm).""" + four_probes = [ + _get_probe( + q_array=q_array, + dq_array=dq_array, + model_name=model_name, + storage=storage, + oversampling_factor=oversampling_factor, + magnetism=True, # Enable magnetism for polarized probes + ) + for _ in range(4) + ] + + try: + polarized_probe = names.PolarizedNeutronQProbe(xs=four_probes, name='polarized') + except AttributeError: + # refl1d 1.0.0 bug: PolarizedQProbe.__init__ calls _calculate_union(), which + # reads self._union_cache_key before the attribute is ever assigned (the + # non-Q PolarizedNeutronProbe assigns it in __init__; the Q variant does + # not). Pre-seed the attribute and re-run __init__. The try/except makes + # the workaround self-removing once refl1d fixes the initialization. + polarized_probe = names.PolarizedNeutronQProbe.__new__(names.PolarizedNeutronQProbe) + polarized_probe._union_cache_key = None + polarized_probe.__init__(xs=four_probes, name='polarized') return polarized_probe diff --git a/src/easyreflectometry/calculators/refnx/wrapper.py b/src/easyreflectometry/calculators/refnx/wrapper.py index 65dc8662..3ebb0bc4 100644 --- a/src/easyreflectometry/calculators/refnx/wrapper.py +++ b/src/easyreflectometry/calculators/refnx/wrapper.py @@ -14,22 +14,6 @@ class RefnxWrapper(WrapperBase): - @property - def include_magnetism(self) -> bool: - """Include magnetism.""" - return self._magnetism - - @include_magnetism.setter - def include_magnetism(self, magnetism: bool) -> None: - """Set the magnetism flag. - - Parameters - ---------- - magnetism : bool - The magnetism flag. - """ - raise NotImplementedError('Magnetism is not supported by refnx') - def create_material(self, name: str): """Create a material using SLD. diff --git a/src/easyreflectometry/calculators/wrapper_base.py b/src/easyreflectometry/calculators/wrapper_base.py index dc53ceca..32e1d4be 100644 --- a/src/easyreflectometry/calculators/wrapper_base.py +++ b/src/easyreflectometry/calculators/wrapper_base.py @@ -8,11 +8,17 @@ from easyreflectometry.model import PercentageFwhm from easyreflectometry.model import ResolutionFunction +from .polarization import PolarizationChannel + class WrapperBase: + #: Whether this calculator backend can model magnetic samples. + supports_magnetism = False + def __init__(self): """Constructor.""" self._magnetism = False + self._polarization_channel = PolarizationChannel.PP self.storage = { 'material': {}, 'layer': {}, @@ -317,4 +323,120 @@ def magnetism(self, magnetism: bool) -> None: magnetism : bool The magnetism flag. """ + if magnetism and not self.supports_magnetism: + raise NotImplementedError(f'Magnetism is not supported by {self.__class__.__name__}') self._magnetism = magnetism + if magnetism: + # Attach any magnetic values set (or restored) while magnetism was off. + self._apply_magnetism_to_layers() + else: + # A non-pp channel is only meaningful on the polarized probe path. + self._polarization_channel = PolarizationChannel.PP + # Leave no magnetic residue behind: the unpolarized calculation path + # must work on the layers that already exist. + self._remove_magnetism_from_layers() + + def _remove_magnetism_from_layers(self) -> None: + """Strip backend magnetism state from existing layers when magnetism is disabled. + + No-op by default; overridden by backends that attach magnetic objects to layers. + """ + + def _apply_magnetism_to_layers(self) -> None: + """Attach stored magnetic values to existing layers when magnetism is enabled. + + No-op by default; overridden by backends that attach magnetic objects to layers. + """ + + def remove_layer_magnetism(self, name: str) -> None: + """Remove the magnetic state of one layer; disable magnetism when none is left. + + No-op by default; overridden by backends that support magnetism. + + Parameters + ---------- + name : str + The layer name. + """ + + @property + def polarization_channel(self) -> PolarizationChannel: + """The spin channel returned by `calculate` when magnetism is enabled.""" + return self._polarization_channel + + @polarization_channel.setter + def polarization_channel(self, channel: PolarizationChannel | str) -> None: + """Set the spin channel returned by `calculate`. + + Parameters + ---------- + channel : PolarizationChannel | str + One of 'pp', 'pm', 'mp', 'mm' (or the corresponding enum member). + """ + channel = PolarizationChannel(channel) + if channel is not PolarizationChannel.PP and not self._magnetism: + raise ValueError(f"Selecting the '{channel.value}' channel requires magnetism to be enabled.") + self._polarization_channel = channel + + def calculate_channel(self, q_array: np.ndarray, model_name: str, channel: PolarizationChannel | str) -> np.ndarray: + """For a given q array calculate the reflectivity of one explicit spin channel. + + Unlike the `polarization_channel` property (global calculator state used by + `calculate`), the channel is passed explicitly, so several channels can be + evaluated against the same model, e.g. one per dataset in a simultaneous + multi-channel fit. + + Parameters + ---------- + q_array : np.ndarray + Array of data points to be calculated. + model_name : str + The model name. + channel : PolarizationChannel | str + One of 'pp', 'pm', 'mp', 'mm' (or the corresponding enum member). + + Returns + ------- + np.ndarray + Reflectivity of the requested channel at q. + """ + channel = PolarizationChannel(channel) + if not self._magnetism: + if channel is PolarizationChannel.PP: + # No explicit `.copy()` needed here: `calculate()` always returns + # a fresh array (a cached, shared array only exists on the + # magnetism-enabled `calculate_polarized` path below). + return self.calculate(q_array, model_name) + raise ValueError(f"Calculating the '{channel.value}' channel requires magnetism to be enabled.") + return self.calculate_polarized(q_array, model_name)[channel.value] + + def calculate_polarized(self, q_array: np.ndarray, model_name: str) -> dict[str, np.ndarray]: + """For a given q array calculate the reflectivity of all four spin channels. + + Parameters + ---------- + q_array : np.ndarray + Array of data points to be calculated. + model_name : str + The model name. + + Returns + ------- + dict[str, np.ndarray] + Reflectivity per spin channel, keyed 'pp', 'pm', 'mp', 'mm' (in that order). + """ + raise NotImplementedError(f'{self.__class__.__name__} does not support polarized reflectivity.') + + def magnetic_sld_profile(self, model_name: str) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """Return the nuclear and magnetic scattering length density profiles. + + Parameters + ---------- + model_name : str + The model name. + + Returns + ------- + z, sld(z), magnetic sld rhoM(z) and magnetic angle thetaM(z). + """ + raise NotImplementedError(f'{self.__class__.__name__} does not support magnetic sld profiles.') diff --git a/src/easyreflectometry/data/__init__.py b/src/easyreflectometry/data/__init__.py index 0d058120..e63470eb 100644 --- a/src/easyreflectometry/data/__init__.py +++ b/src/easyreflectometry/data/__init__.py @@ -6,6 +6,8 @@ from .measurement import load from .measurement import load_as_dataset from .measurement import merge_datagroups +from .polarized import PolarizedDataSet +from .polarized import detect_polarization_channel __all__ = [ 'load', @@ -13,4 +15,6 @@ 'merge_datagroups', 'ProjectData', 'DataSet1D', + 'PolarizedDataSet', + 'detect_polarization_channel', ] diff --git a/src/easyreflectometry/data/polarized.py b/src/easyreflectometry/data/polarized.py new file mode 100644 index 00000000..ae8fcac8 --- /dev/null +++ b/src/easyreflectometry/data/polarized.py @@ -0,0 +1,284 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Polarized experiment data: per-spin-channel datasets and channel detection. + +A polarized neutron reflectometry experiment measures up to four spin +cross-sections (non-spin-flip 'pp'/'mm', spin-flip 'pm'/'mp'), typically stored +as one file per channel. `PolarizedDataSet` groups those per-channel +`DataSet1D` objects into a single experiment sharing one model. +""" + +from __future__ import annotations + +import os +import re +from types import MappingProxyType +from typing import Mapping +from typing import Optional +from typing import Union + +from easyreflectometry.calculators.polarization import POLARIZATION_CHANNEL_TO_INDEX +from easyreflectometry.calculators.polarization import PolarizationChannel + +from .data_store import DataSet1D + +# ORSO instrument_settings.polarization values → spin channel. +# Only the four fully-analysed cross-sections are mapped. Partially-analysed +# observables are deliberately NOT: 'po' (incident plus, no outgoing analysis) +# measures the sum pp + pm — a physically different observable from the pp +# cross-section — and likewise 'mo' = mp + mm; 'op'/'om' analyse only the +# outgoing spin; 'unpolarized' is no channel at all. A header declaring any of +# those suppresses the filename fallback and the user must assign (or the data +# must be modelled as a channel sum, which this formalism does not do yet). +_ORSO_POLARIZATION_TO_CHANNEL = { + 'pp': PolarizationChannel.PP, + 'pm': PolarizationChannel.PM, + 'mp': PolarizationChannel.MP, + 'mm': PolarizationChannel.MM, +} + +# Filename tokens → spin channel (used when there is no ORSO header). +_TOKEN_TO_CHANNEL = { + 'pp': PolarizationChannel.PP, + 'uu': PolarizationChannel.PP, + 'upup': PolarizationChannel.PP, + 'plusplus': PolarizationChannel.PP, + 'mm': PolarizationChannel.MM, + 'dd': PolarizationChannel.MM, + 'downdown': PolarizationChannel.MM, + 'minusminus': PolarizationChannel.MM, + 'pm': PolarizationChannel.PM, + 'ud': PolarizationChannel.PM, + 'updown': PolarizationChannel.PM, + 'mp': PolarizationChannel.MP, + 'du': PolarizationChannel.MP, + 'downup': PolarizationChannel.MP, +} + +# Single tokens for 2-channel (NSF-only) naming like 'sample_up.dat' / 'sample_down.dat'. +_SINGLE_TOKEN_TO_CHANNEL = { + 'u': PolarizationChannel.PP, + 'up': PolarizationChannel.PP, + 'p': PolarizationChannel.PP, + 'plus': PolarizationChannel.PP, + 'd': PolarizationChannel.MM, + 'down': PolarizationChannel.MM, + 'm': PolarizationChannel.MM, + 'minus': PolarizationChannel.MM, +} + +# Adjacent token pairs like 'up_down' → channel. +_TOKEN_PAIR_TO_CHANNEL = { + ('up', 'up'): PolarizationChannel.PP, + ('up', 'down'): PolarizationChannel.PM, + ('down', 'up'): PolarizationChannel.MP, + ('down', 'down'): PolarizationChannel.MM, + ('u', 'u'): PolarizationChannel.PP, + ('u', 'd'): PolarizationChannel.PM, + ('d', 'u'): PolarizationChannel.MP, + ('d', 'd'): PolarizationChannel.MM, + ('plus', 'plus'): PolarizationChannel.PP, + ('plus', 'minus'): PolarizationChannel.PM, + ('minus', 'plus'): PolarizationChannel.MP, + ('minus', 'minus'): PolarizationChannel.MM, +} + + +class PolarizedDataSet: + """A polarized experiment: one dataset per measured spin channel, one shared model. + + Parameters + ---------- + name : str, optional + Name of the experiment. By default, 'PolarizedSeries'. + channels : dict[PolarizationChannel | str, DataSet1D] + The measured channels; at least one. Keys are 'pp', 'pm', 'mp', 'mm' + (or the corresponding enum members). Two-channel NSF-only experiments + simply provide 'pp' and 'mm'. + model : Model, optional + The model shared by all channels. By default, None. + """ + + def __init__( + self, + name: str = 'PolarizedSeries', + channels: Optional[dict[Union[PolarizationChannel, str], DataSet1D]] = None, + model=None, + ): + if not channels: + raise ValueError('A PolarizedDataSet requires at least one channel dataset.') + normalized: dict[PolarizationChannel, DataSet1D] = {} + for channel, dataset in channels.items(): + channel = PolarizationChannel(channel) + if channel in normalized: + raise ValueError(f"Duplicate channel '{channel.value}'.") + normalized[channel] = self._validated_dataset(channel, dataset) + self._channels = self._in_canonical_order(normalized) + self.name = name + self.model = model + + @staticmethod + def _validated_dataset(channel: PolarizationChannel, dataset: DataSet1D) -> DataSet1D: + if not isinstance(dataset, DataSet1D): + raise ValueError(f"Channel '{channel.value}' must be a DataSet1D, got {type(dataset).__name__}.") + return dataset + + @staticmethod + def _in_canonical_order(channels: dict[PolarizationChannel, DataSet1D]) -> dict[PolarizationChannel, DataSet1D]: + # Canonical channel order: pp, pm, mp, mm. + return {channel: channels[channel] for channel in POLARIZATION_CHANNEL_TO_INDEX if channel in channels} + + @property + def model(self): + """The model shared by all channels.""" + return self._model + + @model.setter + def model(self, new_model) -> None: + self._model = new_model + for dataset in self._channels.values(): + dataset.model = new_model + + @property + def channels(self) -> Mapping[PolarizationChannel, DataSet1D]: + """The measured channels, in canonical order (pp, pm, mp, mm). + + Read-only view: use :meth:`set_channel` / :meth:`remove_channel` to + modify the channel set, so validation and model propagation apply. + """ + return MappingProxyType(self._channels) + + @property + def available_channels(self) -> list[PolarizationChannel]: + """The measured channels, in canonical order (pp, pm, mp, mm).""" + return list(self._channels.keys()) + + def set_channel(self, channel: Union[PolarizationChannel, str], dataset: DataSet1D) -> None: + """Add or replace one channel dataset. + + The dataset is validated, adopts the shared model, and the canonical + channel order is preserved. + + Parameters + ---------- + channel : Union[PolarizationChannel, str] + One of 'pp', 'pm', 'mp', 'mm' (or the corresponding enum member). + dataset : DataSet1D + The channel data. + """ + channel = PolarizationChannel(channel) + dataset = self._validated_dataset(channel, dataset) + dataset.model = self._model + merged = dict(self._channels) + merged[channel] = dataset + self._channels = self._in_canonical_order(merged) + + def remove_channel(self, channel: Union[PolarizationChannel, str]) -> None: + """Remove one channel dataset; the last remaining channel cannot be removed. + + Parameters + ---------- + channel : Union[PolarizationChannel, str] + One of 'pp', 'pm', 'mp', 'mm' (or the corresponding enum member). + """ + channel = PolarizationChannel(channel) + if channel not in self._channels: + raise ValueError(f"No '{channel.value}' channel in this dataset.") + if len(self._channels) == 1: + raise ValueError('A PolarizedDataSet requires at least one channel dataset.') + del self._channels[channel] + + def __getitem__(self, channel: Union[PolarizationChannel, str]) -> DataSet1D: + return self._channels[PolarizationChannel(channel)] + + def __contains__(self, channel: Union[PolarizationChannel, str]) -> bool: + try: + return PolarizationChannel(channel) in self._channels + except ValueError: + return False + + def __len__(self) -> int: + return len(self._channels) + + @property + def is_experiment(self) -> bool: + """Is experiment.""" + return self._model is not None + + @property + def is_simulation(self) -> bool: + """Is simulation.""" + return self._model is None + + def __repr__(self) -> str: + channel_names = ', '.join(channel.value for channel in self._channels) + return f"Polarized dataset '{self.name}' with channels: {channel_names}" + + +def detect_polarization_channel(path: str) -> Optional[PolarizationChannel]: + """Detect the spin channel of a data file. + + Tries the ORSO header (`data_source.measurement.instrument_settings.polarization`) + first, then filename heuristics ('_uu'/'_pp'/'_up' → pp, '_dd'/'_mm'/'_down' → mm, + '_ud'/'_pm' → pm, '_du'/'_mp' → mp, ...). Returns None when neither yields a + channel; a GUI should then ask the user to assign one. + + Parameters + ---------- + path : str + Path to the data file. + + Returns + ------- + Optional[PolarizationChannel] + The detected channel, or None. + """ + header_declared, channel = _channel_from_orso_header(path) + if channel is not None: + return channel + if header_declared: + # The header explicitly declares a non-channel polarization (e.g. + # 'unpolarized'): trust it over any channel-looking filename tokens. + return None + return _channel_from_filename(path) + + +def _channel_from_orso_header(path: str) -> tuple[bool, Optional[PolarizationChannel]]: + """Read the polarization of the first dataset in an ORSO file. + + Returns + ------- + tuple[bool, Optional[PolarizationChannel]] + (header declares a polarization, mapped channel or None). The flag is + False when the file is unreadable or carries no polarization field. + """ + try: + from orsopy.fileio import orso + + orso_data = orso.load_orso(str(path)) + polarization = orso_data[0].info.data_source.measurement.instrument_settings.polarization + except Exception: + return False, None + if polarization is None: + return False, None + value = getattr(polarization, 'value', polarization) + return True, _ORSO_POLARIZATION_TO_CHANNEL.get(str(value).lower()) + + +def _channel_from_filename(path: str) -> Optional[PolarizationChannel]: + """Guess the spin channel from separator-delimited tokens in the file name.""" + basename = os.path.splitext(os.path.basename(str(path)))[0].lower() + tokens = [token for token in re.split(r'[^a-z0-9]+', basename) if token] + + # Prefer the most specific match, scanning from the end (suffix convention). + for token in reversed(tokens): + if token in _TOKEN_TO_CHANNEL: + return _TOKEN_TO_CHANNEL[token] + for first, second in reversed(list(zip(tokens, tokens[1:]))): + if (first, second) in _TOKEN_PAIR_TO_CHANNEL: + return _TOKEN_PAIR_TO_CHANNEL[(first, second)] + for token in reversed(tokens): + if token in _SINGLE_TOKEN_TO_CHANNEL: + return _SINGLE_TOKEN_TO_CHANNEL[token] + return None diff --git a/src/easyreflectometry/fitting.py b/src/easyreflectometry/fitting.py index 0efaaaa5..37ba637e 100644 --- a/src/easyreflectometry/fitting.py +++ b/src/easyreflectometry/fitting.py @@ -14,6 +14,7 @@ from easyscience.fitting.multi_fitter import MultiFitter as EasyScienceMultiFitter from easyreflectometry.data import DataSet1D +from easyreflectometry.data import PolarizedDataSet from easyreflectometry.model import Model _VALID_OBJECTIVES = ('legacy_mask', 'mighell', 'hybrid', 'auto') @@ -161,6 +162,93 @@ def _fit_result_reduced_chi(result: FitResults, n_points: int | None = None) -> raise AttributeError('FitResults object has neither reduced_chi nor reduced_chi2') +def _bind_fit_func(func: Callable, unique_name: str) -> Callable: + """Bind a model's fit function to its ``unique_name`` for ``EasyScienceMultiFitter``. + + ``EasyScienceMultiFitter`` calls each fit function positionally as + ``func(x, *extra_args)``; the model's ``interface.fit_func`` expects its + ``unique_name`` as that extra positional argument, so it has to be closed + over here rather than passed through the fitter's call signature. + """ + + def wrapped(*args, **kwargs): + return func(*args, unique_name, **kwargs) + + return wrapped + + +def _emit_array_prep_warnings(stats: dict, y_vals: np.ndarray, label: str, *, action: str = 'fitting', extra: str = '') -> None: + """Warn about zero-variance handling applied by :func:`_prepare_fit_arrays`. + + Parameters + ---------- + stats : dict + The ``stats`` dict returned by :func:`_prepare_fit_arrays`. + y_vals : np.ndarray + The original (pre-transform) y values, used for the "all points" count. + label : str + Identifies what was fitted/sampled, e.g. ``'reflectivity 1'`` or + ``'channel pp'``. + action : str, optional + Verb describing the operation, e.g. ``'fitting'`` or ``'sampling'``. By default, 'fitting'. + extra : str, optional + Extra sentence(s) appended to the Mighell-related warnings (e.g. a + likelihood-validity caveat for MCMC). By default, ''. + """ + if stats['masked'] > 0: + warnings.warn( + f'Masked {stats["masked"]} data point(s) in {label} due to zero variance during {action}.', + UserWarning, + ) + if stats.get('transformed_all_points'): + warnings.warn( + f'Applied Mighell transform to all {len(y_vals)} point(s) in {label} during {action}.{extra}', + UserWarning, + ) + elif stats['mighell_substituted'] > 0: + warnings.warn( + f'Applied Mighell substitution to {stats["mighell_substituted"]} ' + f'zero-variance point(s) in {label} during {action}.{extra}', + UserWarning, + ) + + +def _classical_metrics_for(original: dict, model_curve: np.ndarray, result: FitResults, n_points: int | None = None) -> dict: + """Assemble the classical (positive-variance-only) and objective-space fit metrics. + + Parameters + ---------- + original : dict + Dict with keys ``'y'`` and ``'variances'`` holding the un-transformed + observed values and their variances (σ²). + model_curve : np.ndarray + Model evaluated at the original x values. + result : FitResults + The minimizer's result for this dataset/channel. + n_points : int | None, optional + Number of points actually fitted, used as the ``reduced_chi``/``reduced_chi2`` + fallback's point count. If ``None``, derived from ``result.x``. By default, None. + + Returns + ------- + dict + Keys ``'classical_chi2'``, ``'classical_reduced_chi'``, + ``'objective_chi2'``, ``'objective_reduced_chi'``, ``'n_classical_points'``. + """ + sigma_classical = np.sqrt(np.clip(original['variances'], 0.0, None)) + n_classical_points = int(np.sum(original['variances'] > 0.0)) + classical_chi2 = _compute_weighted_chi2(original['y'], model_curve, sigma_classical) + if n_points is None: + n_points = np.size(result.x) + return { + 'classical_chi2': classical_chi2, + 'classical_reduced_chi': _compute_reduced_chi2(classical_chi2, n_classical_points, result.n_pars), + 'objective_chi2': float(result.chi2), + 'objective_reduced_chi': _fit_result_reduced_chi(result, n_points), + 'n_classical_points': n_classical_points, + } + + class MultiFitter: def __init__(self, *args: Model, objective: str = 'hybrid'): r"""A convenience class for the :py:class:`easyscience.Fitting.Fitting` @@ -179,23 +267,100 @@ def __init__(self, *args: Model, objective: str = 'hybrid'): ``'auto'`` (alias for ``'hybrid'``). By default, 'hybrid'. """ - # This lets the unique_name be passed with the fit_func. - def func_wrapper(func, unique_name): - """Func wrapper.""" - - def wrapped(*args, **kwargs): - """Wrapped function.""" - return func(*args, unique_name, **kwargs) - - return wrapped - - self._fit_func = [func_wrapper(m.interface.fit_func, m.unique_name) for m in args] + self._fit_func = [_bind_fit_func(m.interface.fit_func, m.unique_name) for m in args] self._models = args self.easy_science_multi_fitter = EasyScienceMultiFitter(args, self._fit_func) self._fit_results: list[FitResults] | None = None self._classical_fit_metrics: list[dict] | None = None self._objective = _validate_objective(objective) self._sampler: Sampler | None = None + # Set by `for_experiments`: the datasets the fit functions correspond + # to, and the spin channel each one is evaluated on (None = unpolarized). + self.fit_datasets: list[DataSet1D] = [] + self.fit_channels: list[Any] = [] + + @classmethod + def for_experiments( + cls, + experiments: list[DataSet1D | PolarizedDataSet], + objective: str = 'hybrid', + ) -> 'MultiFitter': + """Build a fitter for a mixed list of unpolarized and polarized experiments. + + Every experiment contributes one fit function per dataset: an ordinary + experiment one, a polarized experiment one per measured spin channel, + each evaluating that channel's spin cross-section against the single + model the channels share. Structural parameters are therefore common to + all channels and the magnetic ones are constrained by all of them at + once, exactly as in :meth:`fit_polarized` — but here several experiments + (and several models) can be fitted together, which is what an + application's "fit everything that is loaded" action needs. + + The resulting fitter is *not* run: the caller supplies the data arrays + to ``easy_science_multi_fitter.fit(...)`` in the order given by + :attr:`fit_datasets`, which lets a GUI drive it from a worker thread. + + Note + ---- + Built via ``cls(*models, objective=objective)`` and then overwrites + ``_fit_func`` / ``easy_science_multi_fitter`` with the per-channel + versions — the ``__init__``-built pair is briefly constructed and + discarded. Unlike :meth:`fit_polarized`, the minimizer selection and + its ``tolerance`` / ``max_evaluations`` are *not* carried over: the + returned fitter starts from ``easy_science_multi_fitter``'s defaults, + so a caller that needs a specific minimizer must set it explicitly + before calling ``.fit(...)``. + + Parameters + ---------- + experiments : list[DataSet1D | PolarizedDataSet] + The loaded experiments, in the order they should be fitted. + objective : str, optional + Zero-variance handling strategy, see :meth:`__init__`. By default, 'hybrid'. + + Returns + ------- + MultiFitter + Fitter whose ``easy_science_multi_fitter`` has one fit function per + entry of ``fit_datasets``, with ``fit_channels`` holding the + matching :class:`PolarizationChannel` (``None`` when unpolarized). + """ + if not experiments: + raise ValueError('At least one experiment is required to build a fitter.') + + models: list[Model] = [] + datasets: list[DataSet1D] = [] + channels: list[Any] = [] + for experiment in experiments: + model = experiment.model + if model is None: + raise ValueError(f"Experiment '{getattr(experiment, 'name', experiment)}' has no model to fit.") + # `in` would compare models by value; identity is what matters here. + if not any(model is known for known in models): + models.append(model) + experiment_channels = getattr(experiment, 'available_channels', None) + if experiment_channels is None: + datasets.append(experiment) + channels.append(None) + continue + for channel in experiment_channels: + datasets.append(experiment[channel]) + channels.append(channel) + + fitter = cls(*models, objective=objective) + + fit_funcs = [] + for dataset, channel in zip(datasets, channels): + model = dataset.model + interface = model.interface + func = interface.fit_func if channel is None else interface.fit_func_for_channel(channel) + fit_funcs.append(_bind_fit_func(func, model.unique_name)) + + fitter._fit_func = fit_funcs + fitter.easy_science_multi_fitter = EasyScienceMultiFitter(models, fit_funcs) + fitter.fit_datasets = datasets + fitter.fit_channels = channels + return fitter def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> sc.DataGroup: """Perform the fitting and populate the DataGroups with the result. @@ -230,23 +395,7 @@ def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> variances = data['data'][f'R_{i}'].variances x_out, y_eff, weights, stats = _prepare_fit_arrays(x_vals, y_vals, variances, obj) - - if stats['masked'] > 0: - warnings.warn( - f'Masked {stats["masked"]} data point(s) in reflectivity {i} due to zero variance during fitting.', - UserWarning, - ) - if stats.get('transformed_all_points'): - warnings.warn( - f'Applied Mighell transform to all {len(y_vals)} point(s) in reflectivity {i} during fitting.', - UserWarning, - ) - elif stats['mighell_substituted'] > 0: - warnings.warn( - f'Applied Mighell substitution to {stats["mighell_substituted"]} ' - f'zero-variance point(s) in reflectivity {i} during fitting.', - UserWarning, - ) + _emit_array_prep_warnings(stats, y_vals, f'reflectivity {i}') x.append(x_out) y.append(y_eff) @@ -270,27 +419,14 @@ def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> values=sld_profile[0], unit=(1 / new_data['coords'][f'Qz_{id}'].unit).unit, ) - original = original_arrays[i] - sigma_classical = np.sqrt(np.clip(original['variances'], 0.0, None)) - n_classical_points = int(np.sum(original['variances'] > 0.0)) - classical_chi2 = _compute_weighted_chi2(original['y'], model_curve, sigma_classical) - classical_reduced_chi = _compute_reduced_chi2(classical_chi2, n_classical_points, result[i].n_pars) - objective_chi2 = float(result[i].chi2) - objective_reduced_chi = _fit_result_reduced_chi(result[i], np.size(result[i].x)) - - self._classical_fit_metrics.append({ - 'classical_chi2': classical_chi2, - 'classical_reduced_chi': classical_reduced_chi, - 'objective_chi2': objective_chi2, - 'objective_reduced_chi': objective_reduced_chi, - 'n_classical_points': n_classical_points, - }) - - new_data['objective_chi2'] = objective_chi2 - new_data['objective_reduced_chi'] = objective_reduced_chi - new_data['classical_chi2'] = classical_chi2 - new_data['classical_reduced_chi'] = classical_reduced_chi - new_data['reduced_chi'] = objective_reduced_chi + metrics = _classical_metrics_for(original_arrays[i], model_curve, result[i]) + self._classical_fit_metrics.append(metrics) + + new_data['objective_chi2'] = metrics['objective_chi2'] + new_data['objective_reduced_chi'] = metrics['objective_reduced_chi'] + new_data['classical_chi2'] = metrics['classical_chi2'] + new_data['classical_reduced_chi'] = metrics['classical_reduced_chi'] + new_data['reduced_chi'] = metrics['objective_reduced_chi'] new_data['success'] = result[i].success return new_data @@ -318,45 +454,111 @@ def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) variances = np.asarray(data.ye) x_out, y_eff, weights, stats = _prepare_fit_arrays(x_vals, y_vals, variances, obj) - - if stats['masked'] > 0: - warnings.warn( - f'Masked {stats["masked"]} data point(s) in single-dataset fit due to zero variance during fitting.', - UserWarning, - ) - if stats.get('transformed_all_points'): - warnings.warn( - f'Applied Mighell transform to all {len(y_vals)} point(s) in single-dataset fit during fitting.', - UserWarning, - ) - elif stats['mighell_substituted'] > 0: - warnings.warn( - f'Applied Mighell substitution to {stats["mighell_substituted"]} ' - 'zero-variance point(s) in single-dataset fit during fitting.', - UserWarning, - ) + _emit_array_prep_warnings(stats, y_vals, 'single-dataset fit') if obj == 'legacy_mask' and len(x_out) == 0: raise ValueError('Cannot fit single dataset: all points have zero variance.') result = self.easy_science_multi_fitter.fit(x=[x_out], y=[y_eff], weights=[weights])[0] self._fit_results = [result] - sigma_classical = np.sqrt(np.clip(variances, 0.0, None)) model_curve = self._fit_func[0](x_vals) - n_classical_points = int(np.sum(variances > 0.0)) - classical_chi2 = _compute_weighted_chi2(y_vals, model_curve, sigma_classical) - classical_reduced_chi = _compute_reduced_chi2(classical_chi2, n_classical_points, result.n_pars) self._classical_fit_metrics = [ - { - 'classical_chi2': classical_chi2, - 'classical_reduced_chi': classical_reduced_chi, - 'objective_chi2': float(result.chi2), - 'objective_reduced_chi': _fit_result_reduced_chi(result, len(x_out)), - 'n_classical_points': n_classical_points, - } + _classical_metrics_for({'y': y_vals, 'variances': variances}, model_curve, result, n_points=len(x_out)) ] return result + def fit_polarized(self, data: PolarizedDataSet, objective: str | None = None) -> dict[str, FitResults]: + """Fit all measured spin channels of a polarized experiment simultaneously. + + Each channel dataset gets its own fit function evaluating the + corresponding spin cross-section, while every channel shares the single + model — so structural parameters (thickness, roughness, nuclear SLD, + scale, background) are common, and the magnetic parameters + (`rho_m`/`theta_m`) are constrained by all channels at once. The refl1d + backend computes all four cross-sections in one kernel evaluation and + caches them per iteration, so fitting N channels costs about as much as + fitting one. + + Parameters + ---------- + data : PolarizedDataSet + The polarized experiment (its model must be the model this fitter + was constructed with). Note that per-channel ``ye`` stores + variances (σ²), not standard deviations. + objective : str | None, optional + Per-call override for the zero-variance objective. + If ``None``, uses the instance default set at construction. By default, None. + + Returns + ------- + dict[str, FitResults] + Fit results per channel, keyed 'pp', 'pm', 'mp', 'mm' (measured + channels only, in that order). + + Note + ---- + Unlike :meth:`for_experiments`, this method does not populate + :attr:`fit_datasets` / :attr:`fit_channels` — those are set only by the + caller-driven, `for_experiments`-built flow. + """ + obj = _validate_objective(objective) if objective is not None else self._objective + if len(self._models) != 1: + raise ValueError('Polarized fitting requires a MultiFitter constructed with exactly one model.') + model = self._models[0] + if data.model is not model: + raise ValueError('PolarizedDataSet.model must be the model this fitter was constructed with.') + channels = data.available_channels + for channel in channels: + if data[channel].model is not model: + raise ValueError(f"The '{channel.value}' channel dataset is bound to a different model than the fitter's.") + + channel_fit_funcs = [ + _bind_fit_func(model.interface.fit_func_for_channel(channel), model.unique_name) for channel in channels + ] + # One fit function per channel, all bound to the single model. Constructed + # per call because the channel set comes from the data; the minimizer + # selection and its generic settings (tolerance, max_evaluations) are + # carried over. Engine-specific settings applied directly to the minimizer + # instance of this fitter would not be. + polarized_fitter = EasyScienceMultiFitter([model], channel_fit_funcs) + polarized_fitter.switch_minimizer(self.easy_science_multi_fitter.minimizer.enum) + polarized_fitter.tolerance = self.easy_science_multi_fitter.tolerance + polarized_fitter.max_evaluations = self.easy_science_multi_fitter.max_evaluations + + x = [] + y = [] + dy = [] + original_arrays = [] + for channel in channels: + dataset = data[channel] + x_vals = np.asarray(dataset.x) + y_vals = np.asarray(dataset.y) + variances = np.asarray(dataset.ye) + + x_out, y_eff, weights, stats = _prepare_fit_arrays(x_vals, y_vals, variances, obj) + _emit_array_prep_warnings(stats, y_vals, f'channel {channel.value}') + if obj == 'legacy_mask' and len(x_out) == 0: + raise ValueError(f'Cannot fit channel {channel.value}: all points have zero variance.') + + x.append(x_out) + y.append(y_eff) + dy.append(weights) + original_arrays.append({'x': x_vals, 'y': y_vals, 'variances': variances}) + + results = polarized_fitter.fit(x, y, weights=dy) + # All channels are fitted against one parameter vector (the shared model), + # so `result.n_pars` is identical across `results`; `reduced_chi` and + # `classical_reduced_chi` below rely on that invariant. + self._fit_results = list(results) + + self._classical_fit_metrics = [] + for index, (channel, result) in enumerate(zip(channels, results)): + original = original_arrays[index] + model_curve = channel_fit_funcs[index](original['x']) + self._classical_fit_metrics.append(_classical_metrics_for(original, model_curve, result)) + + return {channel.value: result for channel, result in zip(channels, results)} + def mcmc_sample( self, data: sc.DataGroup, @@ -425,27 +627,16 @@ def mcmc_sample( ) x_out, y_eff, weights, stats = _prepare_fit_arrays(x_vals, y_vals, variances, obj) - - if stats['masked'] > 0: - warnings.warn( - f'Masked {stats["masked"]} data point(s) in reflectivity {i} due to zero variance during sampling.', - UserWarning, - ) - if stats.get('transformed_all_points'): - warnings.warn( - f'Applied Mighell transform to all {len(y_vals)} point(s) in reflectivity {i} during sampling. ' - 'The Mighell transform is a chi-square bias correction, not a true likelihood; ' - 'posterior widths may be unreliable.', - UserWarning, - ) - elif stats['mighell_substituted'] > 0: - warnings.warn( - f'Applied Mighell substitution to {stats["mighell_substituted"]} ' - f'zero-variance point(s) in reflectivity {i} during sampling. ' - 'The Mighell transform is a chi-square bias correction, not a true likelihood; ' - 'posterior widths may be unreliable.', - UserWarning, - ) + _emit_array_prep_warnings( + stats, + y_vals, + f'reflectivity {i}', + action='sampling', + extra=( + ' The Mighell transform is a chi-square bias correction, not a true likelihood; ' + 'posterior widths may be unreliable.' + ), + ) x.append(x_out) y.append(y_eff) dy.append(weights) @@ -542,6 +733,27 @@ def objective_reduced_chi(self) -> float | None: """Objective-space reduced chi-squared returned by the minimizer.""" return self.reduced_chi + def record_fit_results(self, results: list[FitResults] | None) -> None: + """Adopt fit results produced elsewhere, so this fitter reports on them. + + An application that drives ``easy_science_multi_fitter.fit(...)`` itself + — to run it in a worker thread, for instance — leaves the ``MultiFitter`` + that owns the goodness-of-fit properties none the wiser. Handing the + results back here makes :attr:`chi2` / :attr:`reduced_chi` describe that + fit instead of reporting "no fit performed". + + Only the minimizer-reported metrics are restored: the classical ones + need the original data arrays, which are not part of ``FitResults``, so + :attr:`classical_chi2` and :attr:`classical_reduced_chi` stay None. + + Parameters + ---------- + results : list[FitResults] | None + Results of the fit, one per fitted dataset. None clears them. + """ + self._fit_results = list(results) if results else None + self._classical_fit_metrics = None + def switch_minimizer(self, minimizer: AvailableMinimizers) -> None: """Switch the minimizer for the fitting. diff --git a/src/easyreflectometry/limits.py b/src/easyreflectometry/limits.py index 001bba64..b41a37a1 100644 --- a/src/easyreflectometry/limits.py +++ b/src/easyreflectometry/limits.py @@ -17,14 +17,15 @@ def apply_default_limits(parameter: Parameter, kind: str) -> None: parameter : Parameter The parameter to adjust. kind : str - One of 'thickness', 'roughness', 'sld', 'isld', 'scale'. + One of 'thickness', 'roughness', 'sld', 'isld', 'rho_m', 'scale'. """ if not parameter.independent: return if kind in ('thickness', 'roughness'): _apply_percentage_limits(parameter) - elif kind in ('sld', 'isld'): + elif kind in ('sld', 'isld', 'rho_m'): + # A magnetic SLD is an SLD: same physical scale, same default window. _apply_fixed_limits(parameter, *SLD_LIMITS) elif kind == 'scale': _apply_fixed_limits(parameter, *SCALE_LIMITS) diff --git a/src/easyreflectometry/model/model.py b/src/easyreflectometry/model/model.py index da1396d0..993dc3e8 100644 --- a/src/easyreflectometry/model/model.py +++ b/src/easyreflectometry/model/model.py @@ -196,6 +196,11 @@ def remove_assembly(self, index: int) -> None: if self.interface is not None: self.interface().remove_item_from_model(assembly_unique_name, self.unique_name) + @property + def has_magnetism(self) -> bool: + """Whether any layer in the sample carries magnetic properties.""" + return any(getattr(layer, 'magnetism', None) is not None for assembly in self.sample for layer in assembly.layers) + @property def is_default(self) -> bool: """Whether this model was created as a default placeholder.""" diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index aa0eacf6..1c7fd29e 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -19,7 +19,11 @@ from scipp import DataGroup from easyreflectometry.calculators import CalculatorFactory +from easyreflectometry.calculators import PolarizationChannel +from easyreflectometry.calculators.calculator_base import CalculatorBase from easyreflectometry.data import DataSet1D +from easyreflectometry.data import PolarizedDataSet +from easyreflectometry.data import detect_polarization_channel from easyreflectometry.data import load_as_dataset from easyreflectometry.data.measurement import extract_orso_title from easyreflectometry.data.measurement import load_data_from_orso_file @@ -42,6 +46,33 @@ Q_MAX = 0.3 Q_RESOLUTION = 500 +# Guide-field angle A in degrees, used to project the moment onto the neutron +# quantisation axis for the spin-up/spin-down potentials and nothing else. +# refl1d's default (`Aguide = 270`), which is what every model the library can +# currently build uses — `Aguide` is not exposed as a parameter yet. Keep this +# the single source of the value: exposing it later is a change here only. +GUIDE_FIELD_ANGLE = 270.0 + +# Points whose spin-asymmetry denominator (R⁺⁺ + R⁻⁻) is smaller than this +# multiple of its own uncertainty are dropped: there SA is noise divided by +# noise and would swamp the axis with ±values of no physical meaning. +SPIN_ASYMMETRY_SIGNIFICANCE = 3.0 + +# Points whose spin-asymmetry denominator (R⁺⁺ + R⁻⁻) is smaller than this +# fraction of |R⁺⁺| + |R⁻⁻| are dropped too: there the sum is what is left after +# cancellation between the two channels, so SA is a ratio of rounding noise. +# Unlike the significance rule above this needs no uncertainties, which is what +# keeps a two-column file from putting ±10³ values on the axis. +SPIN_ASYMMETRY_CANCELLATION_FRACTION = 1e-3 + +# A depth whose magnetic SLD is below this fraction of the largest one in the +# profile carries no moment worth speaking of, and its moment *angle* is +# meaningless — the direction of a (nearly) zero-length vector. Used to restrict +# the reported theta_m profile; a relative floor because it has to work both for +# a weak 0.1 and a strong 5 (1e-6 A^-2) moment, and because the interface +# roughness leaves a small erf tail everywhere. +MAGNETIC_MOMENT_FLOOR_FRACTION = 0.01 + DEFAULT_MINIMIZER = AvailableMinimizers.LMFit_leastsq @@ -130,6 +161,12 @@ def _sync_parameter_states(self) -> None: for layer in assembly.layers: self._sync_layer_parameter_state(layer.thickness, 'thickness', disabled_ids) self._sync_layer_parameter_state(layer.roughness, 'roughness', disabled_ids) + magnetism = getattr(layer, 'magnetism', None) + if magnetism is not None: + # Magnetic parameters exist only on magnetic layers, so + # they are never in `disabled_ids`; theta_m carries + # explicit 0-360 bounds and needs no default window. + self._sync_layer_parameter_state(magnetism.rho_m, 'rho_m', disabled_ids) def _sync_layer_parameter_state(self, parameter: Parameter, kind: str, disabled_ids: set[int]) -> None: """Update a layer parameter's enabled state and pending default limits.""" @@ -275,7 +312,11 @@ def models(self, models: ModelCollection) -> None: self._replace_collection(models, self._models) # Use setter to update indicies for current model, assembly and layer self.current_model_index = 0 - self._materials.extend(self._get_materials_in_models()) + # Only track materials not already in the project's material collection + # (e.g. layers built from self._materials, as in default_model(), would + # otherwise be re-added and trigger a spurious duplicate-item warning). + new_materials = [material for material in self._get_materials_in_models() if material not in self._materials] + self._materials.extend(new_materials) for model in self._models: model.interface = self._calculator self._sync_parameter_states() @@ -307,9 +348,46 @@ def calculator(self, calculator: str) -> None: for model in self._models: model.generate_bindings() + # The cached fitter holds fit functions bound to the previous backend. self._fitter = None self._fitter_model_index = None + @property + def calculator_supports_magnetism(self) -> bool: + """Whether the active calculator can model magnetic samples. + + The GUI uses this to gate magnetism-related controls (e.g. when the + refnx or bornagain backend is selected). + """ + return self._calculator().supports_magnetism + + @property + def calculators_supporting_magnetism(self) -> List[str]: + """Names of the available calculators that can model magnetic samples. + + Lets an application offer the switch a magnetic sample needs ("this + requires refl1d — change to it?") instead of only reporting that the + current calculator cannot do it. The active calculator is not touched. + """ + supporting = [] + for name in self._calculator.available_interfaces: + calculator = next( + (candidate for candidate in CalculatorBase._calculators if candidate.name == name), + None, + ) + if calculator is not None and calculator().supports_magnetism: + supporting.append(name) + return supporting + + @property + def models_have_magnetism(self) -> bool: + """Whether any model in the project carries a magnetic layer. + + A calculator without magnetism cannot be selected while this holds — the + binding it would have to build does not exist. + """ + return any(model.has_magnetism for model in self._models) + @property def minimizer(self) -> AvailableMinimizers: """Minimizer function.""" @@ -333,12 +411,12 @@ def minimizer(self, minimizer: AvailableMinimizers) -> None: self._fitter.easy_science_multi_fitter.switch_minimizer(minimizer) @property - def experiments(self) -> Dict[int, DataSet1D]: + def experiments(self) -> Dict[int, Union[DataSet1D, PolarizedDataSet]]: """Experiments function.""" return self._experiments @experiments.setter - def experiments(self, experiments: Dict[int, DataSet1D]) -> None: + def experiments(self, experiments: Dict[int, Union[DataSet1D, PolarizedDataSet]]) -> None: """Experiments function.""" self._experiments = experiments @@ -638,6 +716,92 @@ def load_all_experiments_from_file(self, path: Union[Path, str]) -> int: self._with_experiments = True return len(data_keys) + def suggest_polarized_channel_assignment(self, paths: List[Union[Path, str]]) -> Dict[str, Optional[PolarizationChannel]]: + """Suggest a spin-channel assignment for a set of data files. + + For each file, the ORSO header polarization is used when present, falling + back to filename heuristics ('_uu'/'_up' → pp, '_dd'/'_down' → mm, ...). + Files that cannot be identified map to None; a GUI should present the + result as an editable file → channel table. + + Parameters + ---------- + paths : List[Union[Path, str]] + Paths of the per-channel data files. + + Returns + ------- + Dict[str, Optional[PolarizationChannel]] + Detected channel (or None) per path. + """ + return {str(path): detect_polarization_channel(str(path)) for path in paths} + + def load_polarized_experiment( + self, + paths: Dict[Union[PolarizationChannel, str], Union[Path, str]], + model_index: Optional[int] = None, + ) -> int: + """Load a polarized experiment from one data file per spin channel. + + The channel datasets form a single :class:`PolarizedDataSet` experiment + sharing one model. Two-channel NSF-only experiments simply pass 'pp' and + 'mm' entries; spin-flip channels are optional. + + Parameters + ---------- + paths : Dict[Union[PolarizationChannel, str], Union[Path, str]] + Explicit channel → file assignment (e.g. from the GUI import dialog, + pre-filled via :meth:`suggest_polarized_channel_assignment`). + model_index : Optional[int], optional + Index of the model the experiment belongs to. By default, the + current model. + + Returns + ------- + int + Index of the newly loaded experiment, so a caller can make it + current. + """ + paths = {PolarizationChannel(channel): path for channel, path in paths.items()} + channels = {} + for channel, path in paths.items(): + # One file per channel means one dataset per file; a multi-dataset ORSO + # file has no defined channel-to-dataset assignment here. + if self.count_datasets_in_file(path) > 1: + raise ValueError( + f"File '{path}' contains multiple datasets; polarized loading requires one dataset " + f'per channel file. Export the {channel.value} channel to its own file.' + ) + dataset = load_as_dataset(str(path)) + # Keep the source file visible per channel (file → channel provenance). + dataset.name = f'{channel.value}: {Path(path).name}' + channels[channel] = dataset + + new_index = len(self._experiments) + if model_index is None: + model_index = self._current_model_index + model = self.models[model_index] + + experiment = PolarizedDataSet( + name=f'Polarized experiment {new_index}', + channels=channels, + model=model, + ) + # Name from the ORSO title of the first file, when available. + first_channel = experiment.available_channels[0] + first_path = paths[first_channel] + self._apply_experiment_metadata(first_path, experiment, f'Polarized experiment {new_index}') + + # Background and resolution follow the first (in canonical order) channel; + # per-channel resolution functions are not supported (one per experiment). + first_dataset = experiment[first_channel] + self._auto_set_background(first_dataset) + self._apply_resolution_function(first_dataset, model) + + self._experiments[new_index] = experiment + self._with_experiments = True + return new_index + def load_experiment_for_model_at_index(self, path: Union[Path, str], index: Optional[int] = 0) -> None: """Load experiment for model at index.""" experiment = load_as_dataset(str(path)) @@ -649,9 +813,21 @@ def load_experiment_for_model_at_index(self, path: Union[Path, str], index: Opti self._with_experiments = True self._apply_resolution_function(experiment, self._models[index]) + def _bind_calculator(self, model) -> None: + """Bind the project's calculator to a model unless it already is. + + Reassigning ``model.interface`` re-propagates the interface over the + whole sample tree — an expensive rebuild. The plot getters run on every + chart refresh, so an already-bound model must be left alone; engine + switches go through the ``calculator`` setter, which regenerates the + bindings itself. + """ + if model.interface is not self._calculator: + model.interface = self._calculator + def sld_data_for_model_at_index(self, index: int = 0) -> DataSet1D: """Sld data for model at index.""" - self.models[index].interface = self._calculator + self._bind_calculator(self.models[index]) sld = self.models[index].interface().sld_profile(self._models[index].unique_name) return DataSet1D( name=f'SLD for Model {index}', @@ -659,6 +835,74 @@ def sld_data_for_model_at_index(self, index: int = 0) -> DataSet1D: y=sld[1], ) + def model_has_magnetism_at_index(self, index: int = 0) -> bool: + """Whether the model at index carries magnetism (False when there is no such model).""" + try: + return bool(self.models[index].has_magnetism) + except (IndexError, AttributeError): + return False + + def magnetic_sld_data_for_model_at_index(self, index: int = 0) -> Dict[str, DataSet1D]: + """Nuclear and magnetic depth profiles of a magnetic model. + + Parameters + ---------- + index : int + Index of the model. + + Returns + ------- + Dict[str, DataSet1D] + Profiles versus depth z, keyed: + + - ``'sld'``: nuclear ρ(z), the same curve as + :meth:`sld_data_for_model_at_index`; + - ``'rho_m'``: magnetic SLD ρM(z); + - ``'theta_m'``: in-plane moment angle θM(z) in degrees, restricted + to the depths that carry a moment — the angle of a zero-length + vector is meaningless, so points below + :data:`MAGNETIC_MOMENT_FLOOR_FRACTION` of the largest ρM are left + out instead of drawing an arbitrary angle through vacuum; + - ``'spin_up'`` / ``'spin_down'``: the potentials each spin state + sees, ρ(z) ± ρM(z)·cos(θM(z) − A), where A is the guide-field + angle :data:`GUIDE_FIELD_ANGLE`. + + Raises + ------ + ValueError + The model has no magnetic layer, so there is no magnetic profile. + NotImplementedError + The active calculator cannot model magnetism. + """ + model = self.models[index] + if not model.has_magnetism: + raise ValueError( + f'Model {index} has no magnetic layer; there is no magnetic SLD profile to show. ' + 'Attach magnetism to a layer first.' + ) + self._bind_calculator(model) + z, sld, rho_m, theta_m = model.interface().magnetic_sld_profile(model.unique_name) + z = np.asarray(z, dtype=float) + sld = np.asarray(sld, dtype=float) + rho_m = np.asarray(rho_m, dtype=float) + theta_m = np.asarray(theta_m, dtype=float) + # Component of the moment along the guide field: what the neutron spin + # states add to / subtract from the nuclear potential. + projection = rho_m * np.cos(np.radians(theta_m - GUIDE_FIELD_ANGLE)) + # Only report the angle where there is a moment to have an angle, and + # make it continuous along z: 359° followed by 1° is a 2° turn, but a + # plotted line through the wrapped values sweeps the whole circle. + magnitude = np.abs(rho_m) + has_moment = magnitude > MAGNETIC_MOMENT_FLOOR_FRACTION * magnitude.max(initial=0.0) + theta_display = _unwrapped_angle(theta_m, has_moment) + return { + 'sld': DataSet1D(name=f'SLD for Model {index}', x=z, y=sld), + 'rho_m': DataSet1D(name=f'Magnetic SLD for Model {index}', x=z, y=rho_m), + 'theta_m': DataSet1D(name=f'Moment angle for Model {index}', x=z[has_moment], y=theta_display[has_moment]), + 'spin_up': DataSet1D(name=f'Spin-up potential for Model {index}', x=z, y=sld + projection), + 'spin_down': DataSet1D(name=f'Spin-down potential for Model {index}', x=z, y=sld - projection), + } + def sample_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.array] = None) -> DataSet1D: """Sample data for model at index.""" original_resolution_function = self.models[index].resolution_function @@ -668,24 +912,268 @@ def sample_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.ar return reflectivity_data - def model_data_for_model_at_index(self, index: int = 0, q_range: Optional[np.array] = None) -> DataSet1D: - """Model data for model at index.""" + def model_data_for_model_at_index( + self, + index: int = 0, + q_range: Optional[np.array] = None, + channel: Optional[Union[PolarizationChannel, str]] = None, + ) -> DataSet1D: + """Model data for model at index. + + Parameters + ---------- + index : int + Index of the model. + q_range : Optional[np.array] + Points to calculate at; the project q range by default. + channel : Optional[Union[PolarizationChannel, str]] + Spin cross-section to calculate. `None` (the default) gives the + ordinary, channel-agnostic reflectivity. A channel requires a + magnetic model (except 'pp', which falls back to the unpolarized + calculation) and a calculator supporting magnetism, otherwise the + calculator raises. + """ if q_range is None: q_range = np.linspace(self.q_min, self.q_max, self.q_resolution) - self.models[index].interface = self._calculator - reflectivity = self.models[index].interface().reflectity_profile(q_range, self._models[index].unique_name) + self._bind_calculator(self.models[index]) + if channel is None: + reflectivity = self.models[index].interface().reflectity_profile(q_range, self._models[index].unique_name) + name = f'Reflectivity for Model {index}' + else: + channel = PolarizationChannel(channel) + reflectivity = ( + self.models[index].interface().reflectivity_profile_channel(q_range, self._models[index].unique_name, channel) + ) + name = f'Reflectivity ({channel.value}) for Model {index}' return DataSet1D( - name=f'Reflectivity for Model {index}', + name=name, x=q_range, y=reflectivity, ) - def experimental_data_for_model_at_index(self, index: int = 0) -> DataSet1D: - """Experimental data for model at index.""" - if index in self._experiments.keys(): - return self._experiments[index] - else: + def experimental_data_for_model_at_index( + self, + index: int = 0, + channel: Optional[Union[PolarizationChannel, str]] = None, + ) -> Union[DataSet1D, PolarizedDataSet]: + """Experimental data for model at index. + + Parameters + ---------- + index : int + Index of the experiment. + channel : Optional[Union[PolarizationChannel, str]] + Spin channel of a polarized experiment. `None` (the default) keeps + the historical behavior and returns the stored experiment as is: a + `DataSet1D` for an unpolarized experiment, the whole + `PolarizedDataSet` for a polarized one. + + Returns + ------- + Union[DataSet1D, PolarizedDataSet] + The experiment, or the `DataSet1D` of the requested channel. + + Raises + ------ + IndexError + No experiment is loaded at `index`. + KeyError + `channel` is a valid spin channel but was not measured. + ValueError + `channel` was given for an unpolarized experiment, or is not one of + 'pp', 'pm', 'mp', 'mm'. + """ + if index not in self._experiments.keys(): + raise IndexError(f'No experiment data for model at index {index}') + + experiment = self._experiments[index] + if channel is None: + return experiment + + if not isinstance(experiment, PolarizedDataSet): + raise ValueError( + f"Experiment at index {index} is not polarized; it has no '{channel}' channel. " + 'Call without a channel argument to get its data.' + ) + try: + channel = PolarizationChannel(channel) + except ValueError as exception: + known = ', '.join(member.value for member in PolarizationChannel) + raise ValueError(f"Unknown spin channel '{channel}'; expected one of {known}.") from exception + if channel not in experiment: + measured = ', '.join(member.value for member in experiment.available_channels) + raise KeyError(f"Channel '{channel.value}' was not measured in experiment {index} (measured: {measured}).") + return experiment[channel] + + def experiment_is_polarized_at_index(self, index: int = 0) -> bool: + """Whether the experiment at index holds per-channel (polarized) data. + + Returns False when no experiment is loaded at `index`, so consumers can + use it as a plain predicate. + """ + return isinstance(self._experiments.get(index), PolarizedDataSet) + + def experiment_channels_at_index(self, index: int = 0) -> List[PolarizationChannel]: + """Measured spin channels of the experiment at index ([] when unpolarized).""" + experiment = self._experiments.get(index) + if not isinstance(experiment, PolarizedDataSet): + return [] + return experiment.available_channels + + def experiment_supports_spin_asymmetry_at_index(self, index: int = 0) -> bool: + """Whether SA can be formed for the experiment at index. + + Spin asymmetry needs both non-spin-flip channels; an NSF-incomplete or + spin-flip-only experiment has no SA. The two channel datasets must also + be structurally usable (non-empty, matching lengths, a strictly ordered + q grid) — an application asks this before offering a spin-asymmetry + view, so a dataset that cannot be turned into one must not be advertised + and then fail. + """ + channels = self.experiment_channels_at_index(index) + if not _has_both_nsf_channels(channels): + return False + experiment = self._experiments[index] + try: + for channel in (PolarizationChannel.PP, PolarizationChannel.MM): + _ordered_channel_arrays(experiment[channel]) + except ValueError as exception: + logger.warning('Experiment %s cannot produce a spin asymmetry: %s', index, exception) + return False + return True + + def spin_asymmetry_for_experiment_at_index(self, index: int = 0) -> Dict[str, object]: + """Spin asymmetry SA = (R⁺⁺ − R⁻⁻) / (R⁺⁺ + R⁻⁻) of a polarized experiment. + + The measured SA is formed on the q grid of the pp channel. A mm channel + measured on a different grid is linearly interpolated onto it — values + with the usual weights, variances with the *squared* weights, which is + the propagation rule for independent endpoints (the covariance this + introduces between neighbouring SA points is not representable in + `DataSet1D` and is discarded). Interpolation happens **only inside the q + range both channels cover**: `np.interp` would otherwise clamp to the + edge value and turn extrapolated points into fabricated measurements. + + Points are dropped when the denominator cannot carry a meaningful + asymmetry: + + - it is not positive, or is lost to cancellation between the two + channels (see :data:`SPIN_ASYMMETRY_CANCELLATION_FRACTION`) — this + guard needs no uncertainties and is what keeps a background-subtracted + tail from throwing ±10³ values onto the axis; + - it is not above :data:`SPIN_ASYMMETRY_SIGNIFICANCE` times its own + uncertainty, when the channels carry usable uncertainties; + - the point itself is not usable (non-finite reflectivity, or a negative + or non-finite variance). + + Parameters + ---------- + index : int + Index of the experiment. + + Returns + ------- + Dict[str, object] + - ``'measured'``: `DataSet1D` of SA versus q. As everywhere in this + library, ``ye`` holds **variances**, not standard deviations. + - ``'calculated'``: `DataSet1D` of the model SA on the same q + points, or None when the model is not magnetic (an unpolarized + model has SA ≡ 0, which is not worth drawing). + - ``'masked_points'``: how many measured points were dropped for any + of the reasons above. + - ``'low_significance_points'``: of those, how many had a + denominator below the significance threshold. + - ``'small_denominator_points'``: of those, how many had a + denominator that was non-positive or lost to cancellation. + - ``'invalid_points'``: of those, how many carried a non-finite + reflectivity or an unusable variance. + - ``'out_of_overlap_points'``: number of pp points dropped because + the mm channel does not cover their q. + + Raises + ------ + IndexError + No experiment is loaded at `index`. + ValueError + The experiment does not carry both pp and mm channels, or their + datasets are not structurally usable. + """ + if index not in self._experiments.keys(): raise IndexError(f'No experiment data for model at index {index}') + channels = self.experiment_channels_at_index(index) + if not _has_both_nsf_channels(channels): + raise ValueError(f'Experiment {index} does not have both non-spin-flip channels; spin asymmetry needs pp and mm.') + + experiment = self._experiments[index] + q, r_pp, var_pp = _ordered_channel_arrays(experiment[PolarizationChannel.PP]) + q_mm, r_mm_source, var_mm_source = _ordered_channel_arrays(experiment[PolarizationChannel.MM]) + + out_of_overlap = 0 + if q.shape == q_mm.shape and np.allclose(q, q_mm, rtol=1e-9, atol=0.0): + # The usual case: both channels come from one instrument scan. + # A tolerance keeps grids that only differ by float round-trips + # (text files) on this path. + r_mm, var_mm = r_mm_source, var_mm_source + else: + # Different grids: put mm on the pp grid, but only where mm has data + # — np.interp clamps outside its range, which would silently invent + # measurements at the edges. + inside = (q >= q_mm.min()) & (q <= q_mm.max()) + out_of_overlap = int(np.count_nonzero(~inside)) + if out_of_overlap: + logger.warning( + 'Spin asymmetry of experiment %s: %s of %s pp points lie outside the mm q range ' + '[%.5g, %.5g] and are dropped.', + index, + out_of_overlap, + q.size, + q_mm.min(), + q_mm.max(), + ) + q, r_pp, var_pp = q[inside], r_pp[inside], var_pp[inside] + r_mm, var_mm = _interpolate_with_variance(q, q_mm, r_mm_source, var_mm_source) + + asymmetry, variance, keep, reasons = _spin_asymmetry(r_pp, r_mm, var_pp, var_mm) + measured = DataSet1D( + name=f'Spin asymmetry for Experiment {index}', + x=q[keep], + y=asymmetry[keep], + ye=variance[keep], + x_label='q (1/angstrom)', + y_label='Spin asymmetry', + ) + + calculated = None + model_index = self._model_index_for_experiment(experiment) + if model_index is not None and self.models[model_index].has_magnetism and measured.x.size > 0: + calculated_pp = self.model_data_for_model_at_index(model_index, q_range=measured.x, channel='pp').y + calculated_mm = self.model_data_for_model_at_index(model_index, q_range=measured.x, channel='mm').y + calculated_asymmetry, _, _, _ = _spin_asymmetry(calculated_pp, calculated_mm) + calculated = DataSet1D( + name=f'Calculated spin asymmetry for Experiment {index}', + x=measured.x, + y=calculated_asymmetry, + x_label='q (1/angstrom)', + y_label='Spin asymmetry', + ) + + return { + 'measured': measured, + 'calculated': calculated, + 'masked_points': int(np.count_nonzero(~keep)), + 'out_of_overlap_points': out_of_overlap, + **reasons, + } + + def _model_index_for_experiment(self, experiment) -> Optional[int]: + """Index of the model an experiment is bound to, or None.""" + model = getattr(experiment, 'model', None) + if model is None: + return None + for model_index, candidate in enumerate(self._models): + if candidate is model: + return model_index + return None def default_model(self): """Default model.""" @@ -902,6 +1390,9 @@ def _as_dict_add_experiments(self, project_dict: dict): project_dict['experiments_names'] = {} for key, experiment in self._experiments.items(): + if isinstance(experiment, PolarizedDataSet): + self._as_dict_add_polarized_experiment(project_dict, key, experiment) + continue project_dict['experiments'][key] = [ list(experiment.x), list(experiment.y), @@ -912,6 +1403,31 @@ def _as_dict_add_experiments(self, project_dict: dict): project_dict['experiments_models'][key] = experiment.model.name project_dict['experiments_names'][key] = experiment.name + @staticmethod + def _as_dict_add_polarized_experiment(project_dict: dict, key: int, experiment: PolarizedDataSet) -> None: + """Serialize a `PolarizedDataSet`: one (name, x, y, ye, xe) array set per measured channel. + + `experiments[key]` is a plain list for an ordinary `DataSet1D` (see + `_as_dict_add_experiments`); a dict here — tagged `'polarized': True` — + is how `_from_dict_extract_experiments` tells the two apart on load. + """ + project_dict['experiments'][key] = { + 'polarized': True, + 'channels': { + channel.value: [ + experiment[channel].name, + list(experiment[channel].x), + list(experiment[channel].y), + list(experiment[channel].ye), + list(experiment[channel].xe), + ] + for channel in experiment.available_channels + }, + } + if experiment.model is not None: + project_dict['experiments_models'][key] = experiment.model.name + project_dict['experiments_names'][key] = experiment.name + def from_dict(self, project_dict: dict): """From dict.""" keys = list(project_dict.keys()) @@ -955,21 +1471,45 @@ def from_dict(self, project_dict: dict): # Resolve any pending parameter dependencies (constraints) after all objects are loaded resolve_all_parameter_dependencies(self) - def _from_dict_extract_experiments(self, project_dict: dict) -> Dict[int, DataSet1D]: + def _from_dict_extract_experiments(self, project_dict: dict) -> Dict[int, Union[DataSet1D, PolarizedDataSet]]: """From dict extract experiments.""" experiments = {} - for key in project_dict['experiments'].keys(): + for key, raw in project_dict['experiments'].items(): + if isinstance(raw, dict) and raw.get('polarized'): + experiments[int(key)] = self._polarized_experiment_from_dict(key, raw, project_dict) + continue experiments[int(key)] = DataSet1D( name=project_dict['experiments_names'][key], - x=project_dict['experiments'][key][0], - y=project_dict['experiments'][key][1], - ye=project_dict['experiments'][key][2], - xe=project_dict['experiments'][key][3], + x=raw[0], + y=raw[1], + ye=raw[2], + xe=raw[3], model=self._models[project_dict['experiments_models'][key]], auto_background=False, ) return experiments + def _polarized_experiment_from_dict(self, key: str, raw: dict, project_dict: dict) -> PolarizedDataSet: + """Reconstruct a `PolarizedDataSet` serialized by `_as_dict_add_polarized_experiment`.""" + model = self._models[project_dict['experiments_models'][key]] + channels = { + channel_value: DataSet1D( + name=arrays[0], + x=arrays[1], + y=arrays[2], + ye=arrays[3], + xe=arrays[4], + model=model, + auto_background=False, + ) + for channel_value, arrays in raw['channels'].items() + } + return PolarizedDataSet( + name=project_dict['experiments_names'][key], + channels=channels, + model=model, + ) + def _get_materials_in_models(self) -> MaterialCollection: """Get materials in models.""" materials_in_model = MaterialCollection(populate_if_none=False) @@ -991,3 +1531,190 @@ def _replace_collection(self, src_collection: BaseCollection, dst_collection: Ba def _timestamp_modification(self): """Timestamp modification.""" self._info['modified'] = datetime.datetime.now().strftime('%d.%m.%Y %H:%M') + + +def _unwrapped_angle(angle: np.ndarray, mask: np.ndarray) -> np.ndarray: + """Make an angle profile continuous along z within each magnetic region. + + The moment angle is periodic, so a profile that turns smoothly from 359° to + 1° comes back from `atan2` as a jump of nearly 360°. Drawn as a line that is + a full sweep across the chart where the moment barely moves. Each contiguous + run of `mask` (a magnetic region) is therefore unwrapped on its own and then + shifted by whole turns so it sits as close to the conventional [0, 360) + range as possible — neighbouring regions stay independent, since the angle + between them is not defined. + + Parameters + ---------- + angle : np.ndarray + Wrapped angles in degrees. + mask : np.ndarray + Which points carry a moment. + + Returns + ------- + np.ndarray + Angles in degrees, continuous within each masked region. Points outside + the mask are returned unchanged (callers drop them). + """ + unwrapped = np.array(angle, dtype=float, copy=True) + if mask.size == 0 or not np.any(mask): + return unwrapped + + # Each contiguous run of masked points is one magnetic region. + masked_indices = np.flatnonzero(mask) + region_breaks = np.flatnonzero(np.diff(masked_indices) > 1) + 1 + for region in np.split(masked_indices, region_breaks): + segment = np.unwrap(unwrapped[region], period=360.0) + # Keep the drawn values near the usual range rather than at 720 deg. + turns = np.round(np.median(segment) / 360.0 - 0.5) + unwrapped[region] = segment - turns * 360.0 + return unwrapped + + +def _has_both_nsf_channels(channels: List[PolarizationChannel]) -> bool: + """Whether both non-spin-flip channels (pp, mm) — the pair spin asymmetry needs — are present.""" + return PolarizationChannel.PP in channels and PolarizationChannel.MM in channels + + +def _ordered_channel_arrays(dataset: DataSet1D) -> tuple: + """A channel's (q, reflectivity, variance) arrays, ordered and checked. + + Spin asymmetry pairs two channels point by point and interpolates one onto + the other, both of which assume well-formed, strictly increasing q. Rather + than trusting that, the arrays are checked here and sorted when needed — + `np.interp` silently returns nonsense for a descending grid. + + Raises + ------ + ValueError + The dataset is empty, its arrays disagree in length or shape, its q + values are not finite, or it visits the same q twice. + """ + name = getattr(dataset, 'name', '?') + q = np.asarray(getattr(dataset, 'x', np.empty(0)), dtype=float).ravel() + y = np.asarray(getattr(dataset, 'y', np.empty(0)), dtype=float).ravel() + if q.size == 0: + raise ValueError(f"Channel '{name}' has no data points.") + if q.size != y.size: + raise ValueError(f"Channel '{name}' has {q.size} q values for {y.size} reflectivities.") + if not np.all(np.isfinite(q)): + raise ValueError(f"Channel '{name}' has non-finite q values.") + + variance = np.asarray(getattr(dataset, 'ye', None) if getattr(dataset, 'ye', None) is not None else [], dtype=float) + variance = variance.ravel() + if variance.size == 0: + variance = np.zeros_like(y) + elif variance.size != y.size: + logger.warning( + "Channel '%s' has %s uncertainties for %s points; treating it as having none.", + name, + variance.size, + y.size, + ) + variance = np.zeros_like(y) + + order = np.argsort(q, kind='stable') + if not np.array_equal(order, np.arange(q.size)): + logger.warning("Channel '%s' is not ordered in q; sorting it before pairing.", name) + q, y, variance = q[order], y[order], variance[order] + # A single-point channel has an empty `np.diff`, so the duplicate-q check + # below is vacuously satisfied and it passes through here unrejected. That + # is intentional: a one-point channel is a legitimate (if degenerate) SA + # pair when it lines up exactly with the other channel's grid, and + # `_interpolate_with_variance` handles a size-1 `q_source` correctly (its + # clipped `searchsorted` result always resolves to that single point). + if np.any(np.diff(q) <= 0): + raise ValueError(f"Channel '{name}' visits the same q more than once; the pairing would be ambiguous.") + return q, y, variance + + +def _interpolate_with_variance(q: np.ndarray, q_source: np.ndarray, values: np.ndarray, variances: np.ndarray) -> tuple: + """Linear interpolation of values and their variances onto `q`. + + Values use the linear weights (1−t, t); variances use their **squares**, + which is the propagation rule for independent endpoints — interpolating a + variance linearly (as `np.interp` would) overestimates it, by a factor 2 at + the midpoint of two equal variances. + + `q` must lie inside `q_source`; the caller restricts it to the overlap. + """ + upper = np.clip(np.searchsorted(q_source, q, side='left'), 1, q_source.size - 1) + lower = upper - 1 + span = q_source[upper] - q_source[lower] + # span is > 0 for a strictly increasing source grid; guard anyway. + weight = np.where(span > 0, (q - q_source[lower]) / np.where(span > 0, span, 1.0), 0.0) + interpolated = (1.0 - weight) * values[lower] + weight * values[upper] + interpolated_variance = (1.0 - weight) ** 2 * variances[lower] + weight**2 * variances[upper] + return interpolated, interpolated_variance + + +def _spin_asymmetry( + r_pp: np.ndarray, + r_mm: np.ndarray, + var_pp: Optional[np.ndarray] = None, + var_mm: Optional[np.ndarray] = None, +) -> tuple: + """Spin asymmetry, its variance, which points to keep, and why not. + + Parameters + ---------- + r_pp, r_mm : np.ndarray + Non-spin-flip reflectivities on a common q grid. + var_pp, var_mm : Optional[np.ndarray] + Their variances (`DataSet1D.ye`), or None for a calculated curve. + + Returns + ------- + tuple + SA, its variance (zeros without input variances), a boolean mask of the + points to keep, and a dict counting the dropped ones by reason. + """ + r_pp = np.asarray(r_pp, dtype=float) + r_mm = np.asarray(r_mm, dtype=float) + denominator = r_pp + r_mm + var_pp = np.zeros_like(r_pp) if var_pp is None else np.asarray(var_pp, dtype=float) + var_mm = np.zeros_like(r_mm) if var_mm is None else np.asarray(var_mm, dtype=float) + + # A denominator of exactly zero would divide by zero; those points are + # dropped by the masks below anyway. + safe_denominator = np.where(denominator == 0, np.nan, denominator) + with np.errstate(invalid='ignore', divide='ignore'): + asymmetry = (r_pp - r_mm) / safe_denominator + # sigma_SA = 2 sqrt(R--^2 sigma_++^2 + R++^2 sigma_--^2) / (R++ + R--)^2, + # so the variance is its square. ye holds variances, hence no squaring + # of var_pp / var_mm here. + variance = 4.0 * (r_mm**2 * var_pp + r_pp**2 * var_mm) / safe_denominator**4 + + asymmetry = np.nan_to_num(asymmetry, nan=0.0, posinf=0.0, neginf=0.0) + variance = np.nan_to_num(variance, nan=0.0, posinf=0.0, neginf=0.0) + + # A point with a non-finite reflectivity, or an uncertainty that is not a + # usable variance, cannot produce a meaningful SA — and must not be silently + # demoted to "no uncertainty", which would also skip the significance test. + invalid = ~np.isfinite(r_pp) | ~np.isfinite(r_mm) + invalid |= ~np.isfinite(var_pp) | ~np.isfinite(var_mm) | (var_pp < 0) | (var_mm < 0) + + # Uncertainty-independent guard: the denominator must be positive and must + # not be the small remainder of two much larger numbers. Reflectivities are + # positive, so this only bites on background-subtracted data — which is + # exactly where SA otherwise explodes to +/-1e3 and destroys the axis. + magnitude = np.abs(r_pp) + np.abs(r_mm) + with np.errstate(invalid='ignore'): + degenerate = ~np.isfinite(denominator) | (denominator <= 0) + degenerate |= np.abs(denominator) <= SPIN_ASYMMETRY_CANCELLATION_FRACTION * magnitude + degenerate &= ~invalid + + # Uncertainty-based guard: is the denominator above the noise? + denominator_sigma = np.sqrt(np.clip(var_pp + var_mm, 0.0, None)) + with_uncertainty = denominator_sigma > 0 + insignificant = with_uncertainty & (denominator <= SPIN_ASYMMETRY_SIGNIFICANCE * denominator_sigma) + insignificant &= ~invalid & ~degenerate + + keep = ~invalid & ~degenerate & ~insignificant + reasons = { + 'invalid_points': int(np.count_nonzero(invalid)), + 'small_denominator_points': int(np.count_nonzero(degenerate)), + 'low_significance_points': int(np.count_nonzero(insignificant)), + } + return asymmetry, variance, keep, reasons diff --git a/src/easyreflectometry/sample/__init__.py b/src/easyreflectometry/sample/__init__.py index e6f347ad..e40b90ad 100644 --- a/src/easyreflectometry/sample/__init__.py +++ b/src/easyreflectometry/sample/__init__.py @@ -12,6 +12,7 @@ from .collections.sample import Sample from .elements.layers.layer import Layer from .elements.layers.layer_area_per_molecule import LayerAreaPerMolecule +from .elements.layers.layer_magnetism import LayerMagnetism from .elements.materials.material import Material from .elements.materials.material_density import MaterialDensity from .elements.materials.material_mixture import MaterialMixture @@ -24,6 +25,7 @@ 'Layer', 'LayerAreaPerMolecule', 'LayerCollection', + 'LayerMagnetism', 'Material', 'MaterialCollection', 'MaterialDensity', diff --git a/src/easyreflectometry/sample/elements/layers/layer.py b/src/easyreflectometry/sample/elements/layers/layer.py index 7eea9872..b926d494 100644 --- a/src/easyreflectometry/sample/elements/layers/layer.py +++ b/src/easyreflectometry/sample/elements/layers/layer.py @@ -13,6 +13,7 @@ from ...base_core import BaseCore from ..materials.material import Material +from .layer_magnetism import LayerMagnetism DEFAULTS = { 'thickness': { @@ -45,6 +46,7 @@ def __init__( name: str = 'EasyLayer', unique_name: Optional[str] = None, interface=None, + magnetism: Union[LayerMagnetism, None] = None, ): """Constructor. @@ -58,6 +60,9 @@ def __init__( Layer thickness in Angstrom. By default, None. roughness : Union[Parameter, float, None], optional Upper roughness on the layer in Angstrom. By default, None. + magnetism : Union[LayerMagnetism, None], optional + Magnetic properties of the layer; None for a non-magnetic layer. + By default, None. name : str, optional Name of the layer. By default, 'EasyLayer'. interface : @@ -91,10 +96,30 @@ def __init__( self._material = material self._thickness = thickness self._roughness = roughness + self._magnetism = magnetism if interface is not None: self.interface = interface + # ----- interface (override BaseCore's to switch on calculator magnetism) ----- + + @BaseCore.interface.setter + def interface(self, new_interface) -> None: + """Set the interface; runs `generate_bindings` and, for a magnetic layer, + enables magnetism on the calculator (raising if it does not support it). + """ + BaseCore.interface.fset(self, new_interface) + if new_interface is not None and self._magnetism is not None: + self._enable_calculator_magnetism() + + def _enable_calculator_magnetism(self) -> None: + """Turn on magnetism on the attached calculator. + + Raises `NotImplementedError` when the active calculator cannot model + magnetic samples (e.g. refnx or bornagain). + """ + self.interface().include_magnetism = True + @property def material(self) -> Material: return self._material @@ -119,6 +144,34 @@ def roughness(self) -> Parameter: def roughness(self, value: float) -> None: self._roughness.value = value + @property + def magnetism(self) -> Optional[LayerMagnetism]: + return self._magnetism + + @magnetism.setter + def magnetism(self, value: Optional[LayerMagnetism]) -> None: + """Attach or remove the magnetic properties of this layer. + + Attaching regenerates the calculator bindings so `rho_m`/`theta_m` become + live on the backend; removing drops the layer's magnetic state from the + backend (and switches calculator magnetism off entirely when this was the + last magnetic layer). + """ + if value is None and self._magnetism is not None: + if self.interface is not None: + self.interface().remove_layer_magnetism(self.unique_name) + # Detach the calculator callbacks of the removed parameters so later + # value changes on the detached object no longer reach the backend. + # `property()` (fget/fset/fdel all None) is easyscience's own "no + # callback" sentinel -- `Parameter.__copy__` sets it the same way -- + # so a later `fset` guard in `Parameter` cleanly no-ops. + self._magnetism.rho_m._callback = property() + self._magnetism.theta_m._callback = property() + self._magnetism = value + if value is not None and self.interface is not None: + self._enable_calculator_magnetism() + self.generate_bindings() + def assign_material(self, material: Material) -> None: """Assign a material to the layer interface. @@ -135,10 +188,13 @@ def assign_material(self, material: Material) -> None: @property def _dict_repr(self) -> dict[str, str]: """A simplified dict representation.""" - return { + this_dict = { self.name: { 'material': self.material._dict_repr, 'thickness': f'{self.thickness.value:.3f} {self.thickness.unit}', 'roughness': f'{self.roughness.value:.3f} {self.roughness.unit}', } } + if self._magnetism is not None: + this_dict[self.name]['magnetism'] = self._magnetism._dict_repr + return this_dict diff --git a/src/easyreflectometry/sample/elements/layers/layer_magnetism.py b/src/easyreflectometry/sample/elements/layers/layer_magnetism.py new file mode 100644 index 00000000..dcf16132 --- /dev/null +++ b/src/easyreflectometry/sample/elements/layers/layer_magnetism.py @@ -0,0 +1,125 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + + +from typing import Optional +from typing import Union + +import numpy as np +from easyscience import global_object +from easyscience.variable import Parameter + +from easyreflectometry.utils import get_as_parameter + +from ...base_core import BaseCore + +DEFAULTS = { + 'rho_m': { + 'description': 'The magnetic scattering length density of the layer in e-6 per squared angstrom.', + 'url': 'https://refl1d.readthedocs.io/en/latest/guide/magnetism.html', + 'value': 0.0, + 'unit': '1 / angstrom^2', + 'min': -np.inf, + 'max': np.inf, + 'fixed': True, + }, + 'theta_m': { + 'description': 'The angle of the in-plane magnetic moment with respect to the beam direction in degrees. ' + 'The default of 270 degrees aligns the moment with the default guide field (Aguide), giving no spin-flip.', + 'url': 'https://refl1d.readthedocs.io/en/latest/guide/magnetism.html', + 'value': 270.0, + 'unit': 'degree', + 'min': 0.0, + 'max': 360.0, + 'fixed': True, + }, +} + + +class LayerMagnetism(BaseCore): + """Magnetic properties of a layer: magnetic SLD amplitude and in-plane moment angle. + + Attach to a `Layer` via its `magnetism` argument/property to make the layer magnetic. + Both attributes are `Parameter` objects, so they can be fitted and serialized like any + other sample parameter. A calculator that supports magnetism (refl1d) binds them as + `magnetism_rhoM` / `magnetism_thetaM` on the corresponding slab. + """ + + def __init__( + self, + rho_m: Union[Parameter, float, None] = None, + theta_m: Union[Parameter, float, None] = None, + name: str = 'EasyLayerMagnetism', + unique_name: Optional[str] = None, + interface=None, + ): + """Constructor. + + Parameters + ---------- + rho_m : Union[Parameter, float, None], optional + Magnetic scattering length density in e-6 per squared angstrom. By default, 0. + theta_m : Union[Parameter, float, None], optional + In-plane moment angle in degrees. By default, 270 (aligned with the default + guide field, i.e. no spin-flip). + name : str, optional + Name of the magnetism element. By default, 'EasyLayerMagnetism'. + unique_name : Optional[str], optional + By default, None. + interface : + Calculator interface. By default, None. + """ + if unique_name is None: + unique_name = global_object.generate_unique_name(self.__class__.__name__) + + rho_m_value = rho_m + rho_m = get_as_parameter( + name='rho_m', + value=rho_m, + default_dict=DEFAULTS, + unique_name_prefix=f'{unique_name}_RhoM', + ) + # The default bounds are infinite; `Project._sync_parameter_states` + # narrows them to the shared SLD window unless the caller passed an + # explicit Parameter with its own bounds (same contract as `Layer`). + rho_m.default_limits_pending = not isinstance(rho_m_value, Parameter) + theta_m = get_as_parameter( + name='theta_m', + value=theta_m, + default_dict=DEFAULTS, + unique_name_prefix=f'{unique_name}_ThetaM', + ) + + super().__init__(name=name, unique_name=unique_name) + self._rho_m = rho_m + self._theta_m = theta_m + + if interface is not None: + self.interface = interface + + @property + def rho_m(self) -> Parameter: + return self._rho_m + + @rho_m.setter + def rho_m(self, value: float) -> None: + self._rho_m.value = value + + @property + def theta_m(self) -> Parameter: + return self._theta_m + + @theta_m.setter + def theta_m(self, value: float) -> None: + self._theta_m.value = value + + # Representation + @property + def _dict_repr(self) -> dict[str, str]: + """A simplified dict representation.""" + return { + self.name: { + 'rho_m': f'{self._rho_m.value:.3f}e-6 {self._rho_m.unit}', + 'theta_m': f'{self._theta_m.value:.3f} {self._theta_m.unit}', + } + } diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index f40f23ad..89b82f48 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -48,6 +48,9 @@ def _silence_pdf_converter(): _NAME_MAX_LEN = 20 +# Fixed per-channel colors for polarized experiments, shared with the app so a +# channel keeps the same color in the GUI and in the report. +_CHANNEL_COLORS = {'pp': '#0173B2', 'pm': '#029E73', 'mp': '#CC78BC', 'mm': '#DE8F05'} # Custom href scheme used to pass the full name to QML via TextEdit.hoveredLink. _TOOLTIP_SCHEME = 'nametooltip' @@ -184,22 +187,63 @@ def save_sld_plot(self, filename: str) -> None: fig.savefig(filename, dpi=600) plt.close() + def _measured_series(self) -> list: + """Measured data of experiment 0 as ``(label, dataset, color, channel)`` entries. + + One entry per measured spin channel for a polarized experiment (each + with its own channel color and channel key), a single channel-less + entry for an ordinary experiment, and an empty list when no experiment + is loaded. + """ + try: + experiment = self._project.experimental_data_for_model_at_index(0) + except IndexError: + return [] + + channels = self._project.experiment_channels_at_index(0) + if not channels: + return [('Experiment', experiment, 'red', None)] + return [ + (f'Experiment ({channel.value})', experiment[channel], _CHANNEL_COLORS[channel.value], channel) + for channel in channels + ] + + def _model_curve(self, channel=None): + """Calculated curve for experiment 0, per spin channel when asked. + + Returns None when the requested channel cannot be calculated (e.g. a + spin-flip channel on a non-magnetic model) — an incorrect overlay is + worse than none. + """ + try: + return self._project.model_data_for_model_at_index(0, channel=channel) + except (ValueError, NotImplementedError) as exception: + logging.getLogger(__name__).warning( + 'No calculated curve for channel %s: %s', getattr(channel, 'value', channel), exception + ) + return None + def save_fit_experiment_plot(self, filename: str) -> None: """Save fit experiment plot.""" fig = plt.figure() ax = fig.add_subplot(1, 1, 1) legends = [] - model = self._project.model_data_for_model_at_index(0) - ax.plot(model.x, np.log10(model.y), color='blue') - legends.append('Model') - - try: - experiment = self._project.experimental_data_for_model_at_index(0) - ax.plot(experiment.x, np.log10(experiment.y), color='red') - legends.append('Experiment') - except IndexError: - pass + measured_series = self._measured_series() + # One calculated curve per measured channel; the ordinary single curve + # when the experiment is unpolarized or nothing is loaded. + model_channels = [entry[3] for entry in measured_series] or [None] + for channel in model_channels: + model = self._model_curve(channel) + if model is None: + continue + color = 'blue' if channel is None else _CHANNEL_COLORS[channel.value] + ax.plot(model.x, np.log10(model.y), color=color) + legends.append('Model' if channel is None else f'Model ({channel.value})') + + for label, dataset, color, _channel in measured_series: + ax.plot(dataset.x, np.log10(dataset.y), color=color) + legends.append(label) ax.set_xlabel('Q (Å⁻¹)') ax.set_ylabel('Reflectivity') @@ -260,28 +304,39 @@ def _experiments_section(self) -> str: html_experiments = [] for idx, experiment in self._project.experiments.items(): - experiment_name = experiment.name - num_data_points = len(experiment.x) - resolution_function = experiment.model.resolution_function.as_dict()['smearing'] - if resolution_function == 'PercentageFwhm': - precentage = experiment.model.resolution_function.as_dict()['constant'] - resolution_function = f'{resolution_function} {precentage}%' - range_min = min(experiment.y) - range_max = max(experiment.y) - range_units = 'Å⁻¹' - html_experiment = HTML_DATA_COLLECTION_TEMPLATE - html_experiment = html_experiment.replace('experiment_name', _truncate_name(experiment_name)) - html_experiment = html_experiment.replace('range_min', _format_value(range_min, 2)) - html_experiment = html_experiment.replace('range_max', _format_value(range_max, 2)) - html_experiment = html_experiment.replace('range_units', f'{range_units}') - html_experiment = html_experiment.replace('num_data_points', f'{num_data_points}') - html_experiment = html_experiment.replace('resolution_function', f'{resolution_function}') - html_experiments.append(html_experiment) + # A polarized experiment holds one dataset per spin channel; report + # one row per channel, as the plots already draw one curve each. + channels = getattr(experiment, 'available_channels', None) + if channels is None: + rows = [(experiment.name, experiment)] + else: + rows = [(f'{experiment.name} ({channel.value})', experiment[channel]) for channel in channels] + for row_name, dataset in rows: + html_experiments.append(self._experiment_row(row_name, dataset, experiment.model)) html_experiments_str = '\n'.join(html_experiments) return html_experiments_str + def _experiment_row(self, experiment_name, dataset, model) -> str: + """One row of the experiments table for a single measured dataset.""" + num_data_points = len(dataset.x) + resolution_function = model.resolution_function.as_dict()['smearing'] + if resolution_function == 'PercentageFwhm': + precentage = model.resolution_function.as_dict()['constant'] + resolution_function = f'{resolution_function} {precentage}%' + range_min = min(dataset.y) + range_max = max(dataset.y) + range_units = 'Å⁻¹' + html_experiment = HTML_DATA_COLLECTION_TEMPLATE + html_experiment = html_experiment.replace('experiment_name', _truncate_name(experiment_name)) + html_experiment = html_experiment.replace('range_min', _format_value(range_min, 2)) + html_experiment = html_experiment.replace('range_max', _format_value(range_max, 2)) + html_experiment = html_experiment.replace('range_units', f'{range_units}') + html_experiment = html_experiment.replace('num_data_points', f'{num_data_points}') + html_experiment = html_experiment.replace('resolution_function', f'{resolution_function}') + return html_experiment + def _refinement_section(self) -> str: """Refinement section.""" html_refinement = HTML_REFINEMENT_TEMPLATE @@ -411,30 +466,35 @@ def _fit_experiment_plotly_figure(self): fig = go.Figure() - model = self._project.model_data_for_model_at_index(0) - fig.add_trace( - go.Scatter( - x=np.asarray(model.x), - y=np.asarray(model.y), - mode='lines', - name='Model', - line={'color': 'blue'}, + measured_series = self._measured_series() + # One calculated curve per measured channel; the ordinary single curve + # when the experiment is unpolarized or nothing is loaded. + model_channels = [entry[3] for entry in measured_series] or [None] + for channel in model_channels: + model = self._model_curve(channel) + if model is None: + continue + color = 'blue' if channel is None else _CHANNEL_COLORS[channel.value] + fig.add_trace( + go.Scatter( + x=np.asarray(model.x), + y=np.asarray(model.y), + mode='lines', + name='Model' if channel is None else f'Model ({channel.value})', + line={'color': color}, + ) ) - ) - try: - experiment = self._project.experimental_data_for_model_at_index(0) + for label, dataset, color, _channel in measured_series: fig.add_trace( go.Scatter( - x=np.asarray(experiment.x), - y=np.asarray(experiment.y), + x=np.asarray(dataset.x), + y=np.asarray(dataset.y), mode='markers', - name='Experiment', - marker={'color': 'red', 'size': 4}, + name=label, + marker={'color': color, 'size': 4}, ) ) - except IndexError: - pass fig.update_layout( xaxis_title='Q (Å⁻¹)', diff --git a/tests/calculators/refl1d/test_refl1d_calculator.py b/tests/calculators/refl1d/test_refl1d_calculator.py index 50de2db4..2bdfa410 100644 --- a/tests/calculators/refl1d/test_refl1d_calculator.py +++ b/tests/calculators/refl1d/test_refl1d_calculator.py @@ -155,6 +155,43 @@ def test_calculate_magnetic(self): ] assert_almost_equal(actual, expected, decimal=4) + def test_polarized_reflectivity_profiles(self): + p = Refl1d() + p.include_magnetism = True + p._wrapper.create_material('Material1') + p._wrapper.update_material('Material1', rho=0.000, irho=0.000) + p._wrapper.create_material('Material2') + p._wrapper.update_material('Material2', rho=4.000, irho=0.000) + p._wrapper.create_material('Material3') + p._wrapper.update_material('Material3', rho=2.047, irho=0.000) + p._wrapper.create_model('MyModel') + p._wrapper.create_layer('Layer1') + p._wrapper.assign_material_to_layer('Material1', 'Layer1') + p._wrapper.create_layer('Layer2') + p._wrapper.assign_material_to_layer('Material2', 'Layer2') + p._wrapper.update_layer('Layer2', thickness=100, interface=0) + p._wrapper.update_layer('Layer2', magnetism_rhoM=2, magnetism_thetaM=45) + p._wrapper.create_layer('Layer3') + p._wrapper.assign_material_to_layer('Material3', 'Layer3') + p._wrapper.create_item('Item') + p._wrapper.add_layer_to_item('Layer1', 'Item') + p._wrapper.add_layer_to_item('Layer2', 'Item') + p._wrapper.add_layer_to_item('Layer3', 'Item') + p._wrapper.add_item('Item', 'MyModel') + q = np.linspace(0.005, 0.3, 50) + + channels = p.polarized_reflectivity_profiles(q, 'MyModel') + + assert_equal(list(channels.keys()), ['pp', 'pm', 'mp', 'mm']) + for reflectivity in channels.values(): + assert_equal(len(reflectivity), len(q)) + + # reflectity_profile follows the selected channel + for key in ['pp', 'pm', 'mp', 'mm']: + p.polarization_channel = key + assert_equal(p.polarization_channel.value, key) + assert_almost_equal(p.reflectity_profile(q, 'MyModel'), channels[key]) + def test_sld_profile(self): p = Refl1d() p._wrapper.create_material('Material1') diff --git a/tests/calculators/refl1d/test_refl1d_wrapper.py b/tests/calculators/refl1d/test_refl1d_wrapper.py index 57c43ee8..49ef2966 100644 --- a/tests/calculators/refl1d/test_refl1d_wrapper.py +++ b/tests/calculators/refl1d/test_refl1d_wrapper.py @@ -10,9 +10,12 @@ from unittest.mock import patch import numpy as np +import pytest +from numpy.testing import assert_allclose from numpy.testing import assert_almost_equal from numpy.testing import assert_equal +from easyreflectometry.calculators.polarization import PolarizationChannel from easyreflectometry.calculators.refl1d.wrapper import Refl1dWrapper from easyreflectometry.calculators.refl1d.wrapper import _build_sample from easyreflectometry.calculators.refl1d.wrapper import _get_oversampling_q @@ -377,9 +380,11 @@ def test_get_polarized_probe(): assert all(probe.dQ == dq) assert len(probe.calc_Q) == len(q) assert len(probe.xs) == 4 - assert probe.xs[1:4] == [None, None, None] - assert probe.xs[0].intensity.value == 10 - assert probe.xs[0].background.value == 20 + for cross_section in probe.xs: + assert cross_section is not None + assert cross_section.intensity.value == 10 + assert cross_section.background.value == 20 + assert len(cross_section.calc_Q) == len(q) def test_get_polarized_probe_oversampling(): @@ -396,33 +401,253 @@ def test_get_polarized_probe_oversampling(): probe = _get_polarized_probe(q_array=q, dq_array=dq, model_name=model_name, storage=storage, oversampling_factor=2) # Then - assert len(probe.xs[0].calc_Qo) == 2 * len(q) + for cross_section in probe.xs: + assert len(cross_section.calc_Qo) == 2 * len(q) -def test_get_polarized_probe_polarization(): - # When - q = np.linspace(1, 10, 10) - dq = np.linspace(0.01, 0.1, 10) - model_name = 'model_name' +Q_POLARIZED = np.linspace(0.005, 0.3, 100) - storage = {'model': {model_name: {}}} - storage['model'][model_name]['scale'] = 10.0 - storage['model'][model_name]['bkg'] = 20.0 - # Then - probe = _get_polarized_probe( - q_array=q, - dq_array=dq, - model_name=model_name, - storage=storage, - all_polarizations=True, - ) +def _sample_wrapper(rho: float, magnetic: bool, rhoM: float = 0.0, thetaM: float = 270.0) -> Refl1dWrapper: + """Vacuum | 100 A layer of `rho` (optionally magnetic) | Si substrate. - # Expect - assert len(probe.xs[0].calc_Q) == len(q) - assert len(probe.xs[1].calc_Q) == len(q) - assert len(probe.xs[2].calc_Q) == len(q) - assert len(probe.xs[3].calc_Q) == len(q) + Magnetic values may be set via `update_layer` at any time (also one key at a + time); they are stored per layer and attached to the slabs whenever magnetism + is enabled. + """ + p = Refl1dWrapper() + if magnetic: + p.magnetism = True + p.create_material('Vacuum') + p.update_material('Vacuum', rho=0.0, irho=0.0) + p.create_material('MaterialMag') + p.update_material('MaterialMag', rho=rho, irho=0.0) + p.create_material('Si') + p.update_material('Si', rho=2.047, irho=0.0) + p.create_model('MyModel') + p.create_layer('Superphase') + p.assign_material_to_layer('Vacuum', 'Superphase') + p.create_layer('LayerMag') + p.assign_material_to_layer('MaterialMag', 'LayerMag') + p.update_layer('LayerMag', thickness=100, interface=0) + if magnetic: + p.update_layer('LayerMag', magnetism_rhoM=rhoM, magnetism_thetaM=thetaM) + p.create_layer('Subphase') + p.assign_material_to_layer('Si', 'Subphase') + p.create_item('Item') + p.add_layer_to_item('Superphase', 'Item') + p.add_layer_to_item('LayerMag', 'Item') + p.add_layer_to_item('Subphase', 'Item') + p.add_item('Item', 'MyModel') + return p + + +def test_calculate_polarized_shape(): + p = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=45) + + channels = p.calculate_polarized(Q_POLARIZED, 'MyModel') + + assert list(channels.keys()) == ['pp', 'pm', 'mp', 'mm'] + for reflectivity in channels.values(): + assert isinstance(reflectivity, np.ndarray) + assert len(reflectivity) == len(Q_POLARIZED) + assert np.all(np.isfinite(reflectivity)) + + +def test_calculate_follows_selected_channel(): + p = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=45) + channels = p.calculate_polarized(Q_POLARIZED, 'MyModel') + + for channel in PolarizationChannel: + p.polarization_channel = channel + assert_allclose(p.calculate(Q_POLARIZED, 'MyModel'), channels[channel.value], rtol=1e-10) + + +def test_calculate_polarized_zero_magnetic_sld(): + # Polarized calculation with zero magnetic SLD (magnetism enabled, rhoM=0): + # the non-spin-flip channels degenerate to the unpolarized result and the + # spin-flip channels vanish. + p = _sample_wrapper(rho=4.0, magnetic=True, rhoM=0.0, thetaM=270) + unpolarized = _sample_wrapper(rho=4.0, magnetic=False) + + channels = p.calculate_polarized(Q_POLARIZED, 'MyModel') + reference = unpolarized.calculate(Q_POLARIZED, 'MyModel') + + assert_allclose(channels['pp'], reference, rtol=1e-5) + assert_allclose(channels['mm'], reference, rtol=1e-5) + # Tolerance pinned from the observed numerics of refl1d 1.0.0 (machine noise). + assert np.max(channels['pm']) < 1e-16 + assert np.max(channels['mp']) < 1e-16 + + +def test_calculate_polarized_channel_ordering(): + # Pins the pp/mm halves of POLARIZATION_CHANNEL_TO_INDEX with physics, guarding + # against a pp/mm swap: with the moment collinear with the neutron polarization + # axis there is no spin flip and the non-spin-flip channels see rho +/- rhoM. + # refl1d returns cross-sections in probe._xs_names order ['mm','mp','pm','pp'] + # (refl1d/probe/probe.py; magnetic_amplitude returns (--,-+,+-,++)). With the + # default guide field (Aguide=270), thetaM=270 is the moment-parallel-to-field + # orientation: spin-up ('pp') sees rho + rhoM, spin-down ('mm') sees rho - rhoM. + # thetaM=90 (anti-parallel) swaps the two; both are spin-flip-free. + rho, rhoM = 4.0, 2.0 + plus = _sample_wrapper(rho=rho + rhoM, magnetic=False) + minus = _sample_wrapper(rho=rho - rhoM, magnetic=False) + reflectivity_plus = plus.calculate(Q_POLARIZED, 'MyModel') + reflectivity_minus = minus.calculate(Q_POLARIZED, 'MyModel') + + aligned = _sample_wrapper(rho=rho, magnetic=True, rhoM=rhoM, thetaM=270) + channels = aligned.calculate_polarized(Q_POLARIZED, 'MyModel') + assert_allclose(channels['pp'], reflectivity_plus, rtol=1e-4, atol=1e-9) + assert_allclose(channels['mm'], reflectivity_minus, rtol=1e-4, atol=1e-9) + assert np.max(channels['pm']) < 1e-16 + assert np.max(channels['mp']) < 1e-16 + + anti_aligned = _sample_wrapper(rho=rho, magnetic=True, rhoM=rhoM, thetaM=90) + channels = anti_aligned.calculate_polarized(Q_POLARIZED, 'MyModel') + assert_allclose(channels['pp'], reflectivity_minus, rtol=1e-4, atol=1e-9) + assert_allclose(channels['mm'], reflectivity_plus, rtol=1e-4, atol=1e-9) + + +def test_channel_index_map_matches_refl1d_xs_names(): + # The index map must agree with refl1d's own cross-section labels: the wrapper + # extracts Experiment.reflectivity() results in probe.xs order, which is + # PolarizedNeutronProbe._xs_names. This pins the convention at the source so a + # refl1d-side reordering (or a wrapper-side swap) fails loudly. + from refl1d import names as refl1d_names + + from easyreflectometry.calculators.polarization import POLARIZATION_CHANNEL_TO_INDEX + + xs_names = refl1d_names.PolarizedNeutronQProbe._xs_names + assert len(xs_names) == 4 + for channel, index in POLARIZATION_CHANNEL_TO_INDEX.items(): + assert xs_names[index] == channel.value + + +def test_calculate_polarized_spin_flip(): + # Moment perpendicular to the neutron polarization (thetaM=0) produces spin flip; + # a collinear moment (thetaM=90, see test_calculate_polarized_channel_ordering) + # produces essentially none. + aligned = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=90) + perpendicular = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=0) + + channels_aligned = aligned.calculate_polarized(Q_POLARIZED, 'MyModel') + channels_perpendicular = perpendicular.calculate_polarized(Q_POLARIZED, 'MyModel') + + assert np.max(channels_perpendicular['pm']) > 1e3 * np.max(channels_aligned['pm']) + assert np.max(channels_perpendicular['pm']) > 1e-6 # absolute sanity floor + # pm and mp are identical by symmetry for a non-chiral, non-absorptive sample, + # so this cannot distinguish them: the pm=1 / mp=2 indices rest on the refl1d + # docstring alone ("a sequence pp, pm, mp and mm"). + assert_allclose(channels_perpendicular['pm'], channels_perpendicular['mp'], rtol=1e-10) + + +def test_calculate_polarized_scale_and_background(): + # Intensity and background must reach every cross-section: + # R_out = scale * R + bkg, channel by channel. + scale, bkg = 2.0, 1e-6 + plain = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=45) + scaled = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=45) + scaled.update_model('MyModel', scale=scale, bkg=bkg) + + channels_plain = plain.calculate_polarized(Q_POLARIZED, 'MyModel') + channels_scaled = scaled.calculate_polarized(Q_POLARIZED, 'MyModel') + + for key in ['pp', 'pm', 'mp', 'mm']: + assert_allclose(channels_scaled[key], scale * channels_plain[key] + bkg, rtol=1e-8) + + +def test_calculate_polarized_requires_magnetism(): + p = _sample_wrapper(rho=4.0, magnetic=False) + with pytest.raises(ValueError): + p.calculate_polarized(Q_POLARIZED, 'MyModel') + + +def test_polarization_channel_normalization(): + p = Refl1dWrapper() + p.magnetism = True + + p.polarization_channel = PolarizationChannel.MM + assert p.polarization_channel is PolarizationChannel.MM + p.polarization_channel = 'pm' + assert p.polarization_channel is PolarizationChannel.PM + + for bad in ['MM', 'xx', None]: + with pytest.raises(ValueError): + p.polarization_channel = bad + + +def test_polarization_channel_requires_magnetism(): + p = Refl1dWrapper() + with pytest.raises(ValueError): + p.polarization_channel = 'mm' + # pp is always allowed + p.polarization_channel = 'pp' + assert p.polarization_channel is PolarizationChannel.PP + + +def test_disabling_magnetism_resets_channel(): + p = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=45) + unpolarized = _sample_wrapper(rho=4.0, magnetic=False) + p.polarization_channel = 'mm' + + p.magnetism = False + + # The transition is complete: channel back to pp, slab Magnetism objects + # stripped, and the plain (unpolarized) calculation path works. + assert p.polarization_channel is PolarizationChannel.PP + assert all(layer.magnetism is None for layer in p.storage['layer'].values()) + assert_allclose(p.calculate(Q_POLARIZED, 'MyModel'), unpolarized.calculate(Q_POLARIZED, 'MyModel'), rtol=1e-10) + + # Re-enabling restores the stored magnetic values (rhoM/thetaM survive the + # toggle so the wrapper stays in sync with model parameters that still hold them). + p.magnetism = True + assert p.polarization_channel is PolarizationChannel.PP + restored = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=45) + channels = p.calculate_polarized(Q_POLARIZED, 'MyModel') + reference = restored.calculate_polarized(Q_POLARIZED, 'MyModel') + for channel in ('pp', 'pm', 'mp', 'mm'): + assert_allclose(channels[channel], reference[channel], rtol=1e-10) + + +def test_polarized_reflectivities_guards_malformed_output(): + p = _sample_wrapper(rho=4.0, magnetic=True, rhoM=2.0, thetaM=45) + q = Q_POLARIZED + + # Fewer than four cross-sections + with patch('easyreflectometry.calculators.refl1d.wrapper.names.Experiment') as mock_experiment: + mock_experiment.return_value.reflectivity.return_value = [(q, np.ones(len(q)))] * 3 + with pytest.raises(RuntimeError, match='expected 4'): + p.calculate_polarized(q, 'MyModel') + + # Wrong-length cross-section: index 1 is 'mp' in refl1d's (mm, mp, pm, pp) order. + with patch('easyreflectometry.calculators.refl1d.wrapper.names.Experiment') as mock_experiment: + mock_experiment.return_value.reflectivity.return_value = [ + (q, np.ones(len(q))), + (q, np.ones(len(q) - 1)), + (q, np.ones(len(q))), + (q, np.ones(len(q))), + ] + with pytest.raises(RuntimeError, match='malformed mp'): + p.calculate_polarized(q, 'MyModel') + + # Non-finite values: index 3 is 'pp' in refl1d's (mm, mp, pm, pp) order. + with patch('easyreflectometry.calculators.refl1d.wrapper.names.Experiment') as mock_experiment: + bad = np.ones(len(q)) + bad[0] = np.nan + mock_experiment.return_value.reflectivity.return_value = [(q, np.ones(len(q)))] * 3 + [(q, bad)] + with pytest.raises(RuntimeError, match='malformed pp'): + p.calculate_polarized(q, 'MyModel') + + +def test_polarization_channel_survives_reset_storage(): + # reset_storage leaves _magnetism and the resolution function alone; + # the selected channel behaves consistently. + p = Refl1dWrapper() + p.magnetism = True + p.polarization_channel = 'mm' + p.reset_storage() + assert p.polarization_channel is PolarizationChannel.MM + assert p._magnetism is True @patch('easyreflectometry.calculators.refl1d.wrapper.names.Stack') diff --git a/tests/calculators/refnx/test_refnx_wrapper.py b/tests/calculators/refnx/test_refnx_wrapper.py index bb99d633..4ea18d0a 100644 --- a/tests/calculators/refnx/test_refnx_wrapper.py +++ b/tests/calculators/refnx/test_refnx_wrapper.py @@ -28,7 +28,18 @@ def test_init(self): def test_set_magnetism(self): p = RefnxWrapper() with pytest.raises(NotImplementedError): - p.include_magnetism = True + p.magnetism = True + assert p._magnetism is False + + def test_calculate_polarized_not_supported(self): + p = RefnxWrapper() + with pytest.raises(NotImplementedError): + p.calculate_polarized(np.linspace(0.01, 0.3, 10), 'MyModel') + + def test_polarization_channel_requires_magnetism(self): + p = RefnxWrapper() + with pytest.raises(ValueError): + p.polarization_channel = 'mm' def test_reset_storage(self): p = RefnxWrapper() diff --git a/tests/calculators/test_polarization_interface.py b/tests/calculators/test_polarization_interface.py new file mode 100644 index 00000000..55b72f8c --- /dev/null +++ b/tests/calculators/test_polarization_interface.py @@ -0,0 +1,123 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +""" +Model/factory-level tests for polarization channel selection. +""" + +import numpy as np +import pytest +from numpy.testing import assert_allclose + +from easyreflectometry.calculators import CalculatorFactory +from easyreflectometry.calculators import PolarizationChannel +from easyreflectometry.model import Model +from easyreflectometry.model import PercentageFwhm +from easyreflectometry.sample import Layer +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer +from easyreflectometry.sample import Sample + + +def _magnetic_model() -> Model: + vacuum = Material(sld=0, isld=0, name='Vacuum') + material = Material(sld=4.0, isld=0, name='Sld 4') + si = Material(sld=2.047, isld=0, name='Si') + superphase = Layer(material=vacuum, thickness=0, roughness=0, name='Vacuum Superphase') + layer = Layer(material=material, thickness=100, roughness=0, name='Sld 4 Layer') + subphase = Layer(material=si, thickness=0, roughness=0, name='Si Subphase') + sample = Sample(Multilayer(superphase), Multilayer(layer), Multilayer(subphase), name='Sample') + model = Model(sample=sample, scale=1, background=0, name='Magnetic Model') + model.resolution_function = PercentageFwhm(0) + return model + + +Q = np.linspace(0.005, 0.3, 50) + + +def test_polarized_reflectivity_profiles_through_factory(): + model = _magnetic_model() + interface = CalculatorFactory() + interface.switch('refl1d') + model.interface = interface + calculator = model.interface() + calculator.include_magnetism = True + layer_name = list(calculator._wrapper.storage['layer'].keys())[1] + calculator._wrapper.update_layer(layer_name, magnetism_rhoM=2, magnetism_thetaM=45) + + # Through the factory (model.interface), not only the calculator (model.interface()) + channels = model.interface.polarized_reflectivity_profiles(Q, model.unique_name) + + assert list(channels.keys()) == ['pp', 'pm', 'mp', 'mm'] + for reflectivity in channels.values(): + assert len(reflectivity) == len(Q) + + # reflectity_profile (the fitting path) follows the selected channel + calculator.polarization_channel = 'mm' + assert_allclose(model.interface().reflectity_profile(Q, model.unique_name), channels['mm'], rtol=1e-10) + + +def test_switch_resets_polarization_state(): + model = _magnetic_model() + interface = CalculatorFactory() + interface.switch('refl1d') + model.interface = interface + calculator = model.interface() + calculator.include_magnetism = True + calculator.polarization_channel = 'mm' + + # switch() constructs a fresh calculator instance: channel and magnetism reset + interface.switch('refl1d') + + calculator = model.interface() + assert calculator.polarization_channel is PolarizationChannel.PP + assert calculator.include_magnetism is False + + +def test_magnetic_sld_profile_through_factory(): + model = _magnetic_model() + interface = CalculatorFactory() + interface.switch('refl1d') + model.interface = interface + calculator = model.interface() + calculator.include_magnetism = True + layer_name = list(calculator._wrapper.storage['layer'].keys())[1] + calculator._wrapper.update_layer(layer_name, magnetism_rhoM=2, magnetism_thetaM=45) + + z, sld, sld_magnetic, theta_magnetic = interface.magnetic_sld_profile(model.unique_name) + + assert len(z) == len(sld) == len(sld_magnetic) == len(theta_magnetic) + # inside the 100 angstrom magnetic layer (zero roughness, so plateaus are exact) + inside = (z > 25) & (z < 75) + assert_allclose(sld[inside], 4.0) + assert_allclose(sld_magnetic[inside], 2.0) + assert_allclose(theta_magnetic[inside], 45.0) + + +def test_magnetic_sld_profile_requires_magnetism(): + model = _magnetic_model() + interface = CalculatorFactory() + interface.switch('refl1d') + model.interface = interface + + with pytest.raises(ValueError): + interface.magnetic_sld_profile(model.unique_name) + + +def test_refnx_magnetic_sld_profile_raises(): + model = _magnetic_model() + interface = CalculatorFactory() + interface.switch('refnx') + model.interface = interface + + with pytest.raises(NotImplementedError): + interface.magnetic_sld_profile(model.unique_name) + + +def test_refnx_include_magnetism_raises(): + interface = CalculatorFactory() + interface.switch('refnx') + + with pytest.raises(NotImplementedError): + interface().include_magnetism = True + assert interface().include_magnetism is False diff --git a/tests/data/test_polarized.py b/tests/data/test_polarized.py new file mode 100644 index 00000000..7838c2c3 --- /dev/null +++ b/tests/data/test_polarized.py @@ -0,0 +1,191 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +""" +Tests for PolarizedDataSet and spin-channel detection. +""" + +from types import SimpleNamespace +from unittest.mock import patch + +import numpy as np +import pytest + +from easyreflectometry.calculators import PolarizationChannel +from easyreflectometry.data import DataSet1D +from easyreflectometry.data import PolarizedDataSet +from easyreflectometry.data import detect_polarization_channel +from easyreflectometry.data.polarized import _channel_from_filename + + +def _dataset(name: str = 'series') -> DataSet1D: + return DataSet1D(name=name, x=np.array([0.01, 0.02]), y=np.array([1.0, 0.5])) + + +class TestPolarizedDataSet: + def test_requires_at_least_one_channel(self): + with pytest.raises(ValueError): + PolarizedDataSet(channels={}) + + def test_rejects_invalid_channel_key(self): + with pytest.raises(ValueError): + PolarizedDataSet(channels={'xx': _dataset()}) + + def test_rejects_non_dataset_values(self): + with pytest.raises(ValueError): + PolarizedDataSet(channels={'pp': np.array([1.0])}) + + def test_channels_in_canonical_order(self): + p = PolarizedDataSet(channels={'mm': _dataset('a'), 'pm': _dataset('b'), 'pp': _dataset('c')}) + assert p.available_channels == [ + PolarizationChannel.PP, + PolarizationChannel.PM, + PolarizationChannel.MM, + ] + + def test_getitem_and_contains_accept_strings_and_enums(self): + pp = _dataset('up-up') + p = PolarizedDataSet(channels={'pp': pp, 'mm': _dataset('down-down')}) + assert p['pp'] is pp + assert p[PolarizationChannel.PP] is pp + assert 'pp' in p + assert PolarizationChannel.MM in p + assert 'pm' not in p + assert 'xx' not in p + assert len(p) == 2 + + def test_model_propagates_to_channel_datasets(self): + pp = _dataset() + mm = _dataset() + p = PolarizedDataSet(channels={'pp': pp, 'mm': mm}) + assert p.is_simulation + marker = object() + p.model = marker + assert p.is_experiment + assert pp.model is marker + assert mm.model is marker + + def test_channels_view_is_read_only(self): + p = PolarizedDataSet(channels={'pp': _dataset()}) + with pytest.raises(TypeError): + p.channels[PolarizationChannel.MM] = _dataset() + with pytest.raises(TypeError): + del p.channels[PolarizationChannel.PP] + + def test_set_channel_validates_propagates_and_reorders(self): + p = PolarizedDataSet(channels={'mm': _dataset()}) + marker = object() + p.model = marker + + with pytest.raises(ValueError): + p.set_channel('xx', _dataset()) + with pytest.raises(ValueError): + p.set_channel('pp', np.array([1.0])) + + pp = _dataset('up-up') + p.set_channel('pp', pp) + # Canonical order restored, model propagated to the new dataset. + assert p.available_channels == [PolarizationChannel.PP, PolarizationChannel.MM] + assert p['pp'] is pp + assert pp.model is marker + + replacement = _dataset('up-up-2') + p.set_channel(PolarizationChannel.PP, replacement) + assert p['pp'] is replacement + + def test_remove_channel_guards(self): + p = PolarizedDataSet(channels={'pp': _dataset(), 'mm': _dataset()}) + with pytest.raises(ValueError): + p.remove_channel('pm') # not present + p.remove_channel('pp') + assert p.available_channels == [PolarizationChannel.MM] + with pytest.raises(ValueError): + p.remove_channel('mm') # last channel cannot be removed + assert p.available_channels == [PolarizationChannel.MM] + + +class TestChannelDetection: + @pytest.mark.parametrize( + 'filename,expected', + [ + ('sample_uu.dat', PolarizationChannel.PP), + ('sample_pp.ort', PolarizationChannel.PP), + ('sample-up-up.txt', PolarizationChannel.PP), + ('sample_up.dat', PolarizationChannel.PP), + ('run12_plus.txt', PolarizationChannel.PP), + ('sample_dd.dat', PolarizationChannel.MM), + ('sample_down.dat', PolarizationChannel.MM), + ('sample-down-down.txt', PolarizationChannel.MM), + ('sample_ud.dat', PolarizationChannel.PM), + ('sample_up_down.dat', PolarizationChannel.PM), + ('sample_pm.ort', PolarizationChannel.PM), + ('sample_du.dat', PolarizationChannel.MP), + ('sample_down_up.dat', PolarizationChannel.MP), + ('sample_mp.ort', PolarizationChannel.MP), + ('nothing_here.dat', None), + ('d2o_layer.dat', None), + ], + ) + def test_filename_heuristics(self, filename, expected): + assert _channel_from_filename(filename) == expected + + @pytest.mark.parametrize( + 'polarization,expected', + [ + ('pp', PolarizationChannel.PP), + ('mm', PolarizationChannel.MM), + ('pm', PolarizationChannel.PM), + ('mp', PolarizationChannel.MP), + # Partially-analysed observables are not spin channels: 'po' measures + # pp + pm (incident plus, no outgoing analysis), 'mo' measures mp + mm. + ('po', None), + ('mo', None), + ('op', None), + ('om', None), + ('unpolarized', None), + ], + ) + def test_orso_header_detection(self, polarization, expected): + orso_dataset = SimpleNamespace( + info=SimpleNamespace( + data_source=SimpleNamespace( + measurement=SimpleNamespace( + instrument_settings=SimpleNamespace(polarization=polarization), + ) + ) + ) + ) + with patch('orsopy.fileio.orso.load_orso', return_value=[orso_dataset]): + assert detect_polarization_channel('whatever.ort') == expected + + def test_header_takes_precedence_over_filename(self): + orso_dataset = SimpleNamespace( + info=SimpleNamespace( + data_source=SimpleNamespace( + measurement=SimpleNamespace( + instrument_settings=SimpleNamespace(polarization='mm'), + ) + ) + ) + ) + with patch('orsopy.fileio.orso.load_orso', return_value=[orso_dataset]): + assert detect_polarization_channel('sample_uu.ort') == PolarizationChannel.MM + + def test_unreadable_file_falls_back_to_filename(self): + # No such file: the ORSO branch raises internally and the name decides. + assert detect_polarization_channel('no_such_file_dd.ort') == PolarizationChannel.MM + + def test_unpolarized_header_suppresses_filename_fallback(self): + # A header that explicitly declares a non-channel polarization wins over + # channel-looking filename tokens. + orso_dataset = SimpleNamespace( + info=SimpleNamespace( + data_source=SimpleNamespace( + measurement=SimpleNamespace( + instrument_settings=SimpleNamespace(polarization='unpolarized'), + ) + ) + ) + ) + with patch('orsopy.fileio.orso.load_orso', return_value=[orso_dataset]): + assert detect_polarization_channel('sample_uu.ort') is None diff --git a/tests/integration/test_ort_file_fitting.py b/tests/integration/test_ort_file_fitting.py new file mode 100644 index 00000000..ded7cd99 --- /dev/null +++ b/tests/integration/test_ort_file_fitting.py @@ -0,0 +1,128 @@ +# SPDX-FileCopyrightText: 2025 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""End-to-end fit of a real ORT dataset. + +Not part of ``tests/test_ort_file.py`` because the ``fit_model`` fixture +runs a genuine Bumps_simplex fit (max_evaluations=3000), which takes ~40s +on its own -- by far the single most expensive item in the unit test run. +Run explicitly with ``pixi run integration-tests`` (or +``pytest tests/integration``), not part of the default ``pytest tests``/ +``unit-tests`` run. +""" + +import logging +import os + +import numpy as np +import pytest +from easyscience.fitting import AvailableMinimizers + +import easyreflectometry +from easyreflectometry.calculators import CalculatorFactory +from easyreflectometry.data import load +from easyreflectometry.fitting import MultiFitter +from easyreflectometry.model import Model +from easyreflectometry.model import PercentageFwhm +from easyreflectometry.sample import Layer +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer +from easyreflectometry.sample import Sample + +PATH_STATIC = os.path.join(os.path.dirname(easyreflectometry.__file__), '..', '..', 'tests', '_static') + + +@pytest.fixture(scope='module') +def load_data(): + path = os.path.join(PATH_STATIC, 'amor_reduced_iofq.ort') + logging.info('Loading data from %s', path) + data = load(path) + return data + + +@pytest.fixture(scope='module') +def fit_model(load_data): + data = load_data + # Rescale data + reflectivity = data['data']['R_0'].values + scale_factor = 1 / np.max(reflectivity) + data['data']['R_0'].values *= scale_factor + data['data']['R_0'].variances *= scale_factor**2 + + # Create a model for the sample + + si = Material(sld=2.07, isld=0.0, name='Si') + sio2 = Material(sld=3.47, isld=0.0, name='SiO2') + d2o = Material(sld=6.33, isld=0.0, name='D2O') + dlipids = Material(sld=5.0, isld=0.0, name='DLipids') + + superphase = Layer(material=si, thickness=0, roughness=0, name='Si superphase') + sio2_layer = Layer(material=sio2, thickness=20, roughness=4, name='SiO2 layer') + dlipids_layer = Layer(material=dlipids, thickness=40, roughness=4, name='DLipids layer') + subphase = Layer(material=d2o, thickness=0, roughness=5, name='D2O subphase') + + multi_sample = Sample( + Multilayer(superphase), + Multilayer(sio2_layer), + Multilayer(dlipids_layer), + Multilayer(subphase), + name='Multilayer Structure', + ) + + multi_layer_model = Model( + sample=multi_sample, + scale=1, + background=0.000001, + resolution_function=PercentageFwhm(5), + name='Multilayer Model', + ) + + # Set the fitting parameters + + sio2_layer.roughness.min = 3 + sio2_layer.roughness.max = 12 + sio2_layer.material.sld.min = 3.47 + sio2_layer.material.sld.max = 5 + sio2_layer.thickness.min = 10 + sio2_layer.thickness.max = 30 + + subphase.material.sld.min = 6 + dlipids_layer.thickness.min = 30 + dlipids_layer.thickness.max = 60 + dlipids_layer.roughness.min = 3 + dlipids_layer.roughness.max = 10 + dlipids_layer.material.sld.min = 4 + dlipids_layer.material.sld.max = 6 + multi_layer_model.scale.min = 0.8 + multi_layer_model.scale.max = 1.2 + multi_layer_model.background.min = 1e-6 + multi_layer_model.background.max = 1e-3 + + sio2_layer.roughness.free = True + sio2_layer.material.sld.free = True + sio2_layer.thickness.free = True + subphase.material.sld.free = True + dlipids_layer.thickness.free = True + dlipids_layer.roughness.free = True + dlipids_layer.material.sld.free = True + multi_layer_model.scale.free = True + multi_layer_model.background.free = True + + # Run the model and plot the results + + multi_layer_model.interface = CalculatorFactory() + + fitter1 = MultiFitter(multi_layer_model) + fitter1.switch_minimizer(AvailableMinimizers.Bumps_simplex) + fitter1.easy_science_multi_fitter.max_evaluations = 3000 + + analysed = fitter1.fit(data) + return analysed + + +def test_analyze_reduced_data__fit_model_success(fit_model): + assert fit_model['success'] is True + + +def test_analyze_reduced_data__fit_model_reasonable(fit_model): + assert fit_model['reduced_chi'] < 6.0 diff --git a/tests/sample/elements/layers/test_layer_magnetism.py b/tests/sample/elements/layers/test_layer_magnetism.py new file mode 100644 index 00000000..a2932dec --- /dev/null +++ b/tests/sample/elements/layers/test_layer_magnetism.py @@ -0,0 +1,226 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +""" +Tests for LayerMagnetism and magnetic-parameter plumbing through the calculators. +""" + +import numpy as np +import pytest +from numpy.testing import assert_allclose +from numpy.testing import assert_equal + +from easyreflectometry.calculators.factory import CalculatorFactory +from easyreflectometry.model import Model +from easyreflectometry.model import PercentageFwhm +from easyreflectometry.sample import Layer +from easyreflectometry.sample import LayerMagnetism +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer +from easyreflectometry.sample import Sample + +Q = np.linspace(0.005, 0.3, 50) + + +def _magnetic_model(magnetism: LayerMagnetism | None) -> Model: + vacuum = Material(sld=0, isld=0, name='Vacuum') + material = Material(sld=4.0, isld=0, name='Sld 4') + si = Material(sld=2.047, isld=0, name='Si') + superphase = Layer(material=vacuum, thickness=0, roughness=0, name='Vacuum Superphase') + layer = Layer(material=material, thickness=100, roughness=0, magnetism=magnetism, name='Sld 4 Layer') + subphase = Layer(material=si, thickness=0, roughness=0, name='Si Subphase') + sample = Sample(Multilayer(superphase), Multilayer(layer), Multilayer(subphase), name='Sample') + model = Model(sample=sample, scale=1, background=0, name='Magnetic Model') + model.resolution_function = PercentageFwhm(0) + return model + + +class TestLayerMagnetism: + def test_default_construction(self): + magnetism = LayerMagnetism() + assert_equal(magnetism.name, 'EasyLayerMagnetism') + assert_equal(magnetism.rho_m.value, 0.0) + assert_equal(magnetism.rho_m.fixed, True) + assert_equal(magnetism.theta_m.value, 270.0) + assert_equal(magnetism.theta_m.min, 0.0) + assert_equal(magnetism.theta_m.max, 360.0) + assert_equal(magnetism.theta_m.fixed, True) + + def test_construction_with_values(self): + magnetism = LayerMagnetism(rho_m=2.5, theta_m=45.0, name='FeMoment') + assert_equal(magnetism.name, 'FeMoment') + assert_equal(magnetism.rho_m.value, 2.5) + assert_equal(magnetism.theta_m.value, 45.0) + + def test_dict_round_trip(self): + magnetism = LayerMagnetism(rho_m=1.5, theta_m=90.0) + magnetism_dict = magnetism.as_dict() + reloaded = LayerMagnetism.from_dict(magnetism_dict) + assert_equal(reloaded.rho_m.value, 1.5) + assert_equal(reloaded.theta_m.value, 90.0) + assert sorted(magnetism.as_dict()) == sorted(reloaded.as_dict()) + + +class TestLayerWithMagnetism: + def test_layer_default_is_non_magnetic(self): + layer = Layer() + assert layer.magnetism is None + + def test_layer_dict_round_trip_with_magnetism(self): + layer = Layer(magnetism=LayerMagnetism(rho_m=2.0, theta_m=45.0), name='MagneticLayer') + layer_dict = layer.as_dict() + reloaded = Layer.from_dict(layer_dict) + assert reloaded.magnetism is not None + assert_equal(reloaded.magnetism.rho_m.value, 2.0) + assert_equal(reloaded.magnetism.theta_m.value, 45.0) + assert sorted(layer.as_dict()) == sorted(reloaded.as_dict()) + + def test_magnetic_parameters_are_fittable_variables(self): + magnetism = LayerMagnetism(rho_m=2.0, theta_m=45.0) + layer = Layer(magnetism=magnetism) + variable_names = [variable.name for variable in layer.get_all_variables()] + assert 'rho_m' in variable_names + assert 'theta_m' in variable_names + + +class TestMagnetismThroughCalculator: + def _interface(self, name: str) -> CalculatorFactory: + interface = CalculatorFactory() + interface.switch(name) + return interface + + def test_model_interface_enables_magnetism_and_binds_parameters(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + interface = self._interface('refl1d') + model.interface = interface + calculator = interface() + + assert model.has_magnetism is True + assert calculator.include_magnetism is True + layer = model.sample[1].layers[0] + wrapper = calculator._wrapper + assert wrapper.get_layer_value(layer.unique_name, 'magnetism_rhoM') == 2.0 + assert wrapper.get_layer_value(layer.unique_name, 'magnetism_thetaM') == 45.0 + + def test_magnetic_parameter_change_changes_reflectivity(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = self._interface('refl1d') + + before = model.interface.polarized_reflectivity_profiles(Q, model.unique_name) + model.sample[1].layers[0].magnetism.rho_m = 4.0 + after = model.interface.polarized_reflectivity_profiles(Q, model.unique_name) + + assert not np.allclose(before['pp'], after['pp']) + assert not np.allclose(before['mm'], after['mm']) + + def test_model_parameters_match_direct_wrapper_values(self): + # Setting rho_m/theta_m through the model must reproduce the reflectivity + # obtained by setting magnetism_rhoM/thetaM directly on the wrapper. + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = self._interface('refl1d') + via_model = model.interface.polarized_reflectivity_profiles(Q, model.unique_name) + + reference_model = _magnetic_model(None) + interface = self._interface('refl1d') + reference_model.interface = interface + calculator = interface() + calculator.include_magnetism = True + layer_name = reference_model.sample[1].layers[0].unique_name + calculator._wrapper.update_layer(layer_name, magnetism_rhoM=2.0, magnetism_thetaM=45.0) + via_wrapper = interface.polarized_reflectivity_profiles(Q, reference_model.unique_name) + + for channel in ('pp', 'pm', 'mp', 'mm'): + assert_allclose(via_model[channel], via_wrapper[channel], rtol=1e-10) + + def test_magnetism_added_after_interface_is_bound(self): + model = _magnetic_model(None) + model.interface = self._interface('refl1d') + calculator = model.interface() + assert calculator.include_magnetism is False + + layer = model.sample[1].layers[0] + layer.magnetism = LayerMagnetism(rho_m=2.0, theta_m=45.0) + + assert calculator.include_magnetism is True + wrapper = calculator._wrapper + assert wrapper.get_layer_value(layer.unique_name, 'magnetism_rhoM') == 2.0 + # The parameter is live: a change propagates to the backend. + layer.magnetism.rho_m = 3.0 + assert wrapper.get_layer_value(layer.unique_name, 'magnetism_rhoM') == 3.0 + + def test_removing_last_magnetism_disables_calculator_magnetism(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = self._interface('refl1d') + calculator = model.interface() + layer = model.sample[1].layers[0] + detached = layer.magnetism + + layer.magnetism = None + + # Model and calculator agree again: no magnetism anywhere. + assert layer.magnetism is None + assert model.has_magnetism is False + assert calculator.include_magnetism is False + assert calculator._wrapper.get_layer_value(layer.unique_name, 'magnetism_rhoM') == 0.0 + # The plain (unpolarized) calculation path works. + reflectivity = calculator.reflectity_profile(Q, model.unique_name) + assert len(reflectivity) == len(Q) + # The detached parameters no longer reach the backend. + detached.rho_m = 5.0 + assert calculator._wrapper.get_layer_value(layer.unique_name, 'magnetism_rhoM') == 0.0 + + def test_removing_one_of_two_magnetic_layers_keeps_magnetism(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = self._interface('refl1d') + calculator = model.interface() + subphase = model.sample[2].layers[0] + subphase.magnetism = LayerMagnetism(rho_m=1.0, theta_m=270.0) + + model.sample[1].layers[0].magnetism = None + + # One magnetic layer remains: the polarized path stays on, its values intact. + assert model.has_magnetism is True + assert calculator.include_magnetism is True + assert calculator._wrapper.get_layer_value(subphase.unique_name, 'magnetism_rhoM') == 1.0 + + def test_magnetic_layer_with_refnx_raises(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + with pytest.raises(NotImplementedError): + model.interface = self._interface('refnx') + + def test_supports_magnetism_capability_flag(self): + assert self._interface('refl1d')().supports_magnetism is True + assert self._interface('refnx')().supports_magnetism is False + + def test_project_exposes_calculator_capability(self): + from easyreflectometry.project import Project + + project = Project() + project.calculator = 'refl1d' + assert project.calculator_supports_magnetism is True + project.calculator = 'refnx' + assert project.calculator_supports_magnetism is False + + def test_fit_recovers_rho_m_from_synthetic_data(self): + from easyreflectometry.data import DataSet1D + from easyreflectometry.fitting import MultiFitter + + # Synthesize noiseless pp data from a model with a known moment. + truth = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=270.0)) + truth.interface = self._interface('refl1d') + reflectivity = truth.interface.polarized_reflectivity_profiles(Q, truth.unique_name)['pp'] + # `ye` holds variances. + data = DataSet1D(name='synthetic_pp', x=Q, y=reflectivity, ye=(0.01 * reflectivity) ** 2) + + # Fit a model starting from the wrong moment; only rho_m is free. + model = _magnetic_model(LayerMagnetism(rho_m=1.0, theta_m=270.0)) + model.interface = self._interface('refl1d') + rho_m = model.sample[1].layers[0].magnetism.rho_m + rho_m.fixed = False + rho_m.bounds = (0.0, 5.0) + + fitter = MultiFitter(model) + result = fitter.fit_single_data_set_1d(data) + + assert result.success + assert_allclose(rho_m.value, 2.5, atol=0.01) diff --git a/tests/summary/test_summary.py b/tests/summary/test_summary.py index a636b504..df1a7ac9 100644 --- a/tests/summary/test_summary.py +++ b/tests/summary/test_summary.py @@ -4,6 +4,7 @@ import os from unittest.mock import MagicMock +import numpy as np import pytest from easyscience import global_object @@ -151,6 +152,47 @@ def test_experiments_section_percentage_fhwm(self, project: Project) -> None: # Expect assert 'PercentageFwhm 5%' in html + def test_experiments_section_polarized(self, project: Project, tmp_path) -> None: + # When + # A polarized experiment holds one DataSet1D per spin channel, not the + # x/y arrays an ordinary experiment has — the section used to raise + # AttributeError, which killed the app when QML read the summary. + channel_paths = {} + for channel, suffix in (('pp', 'uu'), ('mm', 'dd')): + path = tmp_path / f'sample_{suffix}.txt' + q = np.linspace(0.01, 0.2, 20) + reflectivity = np.exp(-q * 30) + np.savetxt(path, np.column_stack([q, reflectivity, 0.01 * reflectivity])) + channel_paths[channel] = str(path) + project.calculator = 'refl1d' + project.load_polarized_experiment(channel_paths) + summary = Summary(project) + + # Then + html = summary._experiments_section() + + # Expect: one row per measured channel + assert 'Polarized experiment 0 (pp)' in html + assert 'Polarized experiment 0 (mm)' in html + assert html.count('No. of data points') == 2 + assert '20' in html + + def test_compile_html_summary_polarized(self, project: Project, tmp_path) -> None: + # When + channel_paths = {} + for channel, suffix in (('pp', 'uu'), ('mm', 'dd')): + path = tmp_path / f'sample_{suffix}.txt' + q = np.linspace(0.01, 0.2, 20) + reflectivity = np.exp(-q * 30) + np.savetxt(path, np.column_stack([q, reflectivity, 0.01 * reflectivity])) + channel_paths[channel] = str(path) + project.calculator = 'refl1d' + project.load_polarized_experiment(channel_paths) + summary = Summary(project) + + # Then Expect: the whole report compiles for a polarized experiment + assert 'Polarized experiment 0 (pp)' in summary.compile_html_summary() + def test_refinement_section(self, project: Project) -> None: # When summary = Summary(project) @@ -226,3 +268,84 @@ def test_figures_section_interactive(self, project: Project) -> None: # Two interactive plotly charts with the library embedded inline once. assert html.count('class="plotly-graph-div"') == 2 assert 'Plotly.newPlot' in html + + +class TestSummaryPolarized: + """Report figures of a polarized (per-channel) experiment.""" + + @staticmethod + def _write_channel_file(directory, name: str) -> str: + import numpy as np + + path = directory / name + q = np.linspace(0.01, 0.2, 20) + reflectivity = np.exp(-q * 30) + np.savetxt(path, np.column_stack([q, reflectivity, 0.01 * reflectivity])) + return str(path) + + @pytest.fixture + def polarized_project(self, tmp_path) -> Project: + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.default_model() + project.load_polarized_experiment({ + 'pp': self._write_channel_file(tmp_path, 'sample_uu.txt'), + 'mm': self._write_channel_file(tmp_path, 'sample_dd.txt'), + }) + return project + + def test_measured_series_one_entry_per_channel(self, polarized_project: Project) -> None: + # When + summary = Summary(polarized_project) + + # Then + series = summary._measured_series() + + # Expect + assert [entry[0] for entry in series] == ['Experiment (pp)', 'Experiment (mm)'] + assert series[0][2] != series[1][2] # distinct channel colors + assert [entry[3].value for entry in series] == ['pp', 'mm'] + + @pytest.fixture + def project(self) -> Project: + global_object.map._clear() + project = Project() + project.default_model() + return project + + def test_measured_series_unpolarized_and_empty(self, project: Project) -> None: + # When + summary = Summary(project) + + # Then Expect: nothing loaded + assert summary._measured_series() == [] + + # When an ordinary experiment is loaded + project.load_experiment_for_model_at_index(os.path.join(PATH_STATIC, 'example.ort')) + + # Expect one channel-less entry + series = summary._measured_series() + assert len(series) == 1 + assert series[0][0] == 'Experiment' and series[0][3] is None + + def test_model_curve_skips_uncalculable_channel(self, polarized_project: Project) -> None: + # When: a non-magnetic model cannot produce a spin-flip cross-section + summary = Summary(polarized_project) + + # Then Expect: no misleading overlay is generated for it + assert summary._model_curve('pm') is None + assert summary._model_curve('pp') is not None + + def test_fit_experiment_figure_has_a_trace_per_channel(self, polarized_project: Project) -> None: + # When + summary = Summary(polarized_project) + + # Then + figure = summary._fit_experiment_plotly_figure() + + # Expect: one measured trace per channel plus the calculable model curves + names = [trace.name for trace in figure.data] + assert 'Experiment (pp)' in names + assert 'Experiment (mm)' in names + assert 'Model (pp)' in names diff --git a/tests/test_limits.py b/tests/test_limits.py index 2fd1cc74..190b0d32 100644 --- a/tests/test_limits.py +++ b/tests/test_limits.py @@ -151,3 +151,54 @@ def test_existing_parameter_bounds_preserved(self): mat = Material(sld=custom_sld) assert mat.sld.min == -0.5 assert mat.sld.max == 7.0 + + +class TestMagneticParameterLimits: + def setup_method(self): + global_object.map._clear() + + def test_rho_m_uses_the_sld_window(self): + param = Parameter('rho_m', 5.0, min=-np.inf, max=np.inf) + apply_default_limits(param, 'rho_m') + assert param.min == SLD_LIMITS[0] + assert param.max == SLD_LIMITS[1] + + def test_magnetism_constructor_keeps_default_bounds_until_project_sync(self): + from easyreflectometry.sample import LayerMagnetism + + magnetism = LayerMagnetism(rho_m=5.0) + assert np.isinf(magnetism.rho_m.min) + assert np.isinf(magnetism.rho_m.max) + + def test_project_sync_narrows_rho_m_and_leaves_theta_m(self): + from easyreflectometry.project import Project + from easyreflectometry.sample import LayerMagnetism + + project = Project() + project.calculator = 'refl1d' + project.default_model() + layer = project.models[0].sample[1].layers[0] + layer.magnetism = LayerMagnetism(rho_m=5.0, theta_m=40.0) + + project._sync_parameter_states() + + assert layer.magnetism.rho_m.min == SLD_LIMITS[0] + assert layer.magnetism.rho_m.max == SLD_LIMITS[1] + # theta_m ships with explicit physical bounds; the sync must not touch them. + assert layer.magnetism.theta_m.min == 0.0 + assert layer.magnetism.theta_m.max == 360.0 + + def test_project_sync_keeps_explicit_rho_m_bounds(self): + from easyreflectometry.project import Project + from easyreflectometry.sample import LayerMagnetism + + project = Project() + project.calculator = 'refl1d' + project.default_model() + layer = project.models[0].sample[1].layers[0] + layer.magnetism = LayerMagnetism(rho_m=Parameter('rho_m', 5.0, min=1.0, max=8.0)) + + project._sync_parameter_states() + + assert layer.magnetism.rho_m.min == 1.0 + assert layer.magnetism.rho_m.max == 8.0 diff --git a/tests/test_ort_file.py b/tests/test_ort_file.py index ed8180df..fdaec79d 100644 --- a/tests/test_ort_file.py +++ b/tests/test_ort_file.py @@ -6,18 +6,9 @@ import numpy as np import pytest -from easyscience.fitting import AvailableMinimizers import easyreflectometry -from easyreflectometry.calculators import CalculatorFactory from easyreflectometry.data import load -from easyreflectometry.fitting import MultiFitter -from easyreflectometry.model import Model -from easyreflectometry.model import PercentageFwhm -from easyreflectometry.sample import Layer -from easyreflectometry.sample import Material -from easyreflectometry.sample import Multilayer -from easyreflectometry.sample import Sample PATH_STATIC = os.path.join(os.path.dirname(easyreflectometry.__file__), '..', '..', 'tests', '_static') @@ -30,86 +21,6 @@ def load_data(): return data -@pytest.fixture(scope='module') -def fit_model(load_data): - data = load_data - # Rescale data - reflectivity = data['data']['R_0'].values - scale_factor = 1 / np.max(reflectivity) - data['data']['R_0'].values *= scale_factor - data['data']['R_0'].variances *= scale_factor**2 - - # Create a model for the sample - - si = Material(sld=2.07, isld=0.0, name='Si') - sio2 = Material(sld=3.47, isld=0.0, name='SiO2') - d2o = Material(sld=6.33, isld=0.0, name='D2O') - dlipids = Material(sld=5.0, isld=0.0, name='DLipids') - - superphase = Layer(material=si, thickness=0, roughness=0, name='Si superphase') - sio2_layer = Layer(material=sio2, thickness=20, roughness=4, name='SiO2 layer') - dlipids_layer = Layer(material=dlipids, thickness=40, roughness=4, name='DLipids layer') - subphase = Layer(material=d2o, thickness=0, roughness=5, name='D2O subphase') - - multi_sample = Sample( - Multilayer(superphase), - Multilayer(sio2_layer), - Multilayer(dlipids_layer), - Multilayer(subphase), - name='Multilayer Structure', - ) - - multi_layer_model = Model( - sample=multi_sample, - scale=1, - background=0.000001, - resolution_function=PercentageFwhm(5), - name='Multilayer Model', - ) - - # Set the fitting parameters - - sio2_layer.roughness.min = 3 - sio2_layer.roughness.max = 12 - sio2_layer.material.sld.min = 3.47 - sio2_layer.material.sld.max = 5 - sio2_layer.thickness.min = 10 - sio2_layer.thickness.max = 30 - - subphase.material.sld.min = 6 - dlipids_layer.thickness.min = 30 - dlipids_layer.thickness.max = 60 - dlipids_layer.roughness.min = 3 - dlipids_layer.roughness.max = 10 - dlipids_layer.material.sld.min = 4 - dlipids_layer.material.sld.max = 6 - multi_layer_model.scale.min = 0.8 - multi_layer_model.scale.max = 1.2 - multi_layer_model.background.min = 1e-6 - multi_layer_model.background.max = 1e-3 - - sio2_layer.roughness.free = True - sio2_layer.material.sld.free = True - sio2_layer.thickness.free = True - subphase.material.sld.free = True - dlipids_layer.thickness.free = True - dlipids_layer.roughness.free = True - dlipids_layer.material.sld.free = True - multi_layer_model.scale.free = True - multi_layer_model.background.free = True - - # Run the model and plot the results - - multi_layer_model.interface = CalculatorFactory() - - fitter1 = MultiFitter(multi_layer_model) - fitter1.switch_minimizer(AvailableMinimizers.Bumps_simplex) - fitter1.easy_science_multi_fitter.max_evaluations = 3000 - - analysed = fitter1.fit(data) - return analysed - - def test_read_reduced_data__check_structure(load_data): data_keys = load_data['data'].keys() coord_keys = load_data['coords'].keys() @@ -161,11 +72,3 @@ def test_validate_physical_data__q_values_finite(load_data): @pytest.mark.skip('Currently no meta data to check') def test_validate_meta_data__required_meta_data() -> None: pytest.fail(reason='Currently no meta data to check') - - -def test_analyze_reduced_data__fit_model_success(fit_model): - assert fit_model['success'] is True - - -def test_analyze_reduced_data__fit_model_reasonable(fit_model): - assert fit_model['reduced_chi'] < 6.0 diff --git a/tests/test_polarized_fitting.py b/tests/test_polarized_fitting.py new file mode 100644 index 00000000..d84f536f --- /dev/null +++ b/tests/test_polarized_fitting.py @@ -0,0 +1,1210 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +""" +Tests for explicit-channel calculation, the polarized reflectivity cache, +per-channel experiment loading, and simultaneous multi-channel fitting. +""" + +import json +from unittest.mock import patch + +import numpy as np +import pytest +from easyscience import global_object +from numpy.testing import assert_allclose + +from easyreflectometry.calculators import CalculatorFactory +from easyreflectometry.calculators import PolarizationChannel +from easyreflectometry.calculators.refl1d import wrapper as refl1d_wrapper +from easyreflectometry.data import DataSet1D +from easyreflectometry.data import PolarizedDataSet +from easyreflectometry.fitting import MultiFitter +from easyreflectometry.model import Model +from easyreflectometry.model import ModelCollection +from easyreflectometry.model import PercentageFwhm +from easyreflectometry.project import Project +from easyreflectometry.sample import Layer +from easyreflectometry.sample import LayerMagnetism +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer +from easyreflectometry.sample import Sample + +Q = np.linspace(0.005, 0.3, 50) + + +@pytest.fixture(autouse=True) +def _isolated_global_object(): + """Leave the easyscience object map clean for the next test file. + + Several tests here build a `Project` (and therefore a 'project_models' + collection); a leftover registration makes the *next* module's first + `Project()` fail with 'Object name project_models already exists'. + """ + global_object.map._clear() + yield + global_object.map._clear() + + +def _magnetic_model(magnetism: LayerMagnetism | None) -> Model: + vacuum = Material(sld=0, isld=0, name='Vacuum') + material = Material(sld=4.0, isld=0, name='Sld 4') + si = Material(sld=2.047, isld=0, name='Si') + superphase = Layer(material=vacuum, thickness=0, roughness=0, name='Vacuum Superphase') + layer = Layer(material=material, thickness=100, roughness=0, magnetism=magnetism, name='Sld 4 Layer') + subphase = Layer(material=si, thickness=0, roughness=0, name='Si Subphase') + sample = Sample(Multilayer(superphase), Multilayer(layer), Multilayer(subphase), name='Sample') + model = Model(sample=sample, scale=1, background=0, name='Magnetic Model') + model.resolution_function = PercentageFwhm(0) + return model + + +def _refl1d_interface() -> CalculatorFactory: + interface = CalculatorFactory() + interface.switch('refl1d') + return interface + + +def _polarized_data(channels: dict[str, np.ndarray], model=None) -> PolarizedDataSet: + datasets = { + channel: DataSet1D(name=channel, x=Q, y=reflectivity, ye=(0.01 * reflectivity) ** 2) + for channel, reflectivity in channels.items() + } + return PolarizedDataSet(name='synthetic', channels=datasets, model=model) + + +class TestCalculateChannel: + def test_channels_match_calculate_polarized(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = _refl1d_interface() + calculator = model.interface() + + reference = calculator.polarized_reflectivity_profiles(Q, model.unique_name) + for channel in ('pp', 'pm', 'mp', 'mm'): + assert_allclose( + calculator.reflectivity_profile_channel(Q, model.unique_name, channel), + reference[channel], + rtol=1e-12, + ) + + def test_pp_without_magnetism_falls_back_to_unpolarized(self): + model = _magnetic_model(None) + model.interface = _refl1d_interface() + calculator = model.interface() + + assert_allclose( + calculator.reflectivity_profile_channel(Q, model.unique_name, 'pp'), + calculator.reflectity_profile(Q, model.unique_name), + rtol=1e-12, + ) + + def test_spin_flip_without_magnetism_raises(self): + model = _magnetic_model(None) + model.interface = _refl1d_interface() + with pytest.raises(ValueError): + model.interface().reflectivity_profile_channel(Q, model.unique_name, 'pm') + + def test_fit_func_for_channel(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = _refl1d_interface() + + reference = model.interface.polarized_reflectivity_profiles(Q, model.unique_name) + fit_func = model.interface.fit_func_for_channel('mm') + assert_allclose(fit_func(Q, model.unique_name), reference['mm'], rtol=1e-12) + + def test_magnetism_inside_a_repeating_multilayer_raises_instead_of_silently_wrong(self): + """refl1d itself does not support repeated magnetic slabs (`profile.repeat`). + + `magnetism.ipynb` documents this as a known limitation; this test pins + that the failure is loud (`NotImplementedError`) rather than a silent + wrong answer, so the tutorial's claim stays checked against behaviour. + """ + from easyreflectometry.sample import RepeatingMultilayer + + vacuum = Material(sld=0, isld=0, name='Vacuum') + material = Material(sld=4.0, isld=0, name='Fe') + si = Material(sld=2.047, isld=0, name='Si') + superphase = Layer(material=vacuum, thickness=0, roughness=0, name='Vacuum Superphase') + layer = Layer( + material=material, + thickness=50, + roughness=2, + magnetism=LayerMagnetism(rho_m=5.0, theta_m=40.0), + name='Fe film', + ) + subphase = Layer(material=si, thickness=0, roughness=3, name='Si Subphase') + repeated = RepeatingMultilayer(layer, repetitions=3, name='Repeated Fe') + sample = Sample(Multilayer(superphase), repeated, Multilayer(subphase), name='Repeated magnetic sample') + model = Model(sample=sample, scale=1, background=0, name='Repeated magnetic model') + model.resolution_function = PercentageFwhm(0) + model.interface = _refl1d_interface() + + with pytest.raises(NotImplementedError, match='[Rr]epeat'): + model.interface().polarized_reflectivity_profiles(Q, model.unique_name) + + +class TestPolarizedCache: + def test_repeated_calculation_hits_cache(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = _refl1d_interface() + calculator = model.interface() + + with patch.object(refl1d_wrapper.names, 'Experiment', wraps=refl1d_wrapper.names.Experiment) as experiment: + first = calculator.polarized_reflectivity_profiles(Q, model.unique_name) + second = calculator.polarized_reflectivity_profiles(Q, model.unique_name) + assert experiment.call_count == 1 + # All four channels through calculate_channel: still no new evaluation. + for channel in ('pp', 'pm', 'mp', 'mm'): + calculator.reflectivity_profile_channel(Q, model.unique_name, channel) + assert experiment.call_count == 1 + for channel in ('pp', 'pm', 'mp', 'mm'): + assert_allclose(first[channel], second[channel], rtol=1e-15) + + def test_parameter_change_invalidates_cache(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = _refl1d_interface() + calculator = model.interface() + + with patch.object(refl1d_wrapper.names, 'Experiment', wraps=refl1d_wrapper.names.Experiment) as experiment: + before = calculator.polarized_reflectivity_profiles(Q, model.unique_name) + model.sample[1].layers[0].magnetism.rho_m = 3.0 + after = calculator.polarized_reflectivity_profiles(Q, model.unique_name) + assert experiment.call_count == 2 + assert not np.allclose(before['mm'], after['mm']) + + def test_q_dtype_is_normalized_before_keying(self): + # Keying happens after normalization to float64 plus explicit shape, so + # byte-identical arrays of different dtype/shape can never collide. A + # float32 grid whose values are exactly representable normalizes to the + # same key as its float64 twin and shares the cache entry. + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = _refl1d_interface() + calculator = model.interface() + q_pow2 = np.array([0.03125, 0.0625, 0.125, 0.25]) # exact in float32 + + with patch.object(refl1d_wrapper.names, 'Experiment', wraps=refl1d_wrapper.names.Experiment) as experiment: + first = calculator.polarized_reflectivity_profiles(q_pow2, model.unique_name) + second = calculator.polarized_reflectivity_profiles(q_pow2.astype(np.float32), model.unique_name) + assert experiment.call_count == 1 + for channel in ('pp', 'pm', 'mp', 'mm'): + assert len(first[channel]) == len(q_pow2) + assert_allclose(second[channel], first[channel], rtol=1e-15) + + def test_different_q_grids_coexist_within_one_state(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = _refl1d_interface() + calculator = model.interface() + q_other = np.linspace(0.01, 0.2, 30) + + with patch.object(refl1d_wrapper.names, 'Experiment', wraps=refl1d_wrapper.names.Experiment) as experiment: + calculator.polarized_reflectivity_profiles(Q, model.unique_name) + calculator.polarized_reflectivity_profiles(q_other, model.unique_name) + assert experiment.call_count == 2 + # Both grids now cached for the same model state. + calculator.polarized_reflectivity_profiles(Q, model.unique_name) + calculator.polarized_reflectivity_profiles(q_other, model.unique_name) + assert experiment.call_count == 2 + + +class TestLoadPolarizedExperiment: + @staticmethod + def _write_channel_file(directory, name: str) -> str: + path = directory / name + q = np.linspace(0.01, 0.2, 20) + reflectivity = np.exp(-q * 30) + error = 0.01 * reflectivity + np.savetxt(path, np.column_stack([q, reflectivity, error])) + return str(path) + + def test_load_polarized_experiment(self, tmp_path): + pp_path = self._write_channel_file(tmp_path, 'sample_uu.txt') + mm_path = self._write_channel_file(tmp_path, 'sample_dd.txt') + + project = Project() + project.calculator = 'refl1d' + project.default_model() + + new_index = project.load_polarized_experiment({'pp': pp_path, 'mm': mm_path}) + + # The index is returned so a GUI can make the new experiment current. + assert new_index == 0 + experiment = project.experiments[0] + assert isinstance(experiment, PolarizedDataSet) + assert experiment.available_channels == [PolarizationChannel.PP, PolarizationChannel.MM] + assert experiment.name == 'Polarized experiment 0' + assert experiment.model is project.models[0] + assert experiment['pp'].model is project.models[0] + assert len(experiment['pp'].x) == 20 + + def test_second_polarized_experiment_gets_the_next_index(self, tmp_path): + pp_path = self._write_channel_file(tmp_path, 'sample_uu.txt') + mm_path = self._write_channel_file(tmp_path, 'sample_dd.txt') + + project = Project() + project.calculator = 'refl1d' + project.default_model() + + first = project.load_polarized_experiment({'pp': pp_path, 'mm': mm_path}) + second = project.load_polarized_experiment({'pp': pp_path}) + + assert (first, second) == (0, 1) + assert len(project.experiments) == 2 + + def test_multi_dataset_file_is_rejected(self, tmp_path): + import os + + multi_path = os.path.join(os.path.dirname(__file__), '_static', 'test_example2.ort') + mm_path = self._write_channel_file(tmp_path, 'sample_dd.txt') + + project = Project() + project.calculator = 'refl1d' + project.default_model() + + with pytest.raises(ValueError, match='multiple datasets'): + project.load_polarized_experiment({'pp': multi_path, 'mm': mm_path}) + assert len(project.experiments) == 0 + + def test_suggest_polarized_channel_assignment(self, tmp_path): + pp_path = self._write_channel_file(tmp_path, 'sample_uu.txt') + mm_path = self._write_channel_file(tmp_path, 'sample_dd.txt') + unknown_path = self._write_channel_file(tmp_path, 'sample_other.txt') + + project = Project() + suggestion = project.suggest_polarized_channel_assignment([pp_path, mm_path, unknown_path]) + + assert suggestion[str(pp_path)] == PolarizationChannel.PP + assert suggestion[str(mm_path)] == PolarizationChannel.MM + assert suggestion[str(unknown_path)] is None + + def test_project_as_dict_and_from_dict_round_trip_a_polarized_experiment(self, tmp_path): + pp_path = self._write_channel_file(tmp_path, 'sample_uu.txt') + mm_path = self._write_channel_file(tmp_path, 'sample_dd.txt') + + project = Project() + project.calculator = 'refl1d' + project.default_model() + project.load_polarized_experiment({'pp': pp_path, 'mm': mm_path}) + original = project.experiments[0] + + # Must not raise (PolarizedDataSet has no .x/.y/.ye of its own) and must + # be plain-JSON-serializable, since that is what `save_as_json` does with it. + project_dict = project.as_dict(include_materials_not_in_model=True) + json.dumps(project_dict) + + global_object.map._clear() + reloaded_project = Project() + reloaded_project.from_dict(project_dict) + reloaded = reloaded_project.experiments[0] + + assert isinstance(reloaded, PolarizedDataSet) + assert reloaded.name == original.name + assert reloaded.available_channels == original.available_channels + assert reloaded.model is reloaded_project.models[0] + for channel in original.available_channels: + assert reloaded[channel].name == original[channel].name + assert reloaded[channel].model is reloaded_project.models[0] + assert_allclose(reloaded[channel].x, original[channel].x) + assert_allclose(reloaded[channel].y, original[channel].y) + assert_allclose(reloaded[channel].ye, original[channel].ye) + + def test_project_save_as_json_and_load_from_json_round_trip_a_polarized_experiment(self, tmp_path): + pp_path = self._write_channel_file(tmp_path, 'sample_uu.txt') + mm_path = self._write_channel_file(tmp_path, 'sample_dd.txt') + + project = Project() + project.set_path_project_parent(tmp_path) + project.calculator = 'refl1d' + project.default_model() + project._info['name'] = 'Polarized round trip' + project.load_polarized_experiment({'pp': pp_path, 'mm': mm_path}) + + project.save_as_json() + assert project.path_json.exists() + + global_object.map._clear() + reloaded_project = Project() + reloaded_project.load_from_json(project.path_json) + reloaded = reloaded_project.experiments[0] + + assert isinstance(reloaded, PolarizedDataSet) + assert reloaded.available_channels == [PolarizationChannel.PP, PolarizationChannel.MM] + + +class TestChannelAwareExperimentAccessors: + """`experimental_data_for_model_at_index(index, channel=…)` and friends.""" + + @staticmethod + def _polarized_project(tmp_path) -> Project: + pp_path = TestLoadPolarizedExperiment._write_channel_file(tmp_path, 'sample_uu.txt') + mm_path = TestLoadPolarizedExperiment._write_channel_file(tmp_path, 'sample_dd.txt') + project = Project() + project.calculator = 'refl1d' + project.default_model() + project.load_polarized_experiment({'pp': pp_path, 'mm': mm_path}) + return project + + def test_without_channel_returns_the_whole_experiment(self, tmp_path): + project = self._polarized_project(tmp_path) + + experiment = project.experimental_data_for_model_at_index(0) + + assert isinstance(experiment, PolarizedDataSet) + assert project.experiment_is_polarized_at_index(0) is True + assert project.experiment_channels_at_index(0) == [PolarizationChannel.PP, PolarizationChannel.MM] + + def test_channel_returns_that_channel_dataset(self, tmp_path): + project = self._polarized_project(tmp_path) + experiment = project.experiments[0] + + for channel in ('pp', PolarizationChannel.MM): + data = project.experimental_data_for_model_at_index(0, channel=channel) + assert isinstance(data, DataSet1D) + assert data is experiment[channel] + + def test_unmeasured_channel_raises_key_error(self, tmp_path): + project = self._polarized_project(tmp_path) + + with pytest.raises(KeyError, match='was not measured'): + project.experimental_data_for_model_at_index(0, channel='pm') + + def test_unknown_channel_raises_value_error(self, tmp_path): + project = self._polarized_project(tmp_path) + + with pytest.raises(ValueError, match='Unknown spin channel'): + project.experimental_data_for_model_at_index(0, channel='xx') + + def test_channel_on_unpolarized_experiment_raises_value_error(self, tmp_path): + path = TestLoadPolarizedExperiment._write_channel_file(tmp_path, 'sample.txt') + project = Project() + project.calculator = 'refl1d' + project.default_model() + project.load_experiment_for_model_at_index(path, 0) + + assert project.experiment_is_polarized_at_index(0) is False + assert project.experiment_channels_at_index(0) == [] + with pytest.raises(ValueError, match='not polarized'): + project.experimental_data_for_model_at_index(0, channel='pp') + + def test_missing_experiment_raises_index_error(self): + project = Project() + project.default_model() + + assert project.experiment_is_polarized_at_index(0) is False + with pytest.raises(IndexError): + project.experimental_data_for_model_at_index(0, channel='pp') + + def test_model_data_per_channel_differs_for_magnetic_model(self): + global_object.map._clear() + model = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=40.0)) + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(model) + q_range = np.linspace(0.01, 0.2, 25) + + pp = project.model_data_for_model_at_index(0, q_range=q_range, channel='pp') + mm = project.model_data_for_model_at_index(0, q_range=q_range, channel='mm') + pm = project.model_data_for_model_at_index(0, q_range=q_range, channel='pm') + + assert pp.name.endswith('(pp) for Model 0') + # Each cross-section is genuinely different — this is what a per-channel + # display/report must show instead of one curve repeated four times. + assert not np.allclose(pp.y, mm.y) + assert not np.allclose(pp.y, pm.y) + + def test_spin_flip_channel_of_non_magnetic_model_raises(self): + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.default_model() + + with pytest.raises(ValueError, match='requires magnetism'): + project.model_data_for_model_at_index(0, channel='pm') + + +class TestFitPolarized: + def test_two_channel_nsf_fit_recovers_rho_m(self): + truth = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=270.0)) + truth.interface = _refl1d_interface() + reference = truth.interface.polarized_reflectivity_profiles(Q, truth.unique_name) + + model = _magnetic_model(LayerMagnetism(rho_m=1.0, theta_m=270.0)) + model.interface = _refl1d_interface() + rho_m = model.sample[1].layers[0].magnetism.rho_m + rho_m.fixed = False + rho_m.bounds = (0.0, 5.0) + + data = _polarized_data({'pp': reference['pp'], 'mm': reference['mm']}, model=model) + fitter = MultiFitter(model) + results = fitter.fit_polarized(data) + + assert list(results.keys()) == ['pp', 'mm'] + assert all(result.success for result in results.values()) + assert_allclose(rho_m.value, 2.5, atol=0.01) + + def test_four_channel_fit_recovers_rho_m_and_theta_m(self): + truth = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=45.0)) + truth.interface = _refl1d_interface() + reference = truth.interface.polarized_reflectivity_profiles(Q, truth.unique_name) + + model = _magnetic_model(LayerMagnetism(rho_m=1.5, theta_m=60.0)) + model.interface = _refl1d_interface() + magnetism = model.sample[1].layers[0].magnetism + magnetism.rho_m.fixed = False + magnetism.rho_m.bounds = (0.0, 5.0) + magnetism.theta_m.fixed = False + magnetism.theta_m.bounds = (0.0, 90.0) + + data = _polarized_data(dict(reference), model=model) + fitter = MultiFitter(model) + results = fitter.fit_polarized(data) + + assert list(results.keys()) == ['pp', 'pm', 'mp', 'mm'] + assert all(result.success for result in results.values()) + assert_allclose(magnetism.rho_m.value, 2.5, atol=0.02) + assert_allclose(magnetism.theta_m.value, 45.0, atol=0.5) + + def test_shared_structural_parameter_fitted_across_channels(self): + truth = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) + truth.interface = _refl1d_interface() + reference = truth.interface.polarized_reflectivity_profiles(Q, truth.unique_name) + + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) + model.interface = _refl1d_interface() + thickness = model.sample[1].layers[0].thickness + thickness.value = 90.0 + thickness.fixed = False + thickness.bounds = (50.0, 150.0) + + data = _polarized_data({'pp': reference['pp'], 'mm': reference['mm']}, model=model) + fitter = MultiFitter(model) + results = fitter.fit_polarized(data) + + assert all(result.success for result in results.values()) + assert_allclose(thickness.value, 100.0, atol=0.1) + + def test_fit_polarized_requires_matching_model(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) + model.interface = _refl1d_interface() + other = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) + other.interface = _refl1d_interface() + + data = _polarized_data({'pp': np.ones_like(Q)}, model=other) + fitter = MultiFitter(model) + with pytest.raises(ValueError, match='must be the model'): + fitter.fit_polarized(data) + + def test_fit_polarized_requires_matching_channel_models(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) + model.interface = _refl1d_interface() + other = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) + other.interface = _refl1d_interface() + + data = _polarized_data({'pp': np.ones_like(Q), 'mm': np.ones_like(Q)}, model=model) + # Rebind one channel dataset behind the experiment's back. + data['mm'].model = other + + fitter = MultiFitter(model) + with pytest.raises(ValueError, match="'mm' channel dataset"): + fitter.fit_polarized(data) + + def test_fit_polarized_requires_single_model(self): + model_a = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) + model_b = _magnetic_model(None) + interface = _refl1d_interface() + model_a.interface = interface + model_b.interface = interface + + data = _polarized_data({'pp': np.ones_like(Q)}, model=model_a) + fitter = MultiFitter(model_a, model_b) + with pytest.raises(ValueError): + fitter.fit_polarized(data) + + +class TestMultiFitterForExperiments: + """`MultiFitter.for_experiments` — one fit function per dataset, channels expanded.""" + + @staticmethod + def _unpolarized_data(model, name='plain') -> DataSet1D: + reflectivity = np.exp(-Q * 30) + dataset = DataSet1D(name=name, x=Q, y=reflectivity, ye=(0.01 * reflectivity) ** 2) + dataset.model = model + return dataset + + def test_polarized_experiment_expands_to_one_function_per_channel(self): + model = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=45.0)) + model.interface = _refl1d_interface() + reference = model.interface.polarized_reflectivity_profiles(Q, model.unique_name) + data = _polarized_data(dict(reference), model=model) + + fitter = MultiFitter.for_experiments([data]) + + assert fitter.fit_channels == [ + PolarizationChannel.PP, + PolarizationChannel.PM, + PolarizationChannel.MP, + PolarizationChannel.MM, + ] + assert fitter.fit_datasets == [data[channel] for channel in data.available_channels] + assert len(fitter._fit_func) == 4 + # Each function evaluates its own cross-section, not four copies of one. + curves = [func(Q) for func in fitter._fit_func] + for index, channel in enumerate(data.available_channels): + assert_allclose(curves[index], reference[channel.value], rtol=1e-9) + + def test_unpolarized_experiment_keeps_one_function(self): + model = _magnetic_model(None) + model.interface = _refl1d_interface() + data = self._unpolarized_data(model) + + fitter = MultiFitter.for_experiments([data]) + + assert fitter.fit_channels == [None] + assert fitter.fit_datasets == [data] + assert_allclose(fitter._fit_func[0](Q), model.interface.fit_func(Q, model.unique_name), rtol=1e-9) + + def test_mixed_experiments_share_one_fitter(self): + """A polarized and an ordinary experiment fitted together, two models.""" + magnetic = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) + plain = _magnetic_model(None) + interface = _refl1d_interface() + magnetic.interface = interface + plain.interface = interface + reference = magnetic.interface.polarized_reflectivity_profiles(Q, magnetic.unique_name) + polarized = _polarized_data({'pp': reference['pp'], 'mm': reference['mm']}, model=magnetic) + unpolarized = self._unpolarized_data(plain) + + fitter = MultiFitter.for_experiments([polarized, unpolarized]) + + assert fitter.fit_channels == [PolarizationChannel.PP, PolarizationChannel.MM, None] + # Both models' parameters are enumerated, so they are fitted together. + assert len(fitter._models) == 2 + assert len(fitter.easy_science_multi_fitter._fit_functions) == 3 + + def test_repeated_model_is_registered_once(self): + model = _magnetic_model(None) + model.interface = _refl1d_interface() + first = self._unpolarized_data(model, name='a') + second = self._unpolarized_data(model, name='b') + + fitter = MultiFitter.for_experiments([first, second]) + + assert len(fitter._models) == 1 + assert len(fitter.fit_datasets) == 2 + + def test_experiment_without_model_is_rejected(self): + dataset = DataSet1D(name='orphan', x=Q, y=np.ones_like(Q), ye=np.ones_like(Q)) + dataset.model = None + + with pytest.raises(ValueError, match='no model'): + MultiFitter.for_experiments([dataset]) + + def test_empty_experiment_list_is_rejected(self): + with pytest.raises(ValueError, match='At least one experiment'): + MultiFitter.for_experiments([]) + + def test_prepared_fitter_recovers_rho_m_when_run(self): + """The fitter is usable exactly like `fit_polarized`, but caller-driven.""" + truth = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=270.0)) + truth.interface = _refl1d_interface() + reference = truth.interface.polarized_reflectivity_profiles(Q, truth.unique_name) + + model = _magnetic_model(LayerMagnetism(rho_m=1.0, theta_m=270.0)) + model.interface = _refl1d_interface() + rho_m = model.sample[1].layers[0].magnetism.rho_m + rho_m.fixed = False + rho_m.bounds = (0.0, 5.0) + data = _polarized_data({'pp': reference['pp'], 'mm': reference['mm']}, model=model) + + fitter = MultiFitter.for_experiments([data]) + x = [np.asarray(dataset.x) for dataset in fitter.fit_datasets] + y = [np.asarray(dataset.y) for dataset in fitter.fit_datasets] + weights = [1.0 / np.sqrt(np.asarray(dataset.ye)) for dataset in fitter.fit_datasets] + results = fitter.easy_science_multi_fitter.fit(x, y, weights=weights) + + assert all(result.success for result in results) + assert_allclose(rho_m.value, 2.5, atol=0.01) + + +class TestRecordFitResults: + """Results produced by a caller-driven fit can be handed back to the fitter.""" + + def _fitter_and_results(self): + model = _magnetic_model(None) + model.interface = _refl1d_interface() + reflectivity = np.exp(-Q * 30) + dataset = DataSet1D(name='plain', x=Q, y=reflectivity, ye=(0.01 * reflectivity) ** 2) + dataset.model = model + fitter = MultiFitter.for_experiments([dataset]) + results = fitter.easy_science_multi_fitter.fit( + [np.asarray(dataset.x)], [np.asarray(dataset.y)], weights=[1.0 / np.sqrt(np.asarray(dataset.ye))] + ) + return fitter, list(results) + + def test_metrics_are_none_before_recording(self): + fitter, _results = self._fitter_and_results() + + # `easy_science_multi_fitter.fit` bypasses MultiFitter entirely. + assert fitter.chi2 is None + assert fitter.reduced_chi is None + + def test_recording_makes_the_metrics_available(self): + fitter, results = self._fitter_and_results() + + fitter.record_fit_results(results) + + assert fitter.chi2 == pytest.approx(sum(r.chi2 for r in results)) + assert fitter.reduced_chi is not None + # The classical metrics need the original arrays, which FitResults lacks. + assert fitter.classical_chi2 is None + + def test_recording_none_clears_the_metrics(self): + fitter, results = self._fitter_and_results() + fitter.record_fit_results(results) + + fitter.record_fit_results(None) + + assert fitter.chi2 is None + + +class TestMagneticSldData: + """`Project.magnetic_sld_data_for_model_at_index` (Phase 5a).""" + + @staticmethod + def _magnetic_project(theta_m: float = 270.0) -> Project: + global_object.map._clear() + model = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=theta_m)) + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(model) + return project + + def test_profiles_and_spin_potentials(self): + project = self._magnetic_project() + + profiles = project.magnetic_sld_data_for_model_at_index(0) + + assert set(profiles) == {'sld', 'rho_m', 'theta_m', 'spin_up', 'spin_down'} + sld, rho_m = profiles['sld'], profiles['rho_m'] + assert sld.x.size == rho_m.x.size and sld.x.size > 0 + assert np.allclose(profiles['spin_up'].x, sld.x) + # theta_m = 270 == the guide field, so the projection is the full rho_m: + # spin-up sees rho + rho_m, spin-down rho - rho_m. + assert_allclose(profiles['spin_up'].y, sld.y + rho_m.y, atol=1e-10) + assert_allclose(profiles['spin_down'].y, sld.y - rho_m.y, atol=1e-10) + # The magnetic layer really is magnetic somewhere along z. + assert np.abs(rho_m.y).max() > 0 + + def test_canted_moment_reduces_the_split(self): + along_field = self._magnetic_project(theta_m=270.0).magnetic_sld_data_for_model_at_index(0) + canted = self._magnetic_project(theta_m=270.0 - 60.0).magnetic_sld_data_for_model_at_index(0) + + split_along = np.abs(along_field['spin_up'].y - along_field['spin_down'].y).max() + split_canted = np.abs(canted['spin_up'].y - canted['spin_down'].y).max() + + # cos(60 deg) = 0.5 of the moment is seen by the spin states. + assert_allclose(split_canted, split_along * 0.5, rtol=1e-6) + + def test_nuclear_profile_matches_the_ordinary_sld_curve(self): + project = self._magnetic_project() + + profiles = project.magnetic_sld_data_for_model_at_index(0) + nuclear = project.sld_data_for_model_at_index(0) + + assert_allclose(profiles['sld'].x, nuclear.x) + assert_allclose(profiles['sld'].y, nuclear.y) + + def test_non_magnetic_model_raises_and_reports_no_magnetism(self): + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.default_model() + + assert project.model_has_magnetism_at_index(0) is False + assert project.model_has_magnetism_at_index(7) is False + with pytest.raises(ValueError, match='no magnetic layer'): + project.magnetic_sld_data_for_model_at_index(0) + + +class TestSpinAsymmetry: + """`Project.spin_asymmetry_for_experiment_at_index` (Phase 5b).""" + + @staticmethod + def _project_with_channels(channels: dict, model=None) -> Project: + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(model if model is not None else _magnetic_model(None)) + data = _polarized_data(channels, model=project.models[0]) + project._experiments[0] = data + return project + + def test_asymmetry_and_error_propagation(self): + global_object.map._clear() + r_pp = np.full_like(Q, 0.6) + r_mm = np.full_like(Q, 0.2) + project = self._project_with_channels({'pp': r_pp, 'mm': r_mm}) + + result = project.spin_asymmetry_for_experiment_at_index(0) + measured = result['measured'] + + # (0.6 - 0.2) / 0.8 + assert_allclose(measured.y, 0.5) + # ye holds variances: sigma_SA = 2 sqrt(R--^2 s++^2 + R++^2 s--^2)/(R+++R--)^2 + var_pp = (0.01 * r_pp) ** 2 + var_mm = (0.01 * r_mm) ** 2 + expected = 4.0 * (r_mm**2 * var_pp + r_pp**2 * var_mm) / (r_pp + r_mm) ** 4 + assert_allclose(measured.ye, expected, rtol=1e-12) + assert result['masked_points'] == 0 + + def test_insignificant_points_are_dropped_and_counted(self): + global_object.map._clear() + r_pp = np.full_like(Q, 0.6) + r_mm = np.full_like(Q, 0.2) + # Make the last five points pure noise: huge uncertainty, tiny signal. + r_pp[-5:] = 1e-9 + r_mm[-5:] = 1e-9 + noisy = np.where(np.arange(Q.size) >= Q.size - 5, 1.0, 1e-12) + datasets = { + 'pp': DataSet1D(name='pp', x=Q, y=r_pp, ye=noisy), + 'mm': DataSet1D(name='mm', x=Q, y=r_mm, ye=noisy), + } + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(_magnetic_model(None)) + project._experiments[0] = PolarizedDataSet(name='noisy', channels=datasets, model=project.models[0]) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['masked_points'] == 5 + assert result['measured'].x.size == Q.size - 5 + assert np.all(np.isfinite(result['measured'].y)) + + def test_channels_on_different_q_grids_are_interpolated(self): + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(_magnetic_model(None)) + q_mm = Q + 0.001 + datasets = { + 'pp': DataSet1D(name='pp', x=Q, y=np.full_like(Q, 0.6), ye=np.full_like(Q, 1e-12)), + 'mm': DataSet1D(name='mm', x=q_mm, y=np.full_like(q_mm, 0.2), ye=np.full_like(q_mm, 1e-12)), + } + project._experiments[0] = PolarizedDataSet(name='shifted', channels=datasets, model=project.models[0]) + + result = project.spin_asymmetry_for_experiment_at_index(0) + measured = result['measured'] + + # SA lives on the pp grid, restricted to where mm has data; the constant + # mm channel interpolates to 0.2 there. + assert_allclose(measured.x, Q[Q >= q_mm.min()]) + assert result['out_of_overlap_points'] == int(np.count_nonzero(Q < q_mm.min())) + assert_allclose(measured.y, 0.5, atol=1e-9) + + def test_calculated_asymmetry_only_for_a_magnetic_model(self): + global_object.map._clear() + magnetic = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=270.0)) + magnetic.interface = _refl1d_interface() + reference = magnetic.interface.polarized_reflectivity_profiles(Q, magnetic.unique_name) + project = self._project_with_channels({'pp': reference['pp'], 'mm': reference['mm']}, model=magnetic) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + calculated = result['calculated'] + assert calculated is not None + # Data and model are the same sample, so the two SA curves agree. + assert_allclose(calculated.y, result['measured'].y, atol=1e-6) + assert np.abs(calculated.y).max() > 0.01 # a real magnetic signal + + def test_no_calculated_asymmetry_without_magnetism(self): + global_object.map._clear() + project = self._project_with_channels({'pp': np.full_like(Q, 0.6), 'mm': np.full_like(Q, 0.2)}) + + assert project.spin_asymmetry_for_experiment_at_index(0)['calculated'] is None + + def test_availability_and_errors(self): + global_object.map._clear() + project = self._project_with_channels({'pp': np.full_like(Q, 0.6), 'mm': np.full_like(Q, 0.2)}) + + assert project.experiment_supports_spin_asymmetry_at_index(0) is True + assert project.experiment_supports_spin_asymmetry_at_index(1) is False + + # pp only: no asymmetry to form. + global_object.map._clear() + nsf_incomplete = self._project_with_channels({'pp': np.full_like(Q, 0.6)}) + assert nsf_incomplete.experiment_supports_spin_asymmetry_at_index(0) is False + with pytest.raises(ValueError, match='both non-spin-flip channels'): + nsf_incomplete.spin_asymmetry_for_experiment_at_index(0) + with pytest.raises(IndexError): + project.spin_asymmetry_for_experiment_at_index(3) + + +class TestMagneticProfileSmoothing: + """CR1 M1: the profile must interpolate the moment as a vector.""" + + @staticmethod + def _two_layer_model(theta_top: float, theta_bottom: float) -> Model: + vacuum = Material(sld=0, isld=0, name='Vacuum') + iron = Material(sld=8.0, isld=0, name='Fe') + si = Material(sld=2.047, isld=0, name='Si') + superphase = Layer(material=vacuum, thickness=0, roughness=0, name='Vacuum Superphase') + top = Layer( + material=iron, + thickness=100, + roughness=5, + magnetism=LayerMagnetism(rho_m=5.0, theta_m=theta_top), + name='Fe top', + ) + bottom = Layer( + material=iron, + thickness=100, + roughness=5, + magnetism=LayerMagnetism(rho_m=5.0, theta_m=theta_bottom), + name='Fe bottom', + ) + subphase = Layer(material=si, thickness=0, roughness=5, name='Si Subphase') + sample = Sample(Multilayer(superphase), Multilayer(top), Multilayer(bottom), Multilayer(subphase), name='Sample') + model = Model(sample=sample, scale=1, background=0, name='Two-layer magnetic') + model.resolution_function = PercentageFwhm(0) + return model + + def _profiles(self, theta_top: float, theta_bottom: float) -> dict: + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(self._two_layer_model(theta_top, theta_bottom)) + return project.magnetic_sld_data_for_model_at_index(0) + + def test_interface_between_almost_antiparallel_angles_does_not_invent_splitting(self): + # 359 deg and 1 deg are 2 deg apart, but smoothing the *angle* takes the + # long way round through 180 deg — and through the guide-field direction + # at 270 deg, where the full moment would look longitudinal. + profiles = self._profiles(359.0, 1.0) + + splitting = np.abs(profiles['spin_up'].y - profiles['spin_down'].y) + + # 2 * 5.0 * |cos(89 deg)| ~ 0.18, not 2 * 5.0. + assert splitting.max() < 0.5 + + def test_collinear_layers_keep_the_full_splitting(self): + # The same geometry with both moments along the guide field must still + # show the whole moment: the fix must not damp real magnetism. + profiles = self._profiles(270.0, 270.0) + + splitting = np.abs(profiles['spin_up'].y - profiles['spin_down'].y) + + assert splitting.max() == pytest.approx(2 * 5.0, rel=1e-3) + + def test_magnitude_and_angle_round_trip(self): + profiles = self._profiles(210.0, 210.0) + + rho_m = profiles['rho_m'] + theta_m = profiles['theta_m'] + + assert rho_m.y.max() == pytest.approx(5.0, rel=1e-3) + # theta_m is reported only where there is a moment. + assert theta_m.x.size < rho_m.x.size + assert np.allclose(theta_m.y, 210.0, atol=1e-6) + + def test_angle_is_not_reported_through_non_magnetic_regions(self): + profiles = self._profiles(270.0, 270.0) + + theta_m = profiles['theta_m'] + rho_m = profiles['rho_m'] + + # Every reported angle sits at a depth that carries a moment. + carried = np.interp(theta_m.x, rho_m.x, rho_m.y) + assert np.all(np.abs(carried) > 0) + assert theta_m.x.size > 0 + + +class TestSpinAsymmetryGridPairing: + """CR1 M2: pair channels only where both were measured.""" + + @staticmethod + def _project_with(pp: DataSet1D, mm: DataSet1D) -> Project: + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(_magnetic_model(None)) + project._experiments[0] = PolarizedDataSet(name='pairing', channels={'pp': pp, 'mm': mm}, model=project.models[0]) + return project + + def test_points_outside_the_mm_range_are_dropped_not_extrapolated(self): + # pp reaches further in q than mm; np.interp would clamp to the mm edge + # value and present the result as measured data. + q_pp = np.linspace(0.01, 0.30, 30) + q_mm = np.linspace(0.01, 0.20, 20) + pp = DataSet1D(name='pp', x=q_pp, y=np.full_like(q_pp, 0.6), ye=np.full_like(q_pp, 1e-12)) + mm = DataSet1D(name='mm', x=q_mm, y=np.full_like(q_mm, 0.2), ye=np.full_like(q_mm, 1e-12)) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['out_of_overlap_points'] == int(np.count_nonzero(q_pp > 0.20)) + assert result['measured'].x.max() <= 0.20 + assert np.allclose(result['measured'].y, 0.5) + + def test_disjoint_grids_give_no_asymmetry(self): + q_pp = np.linspace(0.30, 0.40, 10) + q_mm = np.linspace(0.01, 0.20, 10) + pp = DataSet1D(name='pp', x=q_pp, y=np.full_like(q_pp, 0.6), ye=np.full_like(q_pp, 1e-12)) + mm = DataSet1D(name='mm', x=q_mm, y=np.full_like(q_mm, 0.2), ye=np.full_like(q_mm, 1e-12)) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['out_of_overlap_points'] == q_pp.size + assert result['measured'].x.size == 0 + assert result['calculated'] is None + + def test_a_float_round_trip_difference_still_counts_as_the_same_grid(self): + q = np.linspace(0.01, 0.2, 20) + # The kind of difference a text round-trip introduces. + q_mm = np.array([float(f'{value:.12g}') for value in q * (1 + 1e-12)]) + pp = DataSet1D(name='pp', x=q, y=np.full_like(q, 0.6), ye=np.full_like(q, 1e-12)) + mm = DataSet1D(name='mm', x=q_mm, y=np.full_like(q_mm, 0.2), ye=np.full_like(q_mm, 1e-12)) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['out_of_overlap_points'] == 0 + assert result['measured'].x.size == q.size + + def test_equal_grids_report_no_dropped_points(self): + q = np.linspace(0.01, 0.2, 20) + pp = DataSet1D(name='pp', x=q, y=np.full_like(q, 0.6), ye=np.full_like(q, 1e-12)) + mm = DataSet1D(name='mm', x=q, y=np.full_like(q, 0.2), ye=np.full_like(q, 1e-12)) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['out_of_overlap_points'] == 0 + assert result['masked_points'] == 0 + + +class TestSpinAsymmetryDenominatorGuards: + """CR2: the guard must not depend on the data carrying uncertainties.""" + + @staticmethod + def _project_with(pp: DataSet1D, mm: DataSet1D) -> Project: + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(_magnetic_model(None)) + project._experiments[0] = PolarizedDataSet(name='guards', channels={'pp': pp, 'mm': mm}, model=project.models[0]) + return project + + def test_cancellation_without_uncertainties_is_dropped(self): + # Background-subtracted tail: the two channels nearly cancel, so SA is + # a ratio of rounding noise (here it would be 1999). + q = np.linspace(0.01, 0.2, 5) + pp = DataSet1D(name='pp', x=q, y=np.full_like(q, 1e-300)) + mm = DataSet1D(name='mm', x=q, y=np.full_like(q, -0.999e-300)) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['measured'].x.size == 0 + assert result['small_denominator_points'] == q.size + assert result['masked_points'] == q.size + + def test_ordinary_data_without_uncertainties_is_kept(self): + # A two-column file must still produce its asymmetry. + q = np.linspace(0.01, 0.2, 5) + pp = DataSet1D(name='pp', x=q, y=np.full_like(q, 0.6)) + mm = DataSet1D(name='mm', x=q, y=np.full_like(q, 0.2)) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['measured'].x.size == q.size + assert_allclose(result['measured'].y, 0.5) + assert result['masked_points'] == 0 + + def test_exact_cancellation_is_dropped(self): + q = np.linspace(0.01, 0.2, 4) + pp = DataSet1D(name='pp', x=q, y=np.full_like(q, 0.5)) + mm = DataSet1D(name='mm', x=q, y=np.full_like(q, -0.5)) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['measured'].x.size == 0 + assert result['small_denominator_points'] == q.size + + def test_unusable_variances_do_not_silently_become_no_uncertainty(self): + q = np.linspace(0.01, 0.2, 4) + variance = np.array([1e-12, -1.0, np.nan, 1e-12]) + pp = DataSet1D(name='pp', x=q, y=np.full_like(q, 0.6), ye=variance) + mm = DataSet1D(name='mm', x=q, y=np.full_like(q, 0.2), ye=np.full_like(q, 1e-12)) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + # The two malformed points are dropped and counted; the rest survive. + assert result['invalid_points'] == 2 + assert result['measured'].x.size == 2 + assert np.all(result['measured'].ye >= 0) + + def test_low_significance_and_cancellation_are_counted_separately(self): + q = np.linspace(0.01, 0.2, 6) + # First three: small but clean signal drowned in uncertainty. + # Last three: clean cancellation with negligible uncertainty. + r_pp = np.array([1e-6, 1e-6, 1e-6, 1.0, 1.0, 1.0]) + r_mm = np.array([1e-6, 1e-6, 1e-6, -0.9999, -0.9999, -0.9999]) + variance = np.array([1.0, 1.0, 1.0, 1e-24, 1e-24, 1e-24]) + pp = DataSet1D(name='pp', x=q, y=r_pp, ye=variance) + mm = DataSet1D(name='mm', x=q, y=r_mm, ye=variance) + project = self._project_with(pp, mm) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + assert result['low_significance_points'] == 3 + assert result['small_denominator_points'] == 3 + assert result['masked_points'] == 6 + + +class TestSpinAsymmetryChannelValidation: + """CR2: do not advertise a spin asymmetry that cannot be computed.""" + + @staticmethod + def _project_with(pp: DataSet1D, mm: DataSet1D) -> Project: + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(_magnetic_model(None)) + project._experiments[0] = PolarizedDataSet(name='validation', channels={'pp': pp, 'mm': mm}, model=project.models[0]) + return project + + def test_empty_channel_is_not_advertised(self): + q = np.linspace(0.01, 0.2, 5) + project = self._project_with( + DataSet1D(name='pp', x=np.array([]), y=np.array([])), + DataSet1D(name='mm', x=q, y=np.full_like(q, 0.2)), + ) + + assert project.experiment_supports_spin_asymmetry_at_index(0) is False + + def test_duplicate_q_is_not_advertised(self): + q = np.array([0.01, 0.02, 0.02, 0.03]) + project = self._project_with( + DataSet1D(name='pp', x=q, y=np.full_like(q, 0.6)), + DataSet1D(name='mm', x=q, y=np.full_like(q, 0.2)), + ) + + assert project.experiment_supports_spin_asymmetry_at_index(0) is False + + def test_non_finite_q_is_not_advertised(self): + q = np.array([0.01, 0.02, np.nan, 0.03]) + project = self._project_with( + DataSet1D(name='pp', x=q, y=np.full_like(q, 0.6)), + DataSet1D(name='mm', x=q, y=np.full_like(q, 0.2)), + ) + + assert project.experiment_supports_spin_asymmetry_at_index(0) is False + + def test_descending_grid_is_sorted_before_pairing(self): + # np.interp needs an increasing grid and returns nonsense otherwise. + q = np.linspace(0.01, 0.2, 6) + descending = q[::-1] + project = self._project_with( + DataSet1D(name='pp', x=descending, y=np.full_like(q, 0.6)), + DataSet1D(name='mm', x=q + 0.0005, y=np.linspace(0.2, 0.3, 6)), + ) + + assert project.experiment_supports_spin_asymmetry_at_index(0) is True + measured = project.spin_asymmetry_for_experiment_at_index(0)['measured'] + + assert np.all(np.diff(measured.x) > 0) + assert np.all(np.isfinite(measured.y)) + + +class TestSpinAsymmetryInterpolatedVariance: + """CR2: variances interpolate with squared weights.""" + + def test_midpoint_variance_uses_squared_weights(self): + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(_magnetic_model(None)) + # mm sampled at 0.10 and 0.20 with variances 1 and 9; pp asks for 0.15. + pp = DataSet1D(name='pp', x=np.array([0.15]), y=np.array([1.0]), ye=np.array([0.0])) + mm = DataSet1D(name='mm', x=np.array([0.10, 0.20]), y=np.array([0.5, 0.5]), ye=np.array([0.01, 0.09])) + project._experiments[0] = PolarizedDataSet(name='interp', channels={'pp': pp, 'mm': mm}, model=project.models[0]) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + # var_mm at the midpoint: 0.25*0.01 + 0.25*0.09 = 0.025 (the linear rule + # would give 0.05). + r_pp, r_mm = 1.0, 0.5 + expected = 4.0 * (r_pp**2 * 0.025) / (r_pp + r_mm) ** 4 + assert_allclose(result['measured'].ye, [expected], rtol=1e-12) + + def test_endpoints_take_the_source_variance_unchanged(self): + global_object.map._clear() + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(_magnetic_model(None)) + pp = DataSet1D(name='pp', x=np.array([0.10, 0.20]), y=np.array([1.0, 1.0]), ye=np.array([0.0, 0.0])) + mm = DataSet1D(name='mm', x=np.array([0.10, 0.15, 0.20]), y=np.array([0.5, 0.5, 0.5]), ye=np.array([0.01, 0.04, 0.09])) + project._experiments[0] = PolarizedDataSet(name='endpoints', channels={'pp': pp, 'mm': mm}, model=project.models[0]) + + result = project.spin_asymmetry_for_experiment_at_index(0) + + r_pp, r_mm = 1.0, 0.5 + expected = 4.0 * (r_pp**2 * np.array([0.01, 0.09])) / (r_pp + r_mm) ** 4 + assert_allclose(result['measured'].ye, expected, rtol=1e-12) + + +class TestMagneticProfileDisplayContinuity: + """CR2: a periodic angle must not be drawn as a full sweep.""" + + def test_angle_is_continuous_across_the_zero_boundary(self): + profiles = TestMagneticProfileSmoothing()._profiles(359.0, 1.0) + + theta = profiles['theta_m'].y + + # 359 -> 361 rather than 359 -> 1: no 358-degree jump anywhere. + assert np.abs(np.diff(theta)).max() < 10.0 + assert theta.max() - theta.min() < 10.0 + + def test_component_smoothing_failure_is_not_papered_over(self): + # The wrapper must refuse rather than serve refl1d's angle-smoothed + # profile, which misreports the splitting at such an interface. + global_object.map._clear() + model = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=270.0)) + project = Project() + project.calculator = 'refl1d' + project.models = ModelCollection(model) + calculator = project.models[0].interface() + + with patch.object( + type(calculator._wrapper), '_smoothed_magnetic_vector', side_effect=NotImplementedError('no microslabs') + ): + with pytest.raises(NotImplementedError): + project.magnetic_sld_data_for_model_at_index(0) + + +class TestCalculatorCapabilities: + """Helpers an application needs to offer the engine magnetism requires.""" + + def test_reports_which_calculators_support_magnetism(self): + project = Project() + + supporting = project.calculators_supporting_magnetism + + assert supporting == ['refl1d'] + # Asking must not change the active calculator. + assert project.calculator == 'refnx' + + def test_models_have_magnetism_follows_the_sample(self): + project = Project() + project.calculator = 'refl1d' + project.default_model() + + assert project.models_have_magnetism is False + + project.models[0].sample[1].layers[0].magnetism = LayerMagnetism(rho_m=2.0) + assert project.models_have_magnetism is True + + project.models[0].sample[1].layers[0].magnetism = None + assert project.models_have_magnetism is False From ba07dde068a5a1c38a54af7ed87d317bc8741f36 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Thu, 27 Aug 2026 14:53:07 +0200 Subject: [PATCH 24/38] dont show 0.0 for parameter errors where no fitting was done (#397) --- src/easyreflectometry/summary/summary.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index 89b82f48..cde96538 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -291,7 +291,9 @@ def _sample_section(self) -> str: html_parameter = html_parameter.replace('parameter_name', f'{name}') html_parameter = html_parameter.replace('parameter_value', _format_value(value, 3)) html_parameter = html_parameter.replace('parameter_unit', f'{unit}') - error_str = _format_value(error, 2) + # An unfitted parameter has no uncertainty; a literal '0.0' would + # read as a perfectly determined value, so leave the cell empty. + error_str = _format_value(error, 2) if error else '' html_parameter = html_parameter.replace('parameter_error', error_str) html_parameters.append(html_parameter) From d674b2bbcb8f85d1b22131c52ca8e36a3dc31c6f Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 28 Aug 2026 10:25:01 +0200 Subject: [PATCH 25/38] removed vestiges of BA (#394) --- src/easyreflectometry/calculators/__init__.py | 7 - .../calculators/bornagain/calculator.py | 193 --------- .../calculators/bornagain/wrapper.py | 391 ------------------ src/easyreflectometry/summary/summary.py | 1 - .../bornagain/test_bornagain_calculator.py | 121 ------ .../bornagain/test_bornagain_wrapper.py | 248 ----------- tests/model/test_model.py | 60 --- 7 files changed, 1021 deletions(-) delete mode 100644 src/easyreflectometry/calculators/bornagain/calculator.py delete mode 100644 src/easyreflectometry/calculators/bornagain/wrapper.py delete mode 100644 tests/calculators/bornagain/test_bornagain_calculator.py delete mode 100644 tests/calculators/bornagain/test_bornagain_wrapper.py diff --git a/src/easyreflectometry/calculators/__init__.py b/src/easyreflectometry/calculators/__init__.py index 1282928d..8019bff0 100644 --- a/src/easyreflectometry/calculators/__init__.py +++ b/src/easyreflectometry/calculators/__init__.py @@ -17,13 +17,6 @@ traceback.print_exc() print('Warning: refnx is not installed') -# try: -# from .bornagain.calculator import BornAgain -# imported_calculators.append(BornAgain) -# except Exception: -# traceback.print_exc() -# print('Warning: BornAgain python is not installed') - try: from .refl1d.calculator import Refl1d # noqa: F401 diff --git a/src/easyreflectometry/calculators/bornagain/calculator.py b/src/easyreflectometry/calculators/bornagain/calculator.py deleted file mode 100644 index 2ebb2c08..00000000 --- a/src/easyreflectometry/calculators/bornagain/calculator.py +++ /dev/null @@ -1,193 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - - -from easyscience.fitting.calculators.interface_factory import ItemContainer - -from easyreflectometry.model import Model -from easyreflectometry.sample import Layer -from easyreflectometry.sample import Material -from easyreflectometry.sample import MaterialMixture -from easyreflectometry.sample import Multilayer - -from ..calculator_base import CalculatorBase -from .wrapper import BornAgainWrapper - -""" -THIS CODE IS NOT FUNCTIONAL -PLEASE CONSULT ONE OF THE OTHER CALCULATORS FOR A FUNCTIONAL EXAMPLE -""" - - -class BornAgain(CalculatorBase): - """Calculator for BornAgain.""" - - name = 'BornAgain' - - _material_link = { - 'sld': 'real', - 'isld': 'imag', - } - - _layer_link = { - 'thickness': 'thickness', - 'roughness': 'sigma', - } - - _item_link = { - 'repetitions': 'repeats', - } - - _model_link = { - 'scale': 'scale', - 'background': 'background', - 'resolution': 'resolution', - } - - def __init__(self): - """Init function.""" - super().__init__() - self._wrapper = BornAgainWrapper() - - def reset_storage(self) -> None: - """Reset the storage area of the calculator.""" - self._wrapper.reset_storage() - - def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemContainer]: - """Creation function. - - Parameters - ---------- - model : Material | Layer | Multilayer | Model - Object to be created. - - Returns - ------- - List[ItemContainer] - Item containers of the objects. - """ - r_list = [] - t_ = type(model) - if issubclass(t_, Material): - key = model.unique_name - if key not in self._wrapper.storage['material'].keys(): - self._wrapper.create_material(key) - r_list.append( - ItemContainer( - key, - self._material_link, - self._wrapper.get_material_value, - self._wrapper.update_material, - ) - ) - elif issubclass(t_, MaterialMixture): - key = model.unique_name - if key not in self._wrapper.storage['material'].keys(): - self._wrapper.create_material(key) - r_list.append( - ItemContainer( - key, - self._material_link, - self._wrapper.get_material_value, - self._wrapper.update_material, - ) - ) - elif issubclass(t_, Layer): - key = model.unique_name - if key not in self._wrapper.storage['layer'].keys(): - self._wrapper.create_layer(key) - r_list.append( - ItemContainer( - key, - self._layer_link, - self._wrapper.get_layer_value, - self._wrapper.update_layer, - ) - ) - self.assign_material_to_layer(model.material.unique_name, key) - elif issubclass(t_, Multilayer): - key = model.unique_name - self._wrapper.create_item(key) - r_list.append( - ItemContainer( - key, - self._item_link, - self._wrapper.get_item_value, - self._wrapper.update_item, - ) - ) - for i in model.layers: - self.add_layer_to_item(i.unique_name, model.unique_name) - elif issubclass(t_, Model): - self._wrapper.create_model() - r_list.append( - ItemContainer( - 'model', - self._model_link, - self._wrapper.get_model_value, - self._wrapper.update_model, - ) - ) - for i in model.structure: - self.add_item_to_model(i.unique_name) - return r_list - - def assign_material_to_layer(self, material_id: int, layer_id: int) -> None: - """Assign a material to a layer. - - Parameters - ---------- - layer_id : int - material_id : int - material_name : str - The material name. - layer_name : str - The layer name. - """ - self._wrapper.assign_material_to_layer(material_id, layer_id) - - def add_layer_to_item(self, layer_id: int, item_id: int) -> None: - """Add a layer to the item stack. - - Parameters - ---------- - item_id : int - The item id. - layer_id : int - The layer id. - """ - self._wrapper.add_layer_to_item(layer_id, item_id) - - def remove_layer_from_item(self, layer_id: int, item_id: int) -> None: - """Remove a layer from an item stack. - - Parameters - ---------- - item_id : int - The item id. - layer_id : int - The layer id. - """ - self._wrapper.remove_layer_from_item(layer_id, item_id) - - def add_item_to_model(self, item_id: int) -> None: - """Add a layer to the item stack. - - Parameters - ---------- - item_id : int - The item id. - """ - self._wrapper.add_item(item_id) - - def remove_item_from_model(self, item_id: int) -> None: - """Remove a layer from the item stack. - - Parameters - ---------- - item_id : int - The item id. - layer_id : int - The layer id. - """ - self._wrapper.remove_item(item_id) diff --git a/src/easyreflectometry/calculators/bornagain/wrapper.py b/src/easyreflectometry/calculators/bornagain/wrapper.py deleted file mode 100644 index b0baf9a9..00000000 --- a/src/easyreflectometry/calculators/bornagain/wrapper.py +++ /dev/null @@ -1,391 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - - -import bornagain as ba -import numpy as np -from scipy.stats import norm - -from ..wrapper_base import WrapperBase - -""" -THIS CODE IS NOT FUNCTIONAL -PLEASE CONSULT ONE OF THE OTHER WRAPPES FOR A FUNCTIONAL EXAMPLE -""" - - -class BornAgainWrapper(WrapperBase): - def __init__(self): - """Init function.""" - super().__init__() - self.storage = { - 'layer_material': {}, - 'roughness': {}, - 'item_repeats': {}, - 'model_items': [], - 'model_parameters': {}, - } - - def reset_storage(self): - """Reset the storage area to blank.""" - super().reset_storage() - self.storage = { - 'layer_material': {}, - 'roughness': {}, - 'item_repeats': {}, - 'model_items': [], - 'model_parameters': {}, - } - - def create_material(self, name): - """Create a material using SLD. - - Parameters - ---------- - name : str - The name of the material. - """ - self.storage['material'][name] = ba.MaterialBySLD(str(name), 0.0, 0.0) - - def update_material(self, name, **kwargs): - """Update a material. - - Parameters - ---------- - **kwargs : - name : str - The name of the material. - """ - current_value = self.storage['material'][name].materialData() - real = current_value.real - imag = current_value.imag - if 'real' in kwargs.keys(): - real = kwargs['real'] * 1e-6 - if 'imag' in kwargs.keys(): - if kwargs['imag'] < 0: - raise ValueError('The BornAgain interface does not support negative imaginary scattering length densities') - imag = kwargs['imag'] * 1e-6 - self.storage['material'][name] = ba.MaterialBySLD(str(name), real, imag) - - def get_material_value(self, name, key): - """A function to get a given material value. - - Parameters - ---------- - name : str - The material name. - key : str - The given value keys. - - Returns - ------- - float - The desired value. - """ - current_value = self.storage['material'][name].materialData() - return getattr(current_value, key) / 1e-6 - - def create_layer(self, name): - """Create a layer using Slab. - - Parameters - ---------- - name : str - The name of the layer. - """ - self.storage['layer'][name] = ba.Layer(ba.MaterialBySLD('A', 0, 0)) - self.storage['roughness'][name] = ba.LayerRoughness() - - def update_layer(self, name, **kwargs): - """Update a layer in a given item. - - Parameters - ---------- - **kwargs : - name : str - The layer name. - """ - if 'thickness' in kwargs.keys(): - thickness = kwargs['thickness'] - self.storage['layer'][name] = ba.Layer( - self.storage['material'][self.storage['layer_material'][name]], - thickness * ba.angstrom, - ) - if 'sigma' in kwargs.keys(): - sigma = kwargs['sigma'] - self.storage['roughness'][name] = ba.LayerRoughness() - self.storage['roughness'][name].setSigma(sigma * ba.angstrom) - - def get_layer_value(self, name, key): - """A function to get a given layer value. - - Parameters - ---------- - name : str - The layer name. - key : str - The given value keys. - - Returns - ------- - float - The desired value. - """ - layer = self.storage['layer'][name] - roughness = self.storage['roughness'][name] - if key == 'thickness': - return layer.thickness() / ba.angstrom - if key == 'sigma': - return roughness.getSigma() / ba.angstrom - - def create_item(self, name): - """Create an item. - - Parameters - ---------- - name : str - The name of the item. - """ - self.storage['item'][name] = [] - self.storage['item_repeats'][name] = 1 - - def update_item(self, name, **kwargs): - """Update a layer. - - Parameters - ---------- - **kwargs : - name : str - The item name. - """ - if 'repeats' in kwargs.keys(): - self.storage['item_repeats'][name] = kwargs['repeats'] - - def get_item_value(self, name, key): - """A function to get a given item value. - - Parameters - ---------- - name : str - The item name. - key : str - The given value keys. - - Returns - ------- - float - The desired value. - """ - if key == 'repeats': - return self.storage['item_repeats'][name] - - def create_model(self): - """Create a model for analysis.""" - self.storage['model'] = ba.Multilayer() - self.storage['model'].setRoughnessModel(ba.RoughnessModel.NEVOT_CROCE) - self.storage['model_items'] = [] - self.storage['model_parameters']['scale'] = 1 - self.storage['model_parameters']['background'] = 0 - self.storage['model_parameters']['resolution'] = 0 - - def update_model(self, name, **kwargs): - """Update the non-structural parameters of the model.""" - model = self.storage[name + '_parameters'] - for key in kwargs.keys(): - model[key] = kwargs[key] - - def get_model_value(self, name, key): - """A function to get a given model value. - - Parameters - ---------- - name : - key : str - The given value keys. - - Returns - ------- - float - The desired value. - """ - model = self.storage[name + '_parameters'] - return model[key] - - def assign_material_to_layer(self, material_name, layer_name): - """Assign a material to a layer. - - Parameters - ---------- - material_name : str - The material name. - layer_name : str - The layer name. - """ - self.storage['layer_material'][layer_name] = material_name - - def add_layer_to_item(self, layer_name, item_name): - """Create a layer from the material of the same name, in a given item. - - Parameters - ---------- - layer_name : int - The layer name. - item_name : int - The item name. - """ - item = self.storage['item'][item_name] - item.append(layer_name) - - def add_item(self, item_name): - """Add an item to the model. - - Parameters - ---------- - item_name : str - Items to add to model. - """ - self.storage['model_items'].append(item_name) - - def remove_layer_from_item(self, layer_name, item_name): - """Remove a layer in a given item. - - Parameters - ---------- - layer_name : int - The layer name. - item_name : int - The item name. - """ - layers_idx = self.storage['item'][item_name].index(layer_name) - del self.storage['layer'][layer_name] - del self.storage['item'][item_name][layers_idx] - del self.storage['layer_material'][layer_name] - - def remove_item(self, item_name): - """Remove a given item. - - Parameters - ---------- - item_name : int - The item name. - """ - item_idx = self.storage['model_items'].index(item_name) - del self.storage['model_items'][item_idx] - del self.storage['item_repeats'][item_name] - del self.storage['item'][item_name] - - # To conform the base class the signature should be - # def calculate(self, q_array: np.ndarray, model_name: str) -> np.ndarray: - def calculate(self, q_array: np.ndarray) -> np.ndarray: - """For a given q array calculate the corresponding reflectivity. - - Parameters - ---------- - q_array : np.ndarray - Array of data points to be calculated. - model_name : - The model name. - - Returns - ------- - np.ndarray - Reflectivity calculated at q. - """ - # 3.5 sigma to sync with refnx - n_sig = 3.5 - n_samples = 21 - distr = ba.RangedDistributionGaussian(n_samples, n_sig) - - scan = ba.QSpecScan(q_array / ba.angstrom) - scan.setAbsoluteQResolution( - distr, - q_array / ba.angstrom * (self.storage['model_parameters']['resolution'] * 0.5 / 100), - ) - - simulation = ba.SpecularSimulation() - simulation.setScan(scan) - - total_model = ba.Multilayer() - for i in self.storage['model_items']: - for k in range(int(self.storage['item_repeats'][i])): - for j in self.storage['item'][i]: - layer = ba.Layer( - self.storage['material'][self.storage['layer_material'][j]], - self.storage['layer'][j].thickness(), - ) - total_model.addLayerWithTopRoughness(layer, self.storage['roughness'][j]) - - simulation.setSample(total_model) - simulation.runSimulation() - - return ( - self.storage['model_parameters']['scale'] * simulation.result().array() - + self.storage['model_parameters']['background'] - ) - - def sld_profile(self) -> np.ndarray: - """Return the scattering length density profile. - - This is borrowed from the refnx implementation of the scattering length density. - - Returns - ------- - tuple[np.ndarray, np.ndarray] - Z and sld(z). - """ - number_of_layers = 0 - for i in self.storage['model_items']: - number_of_layers += len(self.storage['item'][i]) * self.storage['item_repeats'][i] - layers = np.zeros((int(number_of_layers), 4)) - - count = 0 - for i in self.storage['model_items']: - for k in range(int(self.storage['item_repeats'][i])): - for j in self.storage['item'][i]: - layers[count, 0] = self.storage['layer'][j].thickness() - layers[count, 1] = self.storage['material'][self.storage['layer_material'][j]].materialData().real - layers[count, 2] = self.storage['material'][self.storage['layer_material'][j]].materialData().imag - layers[count, 3] = self.storage['roughness'][j].getSigma() - count += 1 - - layers2 = np.copy(layers) - layers[:, 0] = np.fabs(layers2[:, 0]) - layers[:, 3] = np.fabs(layers2[:, 3]) - # bounding layers should have zero thickness - layers[0, 0] = layers[-1, 0] = 0 - - # distance of each interface from the fronting interface - dist = np.cumsum(layers[:-1, 0]) - zstart = -5 - 4 * np.fabs(layers2[1, 3]) - zend = 5 + dist[-1] + 4 * layers[-1, 3] - - npnts = 500 - zed = np.linspace(zstart, zend, num=npnts) - - # the output array - sld = np.ones_like(zed, dtype=float) * layers[0, 1] - - # work out the step in SLD at an interface - delta_rho = layers[1:, 1] - layers[:-1, 1] - - # use erf for roughness function, but step if the roughness is zero - def step(z, scale=1, loc=0): - """Step function.""" - new_z = z - loc - f = np.ones_like(new_z) * 0.5 - f[new_z <= -scale] = 0 - f[new_z >= scale] = 1 - return f - - step_f = step - erf_f = norm.cdf - sigma = layers[1:, 3] - - # accumulate the SLD of each step. - for i in range(int(number_of_layers) - 1): - f = erf_f - if sigma[i] == 0: - f = step_f - sld += delta_rho[i] * f(zed, scale=sigma[i], loc=dist[i]) - - return zed / ba.angstrom, sld * 1e6 diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index cde96538..512b434a 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -58,7 +58,6 @@ def _silence_pdf_converter(): _ENGINE_URLS: dict[str, str] = { 'refnx': 'https://refnx.readthedocs.io', 'refl1d': 'https://refl1d.readthedocs.io', - 'bornagain': 'https://www.bornagainproject.org', 'lm': 'https://lmfit.github.io/lmfit-py/', 'bumps': 'https://bumps.readthedocs.io', 'dfo': 'https://github.com/fitbenchmarking/dfo-ls', diff --git a/tests/calculators/bornagain/test_bornagain_calculator.py b/tests/calculators/bornagain/test_bornagain_calculator.py deleted file mode 100644 index 6a7c1aa9..00000000 --- a/tests/calculators/bornagain/test_bornagain_calculator.py +++ /dev/null @@ -1,121 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - -""" -Tests for BornAgain calculator. -""" - - -# import os -# import unittest -# import numpy as np -# from numpy.testing import assert_almost_equal, assert_equal, assert_allclose -# from easyreflectometry.interfaces.bornagain import BornAgain -# from easyreflectometry.sample.material import Material - -# class TestBornAgain(unittest.TestCase): -# def test_init(self): -# p = BornAgain() -# assert_equal(list(p.calculator.storage.keys()), -# ['material', 'layer', 'layer_material', 'roughness', 'item', -# 'item_repeats', 'model', 'model_items', 'model_parameters']) -# assert_equal(p._material_link['sld'], 'real') -# assert_equal(p._material_link['isld'], 'imag') -# assert_equal(p._layer_link['thickness'], 'thickness') -# assert_equal(p._layer_link['roughness'], 'sigma') -# assert_equal(p._item_link['repetitions'], 'repeats') -# assert_equal(p._model_link['scale'], 'scale') -# assert_equal(p._model_link['background'], 'background') -# assert_equal(p._model_link['resolution'], 'resolution') -# assert_equal(p.name, 'BornAgain') - -# def test_fit_func(self): -# p = BornAgain() -# p.calculator.create_material('Material1') -# p.calculator.update_material('Material1', real=0.000, imag=0.000) -# p.calculator.create_material('Material2') -# p.calculator.update_material('Material2', real=2.000, imag=0.000) -# p.calculator.create_material('Material3') -# p.calculator.update_material('Material3', real=4.000, imag=0.000) -# p.calculator.create_model() -# p.calculator.create_layer('Layer1') -# p.calculator.assign_material_to_layer('Material1', 'Layer1') -# p.calculator.create_layer('Layer2') -# p.calculator.assign_material_to_layer('Material2', 'Layer2') -# p.calculator.update_layer('Layer2', thickness=10, sigma=1.0) -# p.calculator.create_layer('Layer3') -# p.calculator.assign_material_to_layer('Material3', 'Layer3') -# p.calculator.update_layer('Layer3', sigma=1.0) -# p.calculator.create_item('Item') -# p.calculator.add_layer_to_item('Layer1', 'Item') -# p.calculator.add_layer_to_item('Layer2', 'Item') -# p.calculator.add_layer_to_item('Layer3', 'Item') -# p.calculator.add_item('Item') -# p.calculator.update_model('model', background=1e-7, resolution=5) -# q = np.linspace(0.001, 0.3, 10) -# expected = [ -# 9.99956517e-01, 2.16286891e-03, 1.14086254e-04, 1.93031759e-05, -# 4.94188894e-06, 1.54191953e-06, 5.45592112e-07, 2.26619392e-07, -# 1.26726993e-07, 1.01842852e-07 -# ] -# assert_allclose(p.fit_func(q), expected, rtol=0.04) - -# def test_calculate2(self): -# p = BornAgain() -# p.calculator.create_material('Material1') -# p.calculator.update_material('Material1', real=0.000, imag=0.000) -# p.calculator.create_material('Material2') -# p.calculator.update_material('Material2', real=2.000, imag=0.000) -# p.calculator.create_material('Material3') -# p.calculator.update_material('Material3', real=4.000, imag=0.000) -# p.calculator.create_model() -# p.calculator.create_layer('Layer1') -# p.calculator.assign_material_to_layer('Material1', 'Layer1') -# p.calculator.create_layer('Layer2') -# p.calculator.assign_material_to_layer('Material2', 'Layer2') -# p.calculator.update_layer('Layer2', thickness=10, sigma=1.0) -# p.calculator.create_layer('Layer3') -# p.calculator.assign_material_to_layer('Material3', 'Layer3') -# p.calculator.update_layer('Layer3', sigma=1.0) -# p.calculator.create_item('Item1') -# p.calculator.add_layer_to_item('Layer1', 'Item1') -# p.calculator.create_item('Item2') -# p.calculator.add_layer_to_item('Layer2', 'Item2') -# p.calculator.add_layer_to_item('Layer1', 'Item2') -# p.calculator.create_item('Item3') -# p.calculator.add_layer_to_item('Layer3', 'Item3') -# p.calculator.add_item('Item1') -# p.calculator.add_item('Item2') -# p.calculator.add_item('Item3') -# p.calculator.update_item('Item2', repeats=10) -# p.calculator.update_model('model', background=1e-7, resolution=5) -# q = np.linspace(0.001, 0.3, 10) -# expected = [1.000000e+00, 1.814452e-05, 1.225890e-04, 2.454331e-06, -# 6.676318e-06, 8.362728e-07, 1.141096e-06, 4.090968e-07, -# 3.489857e-07, 2.470789e-07] -# assert_allclose(p.fit_func(q), expected, rtol=0.01) - -# def test_sld_profile(self): -# p = BornAgain() -# p.calculator.create_material('Material1') -# p.calculator.update_material('Material1', real=0.000, imag=0.000) -# p.calculator.create_material('Material2') -# p.calculator.update_material('Material2', real=2.000, imag=0.000) -# p.calculator.create_material('Material3') -# p.calculator.update_material('Material3', real=4.000, imag=0.000) -# p.calculator.create_model() -# p.calculator.create_layer('Layer1') -# p.calculator.assign_material_to_layer('Material1', 'Layer1') -# p.calculator.create_layer('Layer2') -# p.calculator.assign_material_to_layer('Material2', 'Layer2') -# p.calculator.update_layer('Layer2', thickness=10, sigma=1.0) -# p.calculator.create_layer('Layer3') -# p.calculator.assign_material_to_layer('Material3', 'Layer3') -# p.calculator.update_layer('Layer3', sigma=1.0) -# p.calculator.create_item('Item') -# p.calculator.add_layer_to_item('Layer1', 'Item') -# p.calculator.add_layer_to_item('Layer2', 'Item') -# p.calculator.add_layer_to_item('Layer3', 'Item') -# p.calculator.add_item('Item') -# assert_almost_equal(p.sld_profile()[1][0], 0) -# assert_almost_equal(p.sld_profile()[1][-1], 4) diff --git a/tests/calculators/bornagain/test_bornagain_wrapper.py b/tests/calculators/bornagain/test_bornagain_wrapper.py deleted file mode 100644 index c52c084c..00000000 --- a/tests/calculators/bornagain/test_bornagain_wrapper.py +++ /dev/null @@ -1,248 +0,0 @@ -# SPDX-FileCopyrightText: 2026 EasyScience contributors -# SPDX-License-Identifier: BSD-3-Clause - -""" -Tests for the BornAgain wrapper. -""" - - -# import unittest -# import numpy as np -# from numpy.testing import assert_equal, assert_almost_equal, assert_allclose -# from easyreflectometry.calculators.bornagain import BornAgain -# import bornagain as ba - -# class TestBornAgain(unittest.TestCase): -# def test_init(self): -# p = BornAgain() -# assert_equal(list(p.storage.keys()), ['material', 'layer', 'layer_material', 'roughness', 'item', 'item_repeats', -# 'model', 'model_items', 'model_parameters']) -# assert_equal(issubclass(p.storage['material'].__class__, dict), True) - -# def test_create_material(self): -# p = BornAgain() -# p.create_material('Si') -# assert_equal(list(p.storage['material'].keys()), ['Si']) -# assert_almost_equal(p.storage['material']['Si'].materialData().real, 0.0) -# assert_almost_equal(p.storage['material']['Si'].materialData().imag, 0.0) -# assert_equal(p.storage['material']['Si'].getName(), 'Si') - -# def test_update_material(self): -# p = BornAgain() -# p.create_material('B') -# p.update_material('B', real=6.908, imag=0.278) -# assert_equal(list(p.storage['material'].keys()), ['B']) -# assert_almost_equal(p.storage['material'] -# ['B'].materialData().real, 6.908e-6) -# assert_almost_equal(p.storage['material'] -# ['B'].materialData().imag, 0.278e-6) - -# def test_update_material_neg_imag(self): -# p = BornAgain() -# p.create_material('B') -# with self.assertRaises(ValueError): -# p.update_material('B', real=6.908, imag=-0.278) - -# def test_get_material_value(self): -# p = BornAgain() -# p.create_material('B') -# p.update_material('B', real=6.908, imag=0.278) -# assert_equal(list(p.storage['material'].keys()), ['B']) -# assert_almost_equal(p.get_material_value('B', 'real'), 6.908) -# assert_almost_equal(p.get_material_value('B', 'imag'), 0.278) - -# def test_create_layer(self): -# p = BornAgain() -# p.create_layer('Si') -# assert_equal(list(p.storage['layer'].keys()), ['Si']) -# assert_almost_equal(p.storage['layer']['Si'].thickness(), 0) -# assert_almost_equal(p.storage['roughness']['Si'].getSigma(), 0) - -# def test_update_layer(self): -# p = BornAgain() -# p.create_material('Si') -# p.create_layer('Si') -# p.assign_material_to_layer('Si', 'Si') -# p.update_layer('Si', thickness=10, sigma=5) -# assert_almost_equal(p.storage['layer']['Si'].thickness(), 1) -# assert_almost_equal(p.storage['roughness']['Si'].getSigma(), 0.5) - -# def test_get_layer_value(self): -# p = BornAgain() -# p.create_material('Si') -# p.create_layer('Si') -# p.assign_material_to_layer('Si', 'Si') -# p.update_layer('Si', thickness=10, sigma=5) -# assert_almost_equal(p.get_layer_value('Si', 'thickness'), 10) -# assert_almost_equal(p.get_layer_value('Si', 'sigma'), 5) - -# def test_create_item(self): -# p = BornAgain() -# p.create_item('SiNi') -# assert_equal(list(p.storage['item'].keys()), ['SiNi']) -# assert_equal(p.storage['item']['SiNi'], []) -# assert_equal(p.storage['item_repeats']['SiNi'], 1) - -# def test_update_item(self): -# p = BornAgain() -# p.create_item('SiNi') -# p.update_item('SiNi', repeats=10) -# assert_almost_equal(p.storage['item_repeats']['SiNi'], 10) - -# def test_get_item_value(self): -# p = BornAgain() -# p.create_item('SiNi') -# p.update_item('SiNi', repeats=10) -# assert_almost_equal(p.get_item_value('SiNi', 'repeats'), 10) - -# def test_create_model(self): -# p = BornAgain() -# p.create_model() -# assert_equal(isinstance(p.storage['model'], ba.Multilayer), True) -# assert_equal(p.storage['model'].roughnessModel(), 2) -# assert_equal(list(p.storage['model_parameters'].keys()), ['scale', 'background', 'resolution']) - -# def test_update_model(self): -# p = BornAgain() -# p.create_model() -# p.update_model('model', scale=2, background=1e-3, resolution=2.0) -# assert_almost_equal(p.storage['model_parameters']['scale'], 2) -# assert_almost_equal(p.storage['model_parameters']['background'], 1e-3) -# assert_almost_equal(p.storage['model_parameters']['resolution'], 2.0) - -# def test_get_model_value(self): -# p = BornAgain() -# p.create_model() -# p.update_model('model', scale=2, background=1e-3, resolution=2.0) -# assert_almost_equal(p.get_model_value('model', 'scale'), 2) -# assert_almost_equal(p.get_model_value('model', 'background'), 1e-3) -# assert_almost_equal(p.get_model_value('model', 'resolution'), 2.0) - -# def test_assign_material_to_layer(self): -# p = BornAgain() -# p.create_material('B') -# p.update_material('B', real=6.908, imag=0.278) -# p.create_layer('B_layer') -# p.assign_material_to_layer('B', 'B_layer') -# assert_almost_equal( -# p.storage['material'][p.storage['layer_material']['B_layer']].materialData().real, 6.908e-6) -# assert_almost_equal( -# p.storage['material'][p.storage['layer_material']['B_layer']].materialData().imag, 0.278e-6) - -# def test_add_layer_to_item(self): -# p = BornAgain() -# p.create_material('B') -# p.update_material('B', real=6.908, imag=0.278) -# p.create_layer('B_layer') -# p.update_layer('B_layer', sigma=10) -# p.assign_material_to_layer('B', 'B_layer') -# p.create_item('B_item') -# assert_equal(len(p.storage['item']['B_item']), 0) -# p.add_layer_to_item('B_layer', 'B_item') -# assert_equal(len(p.storage['item']['B_item']), 1) - -# def test_add_item(self): -# p = BornAgain() -# p.create_material('B') -# p.update_material('B', real=6.908, imag=0.278) -# p.create_layer('B_layer') -# p.assign_material_to_layer('B', 'B_layer') -# p.create_item('B_item') -# p.add_layer_to_item('B_layer', 'B_item') -# p.create_model() -# assert_equal(len(p.storage['model_items']), 0) -# p.add_item('B_item') -# assert_equal(len(p.storage['model_items']), 1) - -# def test_remove_layer_from_item(self): -# p = BornAgain() -# p.create_material('B') -# p.update_material('B', real=6.908, imag=0.278) -# p.create_layer('B_layer') -# p.assign_material_to_layer('B', 'B_layer') -# p.create_item('B_item') -# p.add_layer_to_item('B_layer', 'B_item') -# assert_equal(len(p.storage['item']['B_item']), 1) -# p.remove_layer_from_item('B_layer', 'B_item') -# assert_equal(len(p.storage['item']['B_item']), 0) - -# def test_remove_item(self): -# p = BornAgain() -# p.create_material('B') -# p.update_material('B', real=6.908, imag=0.278) -# p.create_layer('B_layer') -# p.assign_material_to_layer('B', 'B_layer') -# p.create_item('B_item') -# p.add_layer_to_item('B_layer', 'B_item') -# p.create_model() -# p.add_item('B_item') -# assert_equal(len(p.storage['model_items']), 1) -# p.remove_item('B_item') -# assert_equal(len(p.storage['model_items']), 0) - -# def test_calculate(self): -# p = BornAgain() -# p.create_material('Material1') -# p.update_material('Material1', real=0.000, imag=0.000) -# p.create_material('Material2') -# p.update_material('Material2', real=2.000, imag=0.000) -# p.create_material('Material3') -# p.update_material('Material3', real=4.000, imag=0.000) -# p.create_model() -# p.create_layer('Layer1') -# p.assign_material_to_layer('Material1', 'Layer1') -# p.create_layer('Layer2') -# p.assign_material_to_layer('Material2', 'Layer2') -# p.update_layer('Layer2', thickness=10, sigma=1.0) -# p.create_layer('Layer3') -# p.assign_material_to_layer('Material3', 'Layer3') -# p.update_layer('Layer3', sigma=1.0) -# p.create_item('Item') -# p.add_layer_to_item('Layer1', 'Item') -# p.add_layer_to_item('Layer2', 'Item') -# p.add_layer_to_item('Layer3', 'Item') -# p.add_item('Item') -# p.update_model('model', background=1e-7, resolution=5) -# q = np.linspace(0.001, 0.3, 10) -# expected = [ -# 9.99956517e-01, 2.16286891e-03, 1.14086254e-04, 1.93031759e-05, -# 4.94188894e-06, 1.54191953e-06, 5.45592112e-07, 2.26619392e-07, -# 1.26726993e-07, 1.01842852e-07 -# ] -# assert_allclose(p.calculate(q), expected, rtol=0.04) - -# def test_calculate2(self): -# p = BornAgain() -# p.create_material('Material1') -# p.update_material('Material1', real=0.000, imag=0.000) -# p.create_material('Material2') -# p.update_material('Material2', real=2.000, imag=0.000) -# p.create_material('Material3') -# p.update_material('Material3', real=4.000, imag=0.000) -# p.create_layer('Layer1') -# p.assign_material_to_layer('Material1', 'Layer1') -# p.create_layer('Layer2') -# p.assign_material_to_layer('Material2', 'Layer2') -# p.update_layer('Layer2', thickness=10, sigma=1.0) -# p.create_layer('Layer3') -# p.assign_material_to_layer('Material3', 'Layer3') -# p.update_layer('Layer3', sigma=1.0) -# p.create_item('Item1') -# p.add_layer_to_item('Layer1', 'Item1') -# p.create_item('Item2') -# p.add_layer_to_item('Layer2', 'Item2') -# p.add_layer_to_item('Layer1', 'Item2') -# p.create_item('Item3') -# p.add_layer_to_item('Layer3', 'Item3') -# p.create_model() -# p.add_item('Item1') -# p.add_item('Item2') -# p.add_item('Item3') -# p.update_item('Item2', repeats=10) -# p.update_model('model', background=1e-7, resolution=5) -# q = np.linspace(0.001, 0.3, 10) -# expected = [1.000000e+00, 1.814452e-05, 1.225890e-04, 2.454331e-06, -# 6.676318e-06, 8.362728e-07, 1.141096e-06, 4.090968e-07, -# 3.489857e-07, 2.470789e-07] -# assert_allclose(p.calculate(q), expected, rtol=0.01) -# assert_allclose(p.calculate(q), expected, rtol=0.01) diff --git a/tests/model/test_model.py b/tests/model/test_model.py index a2dd46ea..e0476552 100644 --- a/tests/model/test_model.py +++ b/tests/model/test_model.py @@ -157,25 +157,6 @@ def test_add_assemblies_with_interface_refl1d(self): assert_equal(len(mod.interface()._wrapper.storage['item']), 2) assert_equal(len(mod.interface()._wrapper.storage['layer']), 2) - # def test_add_assemblies_with_interface_bornagain(self): - # interface = CalculatorFactory() - # interface.switch('BornAgain') - # m1 = Material.from_pars(6.908, 0.278, 'Boron') - # m2 = Material.from_pars(0.487, 0.000, 'Potassium') - # l1 = Layer.from_pars(m1, 5.0, 2.0, 'thinBoron') - # l2 = Layer.from_pars(m2, 50.0, 1.0, 'thickPotassium') - # ls1 = Layers.from_pars(l1, l2, name='twoLayer1') - # ls2 = Layers.from_pars(l2, l1, name='twoLayer2') - # o1 = RepeatingMultilayer.from_pars(ls1, 2.0, 'twoLayerItem1') - # o2 = RepeatingMultilayer.from_pars(ls2, 1.0, 'oneLayerItem2') - # d = Sample.from_pars(o1, name='myModel') - # mod = Model(d, 2, 1e-5, 2.0, 'newModel', interface=interface) - # assert_equal(len(mod.interface()._wrapper.storage['item']), 1) - # assert_equal(len(mod.interface()._wrapper.storage['layer']), 2) - # mod.add_assemblies(o2) - # assert_equal(len(mod.interface()._wrapper.storage['item']), 2) - # assert_equal(len(mod.interface()._wrapper.storage['layer']), 2) - def test_duplicate_assembly(self): m1 = Material(6.908, -0.278, 'Boron') m2 = Material(0.487, 0.000, 'Potassium') @@ -235,25 +216,6 @@ def test_duplicate_assembly_with_interface_refl1d(self): mod.duplicate_assembly(1) assert_equal(len(mod.interface()._wrapper.storage['item']), 3) - # def test_duplicate_item_with_interface_bornagain(self): - # interface = CalculatorFactory() - # interface.switch('BornAgain') - # m1 = Material.from_pars(6.908, 0.278, 'Boron') - # m2 = Material.from_pars(0.487, 0.000, 'Potassium') - # l1 = Layer.from_pars(m1, 5.0, 2.0, 'thinBoron') - # l2 = Layer.from_pars(m2, 50.0, 1.0, 'thickPotassium') - # ls1 = Layers.from_pars(l1, l2, name='twoLayer1') - # ls2 = Layers.from_pars(l2, l1, name='twoLayer2') - # o1 = RepeatingMultilayer.from_pars(ls1, 2.0, 'twoLayerItem1') - # o2 = RepeatingMultilayer.from_pars(ls2, 1.0, 'oneLayerItem2') - # d = Sample.from_pars(o1, name='myModel') - # mod = Model(d, 2, 1e-5, 2.0, 'newModel', interface=interface) - # assert_equal(len(mod.interface()._wrapper.storage['assembly']), 1) - # mod.add_assemblies(o2) - # assert_equal(len(mod.interface()._wrapper.storage['item']), 2) - # mod.duplicate_assembly(1) - # assert_equal(len(mod.interface()._wrapper.storage['item']), 3) - def test_remove_assembly(self): m1 = Material(6.908, -0.278, 'Boron') m2 = Material(0.487, 0.000, 'Potassium') @@ -317,28 +279,6 @@ def test_remove_assembly_with_interface_refl1d(self): assert_equal(len(mod.interface()._wrapper.storage['item']), 1) assert_equal(len(mod.interface()._wrapper.storage['layer']), 2) - # def test_remove_assembly_with_interface_bornagain(self): - # interface = CalculatorFactory() - # interface.switch('BornAgain') - # m1 = Material.from_pars(6.908, 0.278, 'Boron') - # m2 = Material.from_pars(0.487, 0.000, 'Potassium') - # l1 = Layer.from_pars(m1, 5.0, 2.0, 'thinBoron') - # l2 = Layer.from_pars(m2, 50.0, 1.0, 'thickPotassium') - # ls1 = Layers.from_pars(l1, l2, name='twoLayer1') - # ls2 = Layers.from_pars(l2, l1, name='twoLayer2') - # o1 = RepeatingMultilayer.from_pars(ls1, 2.0, 'twoLayerItem1') - # o2 = RepeatingMultilayer.from_pars(ls2, 1.0, 'oneLayerItem2') - # d = Sample.from_pars(o1, name='myModel') - # mod = Model(d, 2, 1e-5, 2.0, 'newModel', interface=interface) - # assert_equal(len(mod.interface()._wrapper.storage['item']), 1) - # assert_equal(len(mod.interface()._wrapper.storage['layer']), 2) - # mod.add_assemblies(o2) - # assert_equal(len(mod.interface()._wrapper.storage['item']), 2) - # assert_equal(len(mod.interface()._wrapper.storage['layer']), 2) - # mod.remove_assembly(0) - # assert_equal(len(mod.interface()._wrapper.storage['item']), 1) - # assert_equal(len(mod.interface()._wrapper.storage['layer']), 2) - def test_remove_all_assemblies(self): # when mod = Model() From c869e636eebe515ee27d9e9ddf94388b85216f1a Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Tue, 1 Sep 2026 09:29:29 +0200 Subject: [PATCH 26/38] Improved constraints + doc migration (#400) * initial version * added magnetic SLD profile * added magnetic parameters * ruff * code review comments addressed * code review fixes for Phase 2, added notebook * ruff * PR code review comments * fixed polarized file load issue * enable magnetic layers * new LayerMagnetism component * ruff * ruff on notebooks * bind calculator to model for performance * attempt at fixing package testing * package tests only on master * don't run ruff twice * code review fixes * added polarized fitting example/notebook * fixed default sample generation * improved wording in the magnetic fitting notebook * move the most expensive test to integration * Improved constraints (#395) * improved handling of constraints * ruff * wording * additional cell in a notebook to showcase the new way of doing constraints * fixed notebook * initial checkin * .bounds -> min, max * updates so the code is self-contained and doesn't depend on changes to core * Code review comments addressed * Updated docs (#398) * move everything to MKDocs minor updates/fixes after code review * ruff fix for notebook * Fix broken conflict resolutions from develop merge The merge of develop (2042778) left two files with damaged conflict resolutions: magnetism.ipynb gained a corrupt hybrid cell (invalid notebook JSON, duplicate cell id) and project.py gained a duplicated copy of the spin-asymmetry method block with an orphaned method body. Both files are restored to the branch-side version, which already contained everything develop had for them. Co-Authored-By: Claude Fable 5 * code review issues addressed * removed explicit EasyCore constraints factory reliance --------- Co-authored-by: Claude Fable 5 --- .github/copilot-instructions.md | 31 +- .github/workflows/documentation-build.yml | 68 -- BAYESIAN_IN_ERL.md | 361 --------- CHANGELOG.md | 64 ++ docs/Makefile | 20 - docs/docs/api-reference/bayesian.md | 1 + docs/docs/api-reference/constraints.md | 85 +++ .../api-reference/elements/layer_magnetism.md | 1 + docs/docs/api-reference/index.md | 38 + .../api-reference/inequality_constraints.md | 36 + docs/docs/api-reference/limits.md | 1 + docs/docs/api-reference/orso.md | 1 + docs/docs/api-reference/plot.md | 1 + docs/docs/api-reference/summary.md | 1 + .../advancedfitting/bayesian_bumps.ipynb | 14 +- .../advancedfitting/constraints.ipynb | 696 ++++++++++++++++++ .../advancedfitting/multi_contrast.ipynb | 86 ++- .../advancedfitting/polarized_fitting.ipynb | 9 +- .../tutorials/basic/assemblies_library.md | 50 +- docs/docs/tutorials/basic/model.md | 93 +++ .../tutorials/fitting/material_solvated.ipynb | 33 +- docs/docs/tutorials/fitting/monolayer.ipynb | 22 +- docs/docs/tutorials/fitting/repeating.ipynb | 12 +- .../tutorials/fitting/simple_fitting.ipynb | 57 +- docs/docs/tutorials/index.md | 16 +- .../docs/tutorials/simulation/magnetism.ipynb | 180 +---- .../simulation/resolution_functions.ipynb | 22 +- docs/make.bat | 36 - docs/mkdocs.yml | 11 + docs/src/_static/favicon.ico | Bin 21690 -> 0 bytes docs/src/_static/logo.png | Bin 395415 -> 0 bytes docs/src/_static/logo.svg | 113 --- docs/src/api/api.rst | 85 --- docs/src/api/assemblies/gradient_layer.rst | 4 - docs/src/api/assemblies/multilayer.rst | 4 - .../api/assemblies/repeating_multilayer.rst | 4 - docs/src/api/assemblies/surfactant_layer.rst | 4 - docs/src/api/data.rst | 4 - docs/src/api/elements/layer.rst | 4 - .../api/elements/layer_area_per_molecule.rst | 4 - docs/src/api/elements/material.rst | 4 - docs/src/api/elements/material_density.rst | 5 - docs/src/api/elements/material_mixture.rst | 4 - docs/src/api/elements/material_solvated.rst | 4 - docs/src/api/fitting.rst | 112 --- docs/src/api/model.rst | 4 - docs/src/api/project.rst | 4 - docs/src/api/sample.rst | 4 - docs/src/authors.rst | 1 - docs/src/conf.py | 193 ----- docs/src/contributing.rst | 1 - docs/src/index.rst | 20 - docs/src/installation.rst | 39 - docs/src/usage.rst | 42 -- notebooks/polarized_fitting.ipynb | 11 +- notebooks/zero_variance_fitting.ipynb | 32 +- pixi.lock | 120 +-- pyproject.toml | 3 +- src/easyreflectometry/__init__.py | 20 + src/easyreflectometry/_bumps_constraints.py | 119 +++ src/easyreflectometry/analysis/bayesian.py | 6 +- src/easyreflectometry/constraints.py | 329 +++++++++ src/easyreflectometry/fitting.py | 217 +++++- .../inequality_constraints.py | 466 ++++++++++++ src/easyreflectometry/model/model.py | 64 ++ .../model/model_collection.py | 18 +- src/easyreflectometry/orso_utils.py | 30 +- src/easyreflectometry/project.py | 380 +++++++++- .../sample/assemblies/base_assembly.py | 54 ++ .../sample/assemblies/multilayer.py | 13 + .../sample/assemblies/repeating_multilayer.py | 8 + src/easyreflectometry/summary/summary.py | 8 +- src/easyreflectometry/utils.py | 26 +- tests/model/test_model_collection.py | 21 + tests/sample/assemblies/test_multilayer.py | 37 + .../assemblies/test_repeating_multilayer.py | 14 + .../elements/layers/test_layer_magnetism.py | 3 +- tests/test_bayesian.py | 34 +- tests/test_fitting.py | 54 +- tests/test_orso_utils.py | 75 ++ tests/test_polarized_fitting.py | 15 +- tests/test_project.py | 9 +- tests/test_utils.py | 20 + tests/unit/test_bumps_constraints_shim.py | 296 ++++++++ tests/unit/test_constraints.py | 442 +++++++++++ tests/unit/test_derived_parameters.py | 197 +++++ tests/unit/test_inequality_constraints.py | 326 ++++++++ 87 files changed, 4648 insertions(+), 1528 deletions(-) delete mode 100644 .github/workflows/documentation-build.yml delete mode 100644 BAYESIAN_IN_ERL.md delete mode 100644 docs/Makefile create mode 100644 docs/docs/api-reference/bayesian.md create mode 100644 docs/docs/api-reference/constraints.md create mode 100644 docs/docs/api-reference/elements/layer_magnetism.md create mode 100644 docs/docs/api-reference/inequality_constraints.md create mode 100644 docs/docs/api-reference/limits.md create mode 100644 docs/docs/api-reference/orso.md create mode 100644 docs/docs/api-reference/plot.md create mode 100644 docs/docs/api-reference/summary.md create mode 100644 docs/docs/tutorials/advancedfitting/constraints.ipynb delete mode 100644 docs/make.bat delete mode 100644 docs/src/_static/favicon.ico delete mode 100644 docs/src/_static/logo.png delete mode 100644 docs/src/_static/logo.svg delete mode 100644 docs/src/api/api.rst delete mode 100644 docs/src/api/assemblies/gradient_layer.rst delete mode 100644 docs/src/api/assemblies/multilayer.rst delete mode 100644 docs/src/api/assemblies/repeating_multilayer.rst delete mode 100644 docs/src/api/assemblies/surfactant_layer.rst delete mode 100644 docs/src/api/data.rst delete mode 100644 docs/src/api/elements/layer.rst delete mode 100644 docs/src/api/elements/layer_area_per_molecule.rst delete mode 100644 docs/src/api/elements/material.rst delete mode 100644 docs/src/api/elements/material_density.rst delete mode 100644 docs/src/api/elements/material_mixture.rst delete mode 100644 docs/src/api/elements/material_solvated.rst delete mode 100644 docs/src/api/fitting.rst delete mode 100644 docs/src/api/model.rst delete mode 100644 docs/src/api/project.rst delete mode 100644 docs/src/api/sample.rst delete mode 100644 docs/src/authors.rst delete mode 100644 docs/src/conf.py delete mode 100644 docs/src/contributing.rst delete mode 100644 docs/src/index.rst delete mode 100644 docs/src/installation.rst delete mode 100644 docs/src/usage.rst create mode 100644 src/easyreflectometry/_bumps_constraints.py create mode 100644 src/easyreflectometry/constraints.py create mode 100644 src/easyreflectometry/inequality_constraints.py create mode 100644 tests/unit/test_bumps_constraints_shim.py create mode 100644 tests/unit/test_constraints.py create mode 100644 tests/unit/test_derived_parameters.py create mode 100644 tests/unit/test_inequality_constraints.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 533cae79..e51d6344 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -91,28 +91,37 @@ tox ### Docstring Style - Include docstrings for all public modules, classes, and functions -- Use **Sphinx/reStructuredText style** docstrings (`:param`, `:type`, - `:return`, `:rtype`) +- Use **NumPy style** docstrings (`Parameters` / `Returns` / `Raises` + sections), the style `mkdocstrings` is configured to parse - Use clear, concise descriptions - Document parameters, return values, and exceptions - Example format: ```python - """ - Brief description of the function. + """Brief description of the function. + + Parameters + ---------- + param_name : type + Description of the parameter. - :param param_name: description of parameter - :type param_name: type - :return: description of return value - :rtype: return_type + Returns + ------- + type + Description of the return value. """ ``` ### Documentation Build -- Documentation is built using Sphinx (version 8.1.3) -- Source files are in the `docs/` directory -- Use `myst_parser` (MyST parser) for Markdown support +- Documentation is built with MkDocs (Material theme), configured in + `docs/mkdocs.yml` +- Source files are Markdown and Jupyter notebooks under `docs/docs/`; + every page must be listed in the `nav` section of `docs/mkdocs.yml` +- API reference pages are one-liners rendered by `mkdocstrings` + (`::: easyreflectometry.`) +- Build locally with `pixi run docs-build` or preview with + `pixi run docs-serve` - Include code examples in documentation where appropriate ## Dependencies diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml deleted file mode 100644 index b3477bb6..00000000 --- a/.github/workflows/documentation-build.yml +++ /dev/null @@ -1,68 +0,0 @@ -# This pipeline -# - builds developer documentation -# - pushes documentation to gh-pages branch of the same repository -# -# Deployment is handled by pages-build-deployment bot -# -# For more information see: https://docs.github.com/en/pages/getting-started-with-github-pages - -name: Build Documentation and Push to gh-pages Branch - -# Controls when the workflow will run -on: - # Triggers the workflow on tag creation - push: - tags: - - 'v*' - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build_documentation: - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - # Grant GITHUB_TOKEN the permissions required to make a Pages deployment - permissions: - contents: read # to clone the repository - pages: write # to deploy to Pages - id-token: write # to verify the deployment originates from an appropriate source - - steps: - - name: Checkout - uses: actions/checkout@master - with: - fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - - name: Upgrade pip - run: | - python -m pip install --upgrade pip - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.12 - - name: Install Pandoc, repo and dependencies - run: | - sudo apt install pandoc - sudo apt install libcairo2-dev - pip install sphinx==8.1.3 - pip install . '.[dev,docs]' - - - name: Install Jupyter kernel - run: | - python -m ipykernel install --user --name=python3 - - - name: Build and Commit - uses: sphinx-notes/pages@v3 - with: - sphinx_version: 8.1.3 - documentation_path: docs/src - - name: Push changes - uses: ad-m/github-push-action@master - continue-on-error: true - with: - branch: gh-pages diff --git a/BAYESIAN_IN_ERL.md b/BAYESIAN_IN_ERL.md deleted file mode 100644 index dc99953f..00000000 --- a/BAYESIAN_IN_ERL.md +++ /dev/null @@ -1,361 +0,0 @@ -# Bayesian Analysis in EasyReflectometryLib — Implementation Plan - -## Current State - -- `BAYESIAN_BUMPS.md` (in `MD/`) describes a 3-phase plan. Phase 1 - (docs/notebook) is complete. -- `bayesian_bumps.py` (attached notebook) works but users must directly - import from `bumps.fitters`, `bumps.names`, `bumps.parameter` — a - leaky abstraction. -- The `Bumps` minimizer in `easyscience` - (`core/src/easyscience/fitting/minimizers/minimizer_bumps.py`) only - exposes **classical optimization** methods (`amoeba`, `newton`, `lm`), - NOT the DREAM/MCMC sampler. -- The `AvailableMinimizers` enum only has `Bumps`, `Bumps_simplex`, - `Bumps_newton`, `Bumps_lm`. This should remain optimizer-focused; - DREAM should be exposed as a sampling workflow, not as another - minimizer choice. -- `MultiFitter` in reflectometry-lib currently only has `fit()` and - `fit_single_data_set_1d()`. - -## Goal - -Users should be able to run Bayesian MCMC sampling with a **clean -high-level API** like: - -```python -fitter = MultiFitter(model) -fitter.switch_minimizer(AvailableMinimizers.Bumps) - -# Classical fit first -analysed = fitter.fit(data) - -# Bayesian sampling -posterior = fitter.sample(data, samples=5000, burn=1000, thin=10) - -# Analyze -from easyreflectometry.analysis.bayesian import plot_corner, posterior_summary -plot_corner(posterior) -print(posterior_summary(posterior)) -``` - -Important API boundary: `fit()` remains classical optimization only. -Bayesian DREAM sampling is exposed through `sample()` so users do not -receive sampler-shaped results from an optimizer-shaped API. - -## Implementation Plan - -### Step 1 — Keep DREAM separate from `AvailableMinimizers` - -**File**: `core/src/easyscience/fitting/available_minimizers.py` - -Do **not** add `Bumps_dream` as a normal `AvailableMinimizers` member. -The enum is currently used to instantiate minimizer backends and to -route calls through `Fitter.fit()`, which expects optimizer-style -`FitResults`. DREAM is an MCMC sampler and returns a sampler state/chain -rather than a best-fit result. - -Use `AvailableMinimizers.Bumps` to select the BUMPS backend, then expose -DREAM through a dedicated `sample()` method. This avoids making -`project.minimizer = AvailableMinimizers.Bumps_dream` look like a valid -classical fitting mode. - -### Step 2 — Add dedicated DREAM sampling support to the Bumps minimizer (core repo) - -**File**: `core/src/easyscience/fitting/minimizers/minimizer_bumps.py` - -**2a.** Keep `supported_methods()` optimizer-only (`amoeba`, `newton`, -`lm`). Do not add `'dream'` there unless the EasyScience fitting -abstraction is later split into optimizer and sampler concepts. - -**2b.** Add a new method `sample()` to the `Bumps` class: - -```python -def sample( - self, - x: np.ndarray, - y: np.ndarray, - weights: np.ndarray, - samples: int = 10000, - burn: int = 2000, - thin: int = 10, - chains: int | None = None, - population: int | None = None, - model: Callable | None = None, - parameters: list | None = None, - progress_callback: Callable | None = None, - seed: int | None = None, - **kwargs, -) -> dict: - """Run Bayesian MCMC sampling using BUMPS DREAM sampler. - - Returns a dict with: - - 'draws': np.ndarray, shape (n_samples, n_params) — posterior samples - - 'param_names': list[str] — parameter names - - 'state': DreamState — raw BUMPS state for save/restore - - 'logp': np.ndarray — log-posterior values - """ -``` - -The implementation would: - -1. Build the `Curve` model + `FitProblem` (reuse `_make_model()` logic) -2. Translate user-friendly aliases to BUMPS DREAM settings and call - `bumps_fit(problem, method='dream', samples=samples, burn=burn, thin=thin, pop=population, ...)` -3. Extract `result.state.draw().points` and return structured dict -4. Preserve and restore the EasyScience global object stack state, - mirroring the existing `fit()` implementation -5. Handle multi-dataset via the same `MultiFitter._precompute_reshaping` - pattern - -**2c.** Do **not** modify `fit()` to handle `method='dream'`, and do -**not** delegate `fit(method='dream')` to `sample()`. `fit()` returns -`FitResults`; `sample()` returns posterior samples and sampler metadata. -Keeping the methods separate prevents incompatible return types from -leaking into `Fitter.fit()` and reflectometry-lib `MultiFitter.fit()`. - -Recommended alias mapping: - -| Public argument | BUMPS DREAM setting | Rationale | -| ---------------- | --------------------------- | ----------------------------------------------------------------------- | -| `samples` | `samples` | Clear user-facing chain length; prefer over optimizer-oriented `steps`. | -| `burn` | `burn` | Matches BUMPS and common MCMC terminology. | -| `thin` | `thin` | Matches BUMPS and common MCMC terminology. | -| `chains` | `pop` | User-friendly MCMC wording; maps to BUMPS population count. | -| `population` | `pop` | BUMPS-aware alias for advanced users. | -| `initialization` | `init` | More readable than `init`, but pass through to BUMPS. | -| `seed` | RNG seeding before sampling | Expose reproducibility without requiring users to know BUMPS internals. | - -If both `chains` and `population` are provided, raise `ValueError` -unless they match. Accept `steps` only as a deprecated alias for -`samples`, with a warning, because `steps` already means optimizer -budget in EasyScience `Bumps.fit()`. - -### Step 3 — Add `sample()` to reflectometry-lib `MultiFitter` - -**File**: `reflectometry-lib/src/easyreflectometry/fitting.py` - -Add a `sample()` method to `MultiFitter`: - -```python -def sample( - self, - data: sc.DataGroup, - samples: int = 10000, - burn: int = 2000, - thin: int = 10, - chains: int | None = None, - population: int | None = None, - seed: int | None = None, - objective: str | None = None, -) -> dict: - """Run Bayesian MCMC sampling on reflectometry data. - - :param data: DataGroup with reflectivity data. - :param samples: Number of retained DREAM samples requested from BUMPS. - :param burn: Burn-in steps. - :param thin: Thinning interval. - :param chains: User-friendly alias for BUMPS DREAM population count. - :param population: BUMPS DREAM population count (`pop`) for advanced users. - :param seed: Random seed for reproducibility. - :param objective: Zero-variance handling strategy. - :return: Dict with posterior samples, parameter names, and sampler state. - """ -``` - -Internally: - -1. Reuse `_prepare_fit_arrays` for data preparation -2. Mirror the EasyScience `Fitter.fit()` lifecycle for reshaping, fit - function wrapping, and restoration -3. Delegate to `self.easy_science_multi_fitter.minimizer.sample(...)` - for the MCMC -4. Handle multi-model / multi-contrast aggregation as one joint - posterior -5. Return structured posterior dict or `PosteriorResults` - -The `MultiFitter` currently stores `self.easy_science_multi_fitter` -which has `.minimizer` — we'll call `sample()` on it when the minimizer -is a `Bumps` instance. - -### Step 4 — Create Bayesian analysis module in reflectometry-lib - -**New file**: -`reflectometry-lib/src/easyreflectometry/analysis/__init__.py` **New -file**: `reflectometry-lib/src/easyreflectometry/analysis/bayesian.py` - -The `bayesian.py` module provides: - -```python -class PosteriorResults: - """Container for Bayesian posterior samples with analysis methods.""" - - draws: np.ndarray # (n_samples, n_params) - param_names: list[str] - logp: np.ndarray | None - sampler_state: Any | None - - def summary(self) -> str: - """Return formatted summary table with mean, sd, HDI for each parameter.""" - - def corner(self, **kwargs) -> None: - """Plot parameter correlation corner plot using the `corner` library.""" - - def credible_interval(self, alpha: float = 0.95) -> dict: - """Return {param_name: (lower, upper)} credible intervals.""" - - def gelman_rubin(self) -> dict: - """Compute R-hat convergence diagnostic.""" - -def posterior_summary(draws, param_names) -> str: ... -def plot_corner(draws, param_names, **kwargs) -> None: ... -def plot_trace(draws, param_names, **kwargs) -> None: ... -def credible_intervals(draws, param_names, alpha=0.95) -> dict: ... -``` - -**Posterior predictive functions** (reflectivity & SLD): - -```python -def posterior_predictive_reflectivity( - draws, param_names, model, q_values, n_samples=200 -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: - """Return (median, lower_95, upper_95) reflectivity arrays.""" - -def posterior_sld_profile( - draws, param_names, model, n_samples=200 -) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: - """Return (z, median, lower_95, upper_95) SLD profile arrays.""" -``` - -Posterior predictive helpers must save original parameter values and -errors before applying any posterior draw, and restore them in a -`finally` block after prediction. Parameter lookup should use -EasyScience parameter `unique_name` values, matching the BUMPS names -after removing the minimizer prefix, rather than display names. This -avoids leaving the model mutated after plotting and avoids collisions -when repeated models or multi-contrast fits contain similarly named -parameters. - -### Step 5 — Dependencies - -Add to `pyproject.toml` in reflectometry-lib: - -```toml -[project.optional-dependencies] -bayesian = ["corner>=2.2", "arviz>=0.18"] -``` - -Make `corner` and `arviz` optional imports — the analysis module should -work with graceful fallbacks when they're not installed. - -### Step 6 — Update exports - -**File**: `reflectometry-lib/src/easyreflectometry/__init__.py` - -Add `PosteriorResults` and analysis functions to the public API if -desired. - -### Step 7 — Update the example notebook - -**File**: -`reflectometry-lib/docs/src/tutorials/advancedfitting/bayesian_bumps.py` - -Replace low-level BUMPS calls with the new high-level API: - -- `fitter.sample(data, samples=500, burn=100, thin=10)` instead of - manual `FitProblem` + `bumps_fit` -- `plot_corner(posterior['draws'], posterior['param_names'])` instead of - manual `corner.corner()` -- `posterior_summary(...)` instead of manual numpy statistics - -### Step 8 — Tests - -**File**: `reflectometry-lib/tests/test_bayesian.py` (new) - -```python -def test_sample_basic(): ... -def test_posterior_summary_format(): ... -def test_corner_plot_does_not_crash(): ... -def test_credible_intervals(): ... -def test_sample_seed_reproducibility(): ... -``` - -Also add a test in `core/tests/` for the `Bumps.sample()` method. - -## Architecture Diagram - -``` -User Code - │ - ├─ fitter.fit(data) ──► classical chi² minimization - ├─ fitter.sample(data, ...) ──► Bayesian DREAM MCMC - │ - ▼ -reflectometry-lib MultiFitter - │ ._prepare_fit_arrays() ← reused from fit() - │ delegates to ↓ - ▼ -easyscience Bumps minimizer - │ .fit(x, y, weights) ──► amoeba/newton/lm - │ .sample(x, y, weights, ...) ──► DREAM MCMC (NEW) - │ - ▼ -bumps.fitters.fit / FitProblem / Curve - └──► reflectivity model evaluation via fit_func - -Post-hoc analysis: - reflectometry-lib analysis.bayesian - ├── PosteriorResults (container) - ├── plot_corner() → corner.corner() - ├── posterior_summary() → numpy stats - └── posterior_predictive_reflectivity() → model.interface.fit_func() -``` - -## Risk Assessment - -| Risk | Mitigation | -| -------------------------------------------- | -------------------------------------------------------------------------------- | -| DREAM may not converge with default settings | Expose `samples`, `burn`, `thin`, `chains`/`population`; document best practices | -| MCMC is 10-100× slower than least-squares | Document that classical fit first is recommended; add progress callback | -| `corner` / `arviz` may not be installed | Make optional dependencies with graceful fallbacks | -| Multi-dataset sampling aggregation | Follow existing `MultiFitter._precompute_reshaping` pattern | -| BUMPS DREAM API changes | Pin bumps version; wrap in our API | -| Reproducibility | Expose `seed` parameter; document how to save/load DreamState | -| Model mutation during posterior prediction | Save/restore original parameter values and map draws by `unique_name` | - -## Files to Create / Modify - -### Create: - -1. `reflectometry-lib/src/easyreflectometry/analysis/__init__.py` -2. `reflectometry-lib/src/easyreflectometry/analysis/bayesian.py` -3. `reflectometry-lib/tests/test_bayesian.py` - -### Modify: - -4. `core/src/easyscience/fitting/available_minimizers.py` — no - `Bumps_dream`; optionally document that samplers are exposed - separately -5. `core/src/easyscience/fitting/minimizers/minimizer_bumps.py` — add - dedicated `sample()` method without adding `'dream'` to optimizer - methods -6. `reflectometry-lib/src/easyreflectometry/fitting.py` — add `sample()` - to `MultiFitter` -7. `reflectometry-lib/src/easyreflectometry/__init__.py` — optional: - export new classes -8. `reflectometry-lib/pyproject.toml` — add optional `bayesian` - dependencies -9. `reflectometry-lib/docs/src/tutorials/advancedfitting/bayesian_bumps.py` - — update to use new API - -## Implementation Order - -1. **core changes** (Steps 1-2): Add `Bumps.sample()` method while - keeping DREAM out of optimizer enum/method dispatch -2. **reflectometry-lib fitting** (Step 3): Add `MultiFitter.sample()` -3. **reflectometry-lib analysis** (Step 4): Create - `analysis/bayesian.py` with corner plot & stats -4. **Dependencies** (Step 5): Add optional deps to pyproject.toml -5. **Exports** (Step 6): Update `__init__.py` -6. **Example update** (Step 7): Update notebook to use new API -7. **Tests** (Step 8): Add test coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d9e5c6b..ffd25625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,57 @@ # Unreleased +## Parameter constraints + +- New equality-constraint helpers `constrain`, `constrain_equal`, + `unconstrain`, `constrain_to_sum` and `derived_parameter` + (`easyreflectometry.constraints`), thin wrappers over the EasyScience + parameter-dependency mechanism. Constraints created through these + helpers survive project save/load; raw `make_dependent_on` calls do + not. A standalone `derived_parameter` is session-only: it has no + structural path and a saved project cannot reference it. +- New inequality constraints + (`easyreflectometry.inequality_constraints`): declarative + `InequalitySpec` objects (`t_head < t_tail`, `t1 + t2 <= 90`) + registered on the project (`Project.add_inequality_constraint` and + friends) and enforced as penalties on the BUMPS fit problem, including + via `MultiFitter.for_experiments`-built fitters driven through the raw + `easy_science_multi_fitter.fit(...)`. Engines that cannot enforce them + (LMFit, DFO-LS) raise instead of silently dropping physics. Older + project files without the new keys load unchanged; files saved with + constraints keep the file format at 2 (old readers ignore the additive + keys and lose the constraints). +- New `clamp_sum_partners` / `restore_sum_partners`: a + `constrain_to_sum` remainder can be driven negative by a fit that + pushes the partners past the total (a layer of negative thickness). + `clamp_sum_partners` caps each partner's `max` at the headroom it + leaves, sharing the slack in proportion to the current values; + `restore_sum_partners` hands the original maxima back and is + idempotent. The stashed maxima are persisted by structural path, so + the round trip survives project save/load. New `is_constrained_to_sum` + reports whether a parameter carries a `constrain_to_sum` dependency, + including after a reload. +- New `easyreflectometry.UnitError`, raised by `check_units` for a unit + problem in an inequality constraint. It subclasses `ValueError`, so + existing `except ValueError` handlers keep working, but callers no + longer have to match message substrings. +- New `Model.total_thickness`: a read-only derived parameter equal to + the summed thickness of the layers between superphase and subphase, + rebuilt whenever the layer structure changes. New + `conformal_thickness` / `conformal_roughness` toggles on assemblies, + also accepted as `Multilayer` / `RepeatingMultilayer` constructor + arguments and serialized from the current graph state, so the ties are + rebuilt on `from_dict`. +- Structural parameter paths (`Project.parameter_path` / + `Project.resolve_parameter_path`) address parameters stably across + save/load. +- `Parameter.bounds = (lo, hi)` assignments in tutorials, notebooks and + integration tests migrated to `.min` / `.max`. +- ORSO model loading now uses the parsed `SampleModel` as-is, so named + materials, sub-stacks and composits are no longer dropped (named + materials previously read back with SLD 0). + +## Polarization + All four polarization channels (pp, pm, mp, mm) are now available from the refl1d calculator. Previously only the non-spin-flip pp channel was returned. @@ -171,6 +223,18 @@ returned. disable/re-enable cycle and are re-attached when magnetism is enabled again. `update_layer` also accepts the magnetism keys one at a time. +## Documentation + +- The documentation is now MkDocs (Material) only. The legacy Sphinx + tree (`docs/src`, `docs/Makefile`, `docs/make.bat`) and the + tag-triggered `documentation-build.yml` workflow have been removed; + the site is built and deployed by `docs.yml` from `docs/mkdocs.yml`. +- New tutorials wired into the navigation: _Constraints & Inequalities_ + and _Bayesian Fitting_. +- New API reference pages for constraints, inequality constraints, + Bayesian analysis, calculators, parameter limits, `LayerMagnetism`, + ORSO, summary and plotting. + # Version 1.7.0 (1 Aug 2026) Restored the measured per-point resolution on data load (issue #368). diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index b7913396..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -msphinx -SPHINXPROJ = EasyReflectometry -SOURCEDIR = ./src -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/docs/api-reference/bayesian.md b/docs/docs/api-reference/bayesian.md new file mode 100644 index 00000000..fd5d454d --- /dev/null +++ b/docs/docs/api-reference/bayesian.md @@ -0,0 +1 @@ +::: easyreflectometry.analysis.bayesian diff --git a/docs/docs/api-reference/constraints.md b/docs/docs/api-reference/constraints.md new file mode 100644 index 00000000..c4ed81dc --- /dev/null +++ b/docs/docs/api-reference/constraints.md @@ -0,0 +1,85 @@ +# Constraints + +EasyReflectometry offers three kinds of constraints between model +parameters: equality constraints, derived read-only parameters and +[inequality constraints](inequality_constraints.md). The +[Constraints tutorial](../tutorials/advancedfitting/constraints.ipynb) +walks through all three on a worked example. + +## Equality constraints (dependencies) + +A parameter can be tied to an arbitrary expression of other parameters. +It then leaves the set of free fit parameters and follows the +expression. + +```python +from easyreflectometry import constrain +from easyreflectometry import constrain_equal +from easyreflectometry import unconstrain + +constrain_equal(layer_b.roughness, to=layer_a.roughness) +constrain(layer_b.thickness, '2 * t', t=layer_a.thickness) +unconstrain(layer_b.thickness) +``` + +Constraints survive `Project` save/load: the expression and the +structural paths of the parameters it refers to are stored with the +project, and the graph is rebuilt when it is loaded. This covers the +helpers above; a dependency created by calling `make_dependent_on` +directly is not recorded. + +## Derived (read-only) parameters + +A _derived parameter_ is a dependent parameter that belongs to no layer: +a live calculation that can be shown or referenced from an equality +constraint. + +```python +from easyreflectometry import constrain_to_sum +from easyreflectometry import derived_parameter + +total = derived_parameter('total', 'a + b', a=layer_a.thickness, b=layer_b.thickness) +# keep the film thickness fixed at 120 Å while the split is fitted +constrain_to_sum(layer_b.thickness, [layer_a.thickness, layer_b.thickness], total=120.0) +``` + +!!! warning + + A standalone derived parameter is **session-only**: it has no + structural path, so it cannot be named in an inequality constraint, + and a project whose equality constraints depend on one cannot be + saved (`Project.as_dict` raises). Numeric totals (as above) are + fine — they are embedded by value. + +For a derived value that persists and can be used in inequalities, use +one owned by the model: every [`Model`](model.md) exposes +`total_thickness`, the summed thickness of the layers between the +superphase and the subphase, re-derived whenever the layer structure +changes. + +## Guarding a sum remainder + +The parameter tied by `constrain_to_sum` absorbs whatever the others +leave over, so on its own the constraint lets a fit push the partners +past the total and drive the remainder negative — a layer of negative +thickness. `clamp_sum_partners` narrows each partner's `max` to the +headroom it actually leaves, sharing the slack in proportion to the +current values, and `restore_sum_partners` hands the original maxima +back when the constraint is released. + +```python +from easyreflectometry import clamp_sum_partners +from easyreflectometry import is_constrained_to_sum +from easyreflectometry import restore_sum_partners + +clamp_sum_partners([layer_a.thickness], remainder=layer_b.thickness.value) +is_constrained_to_sum(layer_b.thickness) # True, also after a project reload + +unconstrain(layer_b.thickness) +restore_sum_partners([layer_a.thickness]) +``` + +The narrowed maxima are persisted by structural path, so removing the +constraint after a save/load still gives the original bounds back. + +::: easyreflectometry.constraints diff --git a/docs/docs/api-reference/elements/layer_magnetism.md b/docs/docs/api-reference/elements/layer_magnetism.md new file mode 100644 index 00000000..2b026b34 --- /dev/null +++ b/docs/docs/api-reference/elements/layer_magnetism.md @@ -0,0 +1 @@ +::: easyreflectometry.sample.elements.layers.layer_magnetism diff --git a/docs/docs/api-reference/index.md b/docs/docs/api-reference/index.md index 85b9e5c7..a9335d5b 100644 --- a/docs/docs/api-reference/index.md +++ b/docs/docs/api-reference/index.md @@ -32,6 +32,35 @@ Fitting helpers and objective functions. - [Fitting](fitting.md) +## Constraints + +Equality constraints and derived parameters tie parameters together and +remove the dependent one from the fit. Inequality constraints keep every +parameter free and are enforced as penalties on the BUMPS fit problem. + +- [Constraints](constraints.md) +- [Inequality Constraints](inequality_constraints.md) + +## Bayesian Analysis + +Posterior analysis of a DREAM sampling run: convergence diagnostics, +credible intervals and correlation plots. + +- [Bayesian Analysis](bayesian.md) + +## Calculators + +The backend engines (refl1d, refnx) that turn a model into a +reflectivity curve, including the polarized interface. + +- [Calculators](calculators.md) + +## Parameter Limits + +Default `min` / `max` windows applied to parameters that have none. + +- [Parameter Limits](limits.md) + ## Assemblies Assemblies are collections of layers that are used to represent a @@ -54,6 +83,7 @@ material with a thickness and a roughness. - [Layer](elements/layer.md) - [Layer Area Per Molecule](elements/layer_area_per_molecule.md) +- [Layer Magnetism](elements/layer_magnetism.md) ### Materials @@ -70,3 +100,11 @@ material with given physical properties. Collection of helper functions. - [Data](data.md) +- [ORSO](orso.md) + +## Reporting + +Summaries of a project and its fit results, and the plotting helpers. + +- [Summary](summary.md) +- [Plotting](plot.md) diff --git a/docs/docs/api-reference/inequality_constraints.md b/docs/docs/api-reference/inequality_constraints.md new file mode 100644 index 00000000..e55b9ff5 --- /dev/null +++ b/docs/docs/api-reference/inequality_constraints.md @@ -0,0 +1,36 @@ +# Inequality Constraints + +Cross-parameter inequalities such as `t_head < t_tail` or +`t1 + t2 <= total` are not dependencies: no parameter is removed from +the fit. They are declared as `InequalitySpec` objects on the project +and enforced by the **BUMPS** engines (the `Bumps*` minimizers and the +DREAM sampler) as penalties on the fit problem. LMFit and DFO-LS cannot +enforce them, and `fit` raises `ValueError` in that case rather than +silently dropping the physics. + +```python +from easyscience.fitting import AvailableMinimizers + +from easyreflectometry import InequalitySpec + +project.minimizer = AvailableMinimizers.Bumps +t_a = project.parameter_path(layer_a.thickness) # 'models/0/sample/1/layers/0/thickness' +t_b = project.parameter_path(layer_b.thickness) +project.add_inequality_constraint(InequalitySpec('a', '<', 'b', {'a': t_a}, {'b': t_b}, name='order')) +project.add_inequality_constraint(InequalitySpec('a + b', '<', '90', {'a': t_a, 'b': t_b}, {})) + +project.violated_inequality_constraints() # check the start point first +project.fitter.fit_single_data_set_1d(dataset) # penalties applied automatically +``` + +Parameters are referenced by _structural path_ (see +`Project.parameter_path`) so the constraints are saved with the project. +While a constraint is violated BUMPS skips the model evaluation and adds +a penalty growing with the violation, steering the optimizer back into +the feasible region; the `Bumps_lm` method spreads the penalty over the +residuals instead and enforces inequalities more weakly. + +Both sides of a spec are unit-checked when it is registered; a mismatch +raises `UnitError`, a subclass of `ValueError`. + +::: easyreflectometry.inequality_constraints diff --git a/docs/docs/api-reference/limits.md b/docs/docs/api-reference/limits.md new file mode 100644 index 00000000..d3aa532d --- /dev/null +++ b/docs/docs/api-reference/limits.md @@ -0,0 +1 @@ +::: easyreflectometry.limits diff --git a/docs/docs/api-reference/orso.md b/docs/docs/api-reference/orso.md new file mode 100644 index 00000000..c741fdbd --- /dev/null +++ b/docs/docs/api-reference/orso.md @@ -0,0 +1 @@ +::: easyreflectometry.orso_utils diff --git a/docs/docs/api-reference/plot.md b/docs/docs/api-reference/plot.md new file mode 100644 index 00000000..124238cc --- /dev/null +++ b/docs/docs/api-reference/plot.md @@ -0,0 +1 @@ +::: easyreflectometry.plot diff --git a/docs/docs/api-reference/summary.md b/docs/docs/api-reference/summary.md new file mode 100644 index 00000000..2a1be976 --- /dev/null +++ b/docs/docs/api-reference/summary.md @@ -0,0 +1 @@ +::: easyreflectometry.summary.summary diff --git a/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb b/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb index bf136533..b4c3ca72 100644 --- a/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb +++ b/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb @@ -133,21 +133,25 @@ "\n", "# ---- Make key parameters free with realistic bounds (essential for MCMC) -----\n", "film_layer.thickness.fixed = False\n", - "film_layer.thickness.bounds = (100, 400)\n", + "film_layer.thickness.min = 100\n", + "film_layer.thickness.max = 400\n", "\n", "film.sld.fixed = False\n", - "film.sld.bounds = (0.5, 4.0)\n", + "film.sld.min = 0.5\n", + "film.sld.max = 4.0\n", "\n", "model.scale.fixed = False\n", - "model.scale.bounds = (0.8, 1.2)\n", + "model.scale.min = 0.8\n", + "model.scale.max = 1.2\n", "\n", "model.background.fixed = False\n", - "model.background.bounds = (1e-7, 1e-5)\n", + "model.background.min = 1e-7\n", + "model.background.max = 1e-5\n", "\n", "print('Model created with the following free parameters:')\n", "for p in model.get_parameters():\n", " if not p.fixed:\n", - " print(f' {p.name}: value={p.value}, bounds={p.bounds}')" + " print(f' {p.name}: value={p.value}, min={p.min}, max={p.max}')" ] }, { diff --git a/docs/docs/tutorials/advancedfitting/constraints.ipynb b/docs/docs/tutorials/advancedfitting/constraints.ipynb new file mode 100644 index 00000000..2540e2d6 --- /dev/null +++ b/docs/docs/tutorials/advancedfitting/constraints.ipynb @@ -0,0 +1,696 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c8077e80", + "metadata": {}, + "source": [ + "# Constraints: equalities, derived parameters and inequalities\n", + "\n", + "Real samples rarely consist of fully independent parameters: layers grown in the same process share a roughness, a film has a known total thickness, or physics dictates an ordering such as $t_\\mathrm{head} < t_\\mathrm{tail}$.\n", + "`easyreflectometry` offers three kinds of constraints to express this, and they survive saving and reloading a project (the one exception — a standalone `derived_parameter` — is called out below):\n", + "\n", + "1. **Equality constraints** (dependencies) — a parameter follows an expression of other parameters and leaves the fit: `constrain`, `constrain_equal`, `unconstrain`.\n", + "2. **Derived read-only parameters** — live calculations such as `Model.total_thickness` or your own `derived_parameter`; they can be referenced from other constraints, and `constrain_to_sum` uses them to keep a total fixed while the split is fitted.\n", + "3. **Inequality constraints** — `t_A < t_B` or `t_A + t_B < 90` declared on the project and *enforced during fitting* as penalties on the BUMPS fit problem. Only the BUMPS minimizers (and the DREAM sampler) support them; LMFit and DFO-LS refuse with a clear error rather than silently ignoring physics.\n", + "\n", + "In this tutorial we build a simple two-layer film, visualize it, apply each kind of constraint, and run an inequality-constrained fit on simulated data whose *true* answer violates the constraint — so we can watch the fit land exactly on the boundary of the allowed region." + ] + }, + { + "cell_type": "markdown", + "id": "e0136841", + "metadata": {}, + "source": [ + "First configure matplotlib to place figures in the notebook and import the needed modules." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2121e31", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae11c47d", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from easyscience import global_object\n", + "from easyscience.fitting import AvailableMinimizers\n", + "\n", + "from easyreflectometry import InequalitySpec\n", + "from easyreflectometry import Project\n", + "from easyreflectometry import UnitError\n", + "from easyreflectometry import clamp_sum_partners\n", + "from easyreflectometry import constrain\n", + "from easyreflectometry import constrain_to_sum\n", + "from easyreflectometry import derived_parameter\n", + "from easyreflectometry import is_constrained_to_sum\n", + "from easyreflectometry import restore_sum_partners\n", + "from easyreflectometry import unconstrain\n", + "from easyreflectometry.data import DataSet1D\n", + "from easyreflectometry.sample import Layer\n", + "from easyreflectometry.sample import Material\n", + "from easyreflectometry.sample import Multilayer" + ] + }, + { + "cell_type": "markdown", + "id": "7821b513", + "metadata": {}, + "source": [ + "## Building the sample\n", + "\n", + "We start from a default project and replace its film with two layers on a silicon substrate:\n", + "\n", + "| | material | SLD (10⁻⁶ Å⁻²) | thickness (Å) |\n", + "|---|---|---|---|\n", + "| superphase | vacuum | 0.0 | ∞ |\n", + "| film A | MatA | 3.0 | 40 |\n", + "| film B | MatB | 5.0 | 60 |\n", + "| subphase | Si | 2.07 | ∞ |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f98cfbdd", + "metadata": {}, + "outputs": [], + "source": [ + "project = Project()\n", + "project.default_model()\n", + "model = project.models[0]\n", + "\n", + "film_a = Multilayer(Layer(Material(3.0, 0.0, 'MatA'), thickness=40.0, roughness=3.0, name='A'), name='Film A')\n", + "film_b = Multilayer(Layer(Material(5.0, 0.0, 'MatB'), thickness=60.0, roughness=3.0, name='B'), name='Film B')\n", + "substrate = model.sample[-1]\n", + "model.remove_assembly(len(model.sample) - 1) # drop the default D2O / Si pair ...\n", + "model.remove_assembly(len(model.sample) - 1)\n", + "model.add_assemblies(film_a, film_b, substrate) # ... and insert our film\n", + "\n", + "t_a = film_a.layers[0].thickness\n", + "t_b = film_b.layers[0].thickness\n", + "[layer.name for assembly in model.sample for layer in assembly.layers]" + ] + }, + { + "cell_type": "markdown", + "id": "04b200c1", + "metadata": {}, + "source": [ + "To make the discussion easier to follow we define a small helper that draws the sample twice: as a **layer stack** (the beam arrives from the top; hatched media are semi-infinite) and as the corresponding **SLD profile** computed by the calculator. We will call it after every change so the effect of each constraint is visible." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ba361b0", + "metadata": {}, + "outputs": [], + "source": [ + "def show_sample(model, title='Sample'):\n", + " \"\"\"Draw the layer stack (left) and the SLD profile (right).\"\"\"\n", + " layers = [layer for assembly in model.sample for layer in assembly.layers]\n", + " fig, (ax_stack, ax_sld) = plt.subplots(1, 2, figsize=(9.5, 4.2), width_ratios=[1.0, 1.6])\n", + "\n", + " slds = [layer.material.sld.value for layer in layers]\n", + " norm = plt.Normalize(min(slds) - 0.5, max(slds) + 0.5)\n", + " cmap = plt.get_cmap('viridis')\n", + " semi_infinite = 30.0 # display height of the semi-infinite media\n", + "\n", + " depth = 0.0\n", + " for index, layer in enumerate(layers):\n", + " is_medium = index in (0, len(layers) - 1)\n", + " thickness = layer.thickness.value\n", + " height = semi_infinite if is_medium or thickness <= 0 else thickness\n", + " ax_stack.add_patch(\n", + " plt.Rectangle(\n", + " (0.0, -depth - height),\n", + " 1.0,\n", + " height,\n", + " facecolor=cmap(norm(layer.material.sld.value)),\n", + " edgecolor='black',\n", + " hatch='//' if is_medium else None,\n", + " alpha=0.85,\n", + " )\n", + " )\n", + " label = layer.name if is_medium else f'{layer.name}: {thickness:.1f} Å'\n", + " ax_stack.text(1.08, -depth - height / 2, label, va='center', fontsize=10)\n", + " depth += height\n", + " ax_stack.annotate('beam', xy=(0.18, -10), xytext=(-0.35, 25), fontsize=9, arrowprops={'arrowstyle': '->'})\n", + " ax_stack.set_xlim(-0.5, 2.6)\n", + " ax_stack.set_ylim(-depth - 5, 30)\n", + " ax_stack.axis('off')\n", + " ax_stack.set_title(title)\n", + "\n", + " z, sld = model.interface().sld_profile(model.unique_name)\n", + " ax_sld.plot(z, sld, color='#00a3e3')\n", + " ax_sld.set_xlabel('z / Å')\n", + " ax_sld.set_ylabel('SLD / 10⁻⁶ Å⁻²')\n", + " ax_sld.set_title('SLD profile')\n", + " plt.tight_layout()\n", + " plt.show()\n", + "\n", + "\n", + "show_sample(model, 'vacuum / A / B / Si')" + ] + }, + { + "cell_type": "markdown", + "id": "eae7fddc", + "metadata": {}, + "source": [ + "## 1. Equality constraints\n", + "\n", + "An equality constraint ties a parameter to an expression of other parameters.\n", + "The constrained parameter becomes *dependent*: it is removed from the free fit parameters, follows the expression whenever any input changes, and cannot be set directly.\n", + "Here the roughness of film B is tied to 1.5× the roughness of film A:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "daace094", + "metadata": {}, + "outputs": [], + "source": [ + "r_a = film_a.layers[0].roughness\n", + "r_b = film_b.layers[0].roughness\n", + "\n", + "constrain(r_b, '1.5 * r', r=r_a)\n", + "print(f'r_A = {r_a.value:g} Å -> r_B = {r_b.value:g} Å (r_B independent: {r_b.independent})')\n", + "\n", + "r_a.value = 4.0\n", + "print(f'r_A = {r_a.value:g} Å -> r_B = {r_b.value:g} Å (follows automatically)')" + ] + }, + { + "cell_type": "markdown", + "id": "fcea4397", + "metadata": {}, + "source": [ + "`unconstrain` releases the parameter again; it keeps its last value and becomes fittable:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d1a2f4e", + "metadata": {}, + "outputs": [], + "source": [ + "unconstrain(r_b)\n", + "print(f'after unconstrain: r_B = {r_b.value:g} Å, independent = {r_b.independent}')" + ] + }, + { + "cell_type": "markdown", + "id": "f357034b", + "metadata": {}, + "source": [ + "## 2. Derived read-only parameters\n", + "\n", + "Every model owns `total_thickness`: a read-only parameter equal to the summed thickness of the layers between the superphase and the substrate.\n", + "It updates whenever a layer thickness — or the layer structure itself — changes, never enters a fit, and can be used inside other constraint expressions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3489f4a", + "metadata": {}, + "outputs": [], + "source": [ + "total = model.total_thickness\n", + "print(f'total film thickness = {total.value:g} Å')\n", + "\n", + "t_a.value = 45.0\n", + "print(f'after t_A -> 45 Å: {total.value:g} Å')\n", + "\n", + "try:\n", + " total.value = 1.0\n", + "except AttributeError as error:\n", + " print(f'setting it fails as expected: {error}')\n", + "\n", + "t_a.value = 40.0" + ] + }, + { + "cell_type": "markdown", + "id": "45367883", + "metadata": {}, + "source": [ + "`derived_parameter` builds your own live calculations from any parameters. It is **session-only**: it belongs to no model, so it has no structural path — it cannot be named in an inequality constraint, and a project whose equality constraints depend on one cannot be saved. Use a model-owned value such as `Model.total_thickness` when persistence matters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d05728e", + "metadata": {}, + "outputs": [], + "source": [ + "contrast = derived_parameter(\n", + " 'sld_contrast',\n", + " 'b - a',\n", + " a=film_a.layers[0].material.sld,\n", + " b=film_b.layers[0].material.sld,\n", + ")\n", + "print(f'derived SLD contrast (B - A) = {contrast.value:g} {contrast.unit}')" + ] + }, + { + "cell_type": "markdown", + "id": "a4b77f0a", + "metadata": {}, + "source": [ + "### Keeping a total fixed while fitting the split\n", + "\n", + "`constrain_to_sum` is the classic use of a derived quantity: keep $t_A + t_B$ fixed while the individual thicknesses vary.\n", + "The last parameter becomes dependent and absorbs whatever the others change by — watch the stack keep its total height as $t_A$ moves:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bff23c00", + "metadata": {}, + "outputs": [], + "source": [ + "constrain_to_sum(t_b, [t_a, t_b], total=100.0)\n", + "\n", + "t_a.value = 30.0\n", + "print(f't_A = {t_a.value:g}, t_B = {t_b.value:g}, sum = {t_a.value + t_b.value:g}')\n", + "show_sample(model, 't_A -> 30 Å: t_B absorbs the change (sum stays 100 Å)')\n", + "\n", + "t_a.value = 70.0\n", + "print(f't_A = {t_a.value:g}, t_B = {t_b.value:g}, sum = {t_a.value + t_b.value:g}')\n", + "show_sample(model, 't_A -> 70 Å: sum still 100 Å')" + ] + }, + { + "cell_type": "markdown", + "id": "b1c0d2e3", + "metadata": {}, + "source": [ + "### Keeping the remainder non-negative\n", + "\n", + "The tied parameter absorbs whatever the others leave over, and nothing in the constraint itself stops a fit from pushing the free partners past the total — which drives the remainder negative, i.e. a layer of negative thickness:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1c0d2e4", + "metadata": {}, + "outputs": [], + "source": [ + "t_a.value = 130.0 # a minimizer step past the 100 Å total\n", + "print(f'unguarded: t_A = {t_a.value:g} Å, t_B = {t_b.value:g} Å <- a negative layer')" + ] + }, + { + "cell_type": "markdown", + "id": "b1c0d2e5", + "metadata": {}, + "source": [ + "`clamp_sum_partners` closes that door: it narrows each partner's upper bound to the headroom it actually has, sharing the slack in proportion to the current values.\n", + "`restore_sum_partners` hands the original bounds back when the constraint is released, and `is_constrained_to_sum` reports whether a parameter carries such a constraint — also for one restored from a project file.\n", + "The stashed bounds are written to the project file too, so the guard survives a save/load cycle.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1c0d2e6", + "metadata": {}, + "outputs": [], + "source": [ + "print(f'is_constrained_to_sum(t_B) = {is_constrained_to_sum(t_b, [t_a, t_b])}')\n", + "\n", + "t_a.value = 70.0 # back to a sensible split\n", + "clamp_sum_partners([t_a], remainder=float(t_b.value))\n", + "print(f'clamped : t_A max = {t_a.max:g} Å (its share of the budget)')\n", + "\n", + "t_a.value = 130.0 # the same step, now refused by the bound\n", + "print(f'guarded : t_A = {t_a.value:g} Å, t_B = {t_b.value:g} Å <- stops at zero instead')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32af147e", + "metadata": {}, + "outputs": [], + "source": [ + "# release it again and restore the starting structure\n", + "unconstrain(t_b)\n", + "restore_sum_partners([t_a])\n", + "t_a.value = 40.0\n", + "t_b.value = 60.0" + ] + }, + { + "cell_type": "markdown", + "id": "0afca80c", + "metadata": {}, + "source": [ + "## 3. Inequality constraints\n", + "\n", + "Inequalities are different from the dependencies above: **no parameter leaves the fit**.\n", + "Instead the constraint is declared on the *project* and translated, at the start of every fit, into a penalty on the BUMPS fit problem.\n", + "While the constraint is violated BUMPS skips the model evaluation and adds a penalty that grows with the violation, steering the optimizer back into the allowed region.\n", + "\n", + "Constraints reference parameters by **structural path** (stable across save/load, unlike unique names):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe203b23", + "metadata": {}, + "outputs": [], + "source": [ + "path_a = project.parameter_path(t_a)\n", + "path_b = project.parameter_path(t_b)\n", + "print(path_a)\n", + "print(path_b)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b615bd3c", + "metadata": {}, + "outputs": [], + "source": [ + "project.add_inequality_constraint(\n", + " InequalitySpec('a', '<', 'b', lhs_paths={'a': path_a}, rhs_paths={'b': path_b}, name='A below B')\n", + ")\n", + "project.add_inequality_constraint(\n", + " InequalitySpec('a + b', '<', '90', lhs_paths={'a': path_a, 'b': path_b}, rhs_paths={}, name='budget')\n", + ")\n", + "\n", + "for spec, evaluation in zip(project.inequality_constraints, project.evaluate_inequality_constraints()):\n", + " status = 'satisfied' if evaluation.satisfied else 'VIOLATED'\n", + " print(f'{spec.name:10s} {spec!s:12s} lhs={evaluation.lhs:g} rhs={evaluation.rhs:g} {status}')" + ] + }, + { + "cell_type": "markdown", + "id": "1b2e5cc4", + "metadata": {}, + "source": [ + "Expressions are unit-checked when a constraint is registered — comparing a thickness with an SLD is refused with a `UnitError` (a `ValueError`, so older `except ValueError` handlers keep working):\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7697b28d", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " project.add_inequality_constraint(\n", + " InequalitySpec(\n", + " 'a',\n", + " '<',\n", + " 's',\n", + " lhs_paths={'a': path_a},\n", + " rhs_paths={'s': project.parameter_path(film_a.layers[0].material.sld)},\n", + " )\n", + " )\n", + "except UnitError as error:\n", + " print(error)" + ] + }, + { + "cell_type": "markdown", + "id": "6151592e", + "metadata": {}, + "source": [ + "A fit started from a point that already violates a constraint would begin on the penalty plateau, where only the penalty slope guides the optimizer — so check the start point first.\n", + "Our current values ($40 + 60 = 100$) violate the 90 Å budget; we move to a feasible start:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22ab8f72", + "metadata": {}, + "outputs": [], + "source": [ + "print('violated now:', [spec.name for spec in project.violated_inequality_constraints()])\n", + "\n", + "t_a.value, t_b.value = 30.0, 50.0 # 30 < 50 and 30 + 50 = 80 < 90\n", + "print('after moving :', [spec.name for spec in project.violated_inequality_constraints()] or 'none')" + ] + }, + { + "cell_type": "markdown", + "id": "3e6e8543", + "metadata": {}, + "source": [ + "### Simulated data whose true answer violates the budget\n", + "\n", + "We simulate a measurement from $t_A = 45$ Å, $t_B = 55$ Å — a film whose total (100 Å) breaks the 90 Å budget on purpose — and add 5 % noise.\n", + "This is the interesting case: the *unconstrained* optimum lies outside the allowed region, so the constrained fit must settle on the boundary." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5aa5e28a", + "metadata": {}, + "outputs": [], + "source": [ + "q = np.linspace(0.01, 0.3, 150)\n", + "\n", + "t_a.value, t_b.value = 45.0, 55.0 # the \"truth\"\n", + "r_true = model.interface.fit_func(q, model.unique_name)\n", + "rng = np.random.default_rng(42)\n", + "r_measured = r_true * rng.normal(1.0, 0.05, size=q.size)\n", + "t_a.value, t_b.value = 30.0, 50.0 # back to the feasible start\n", + "\n", + "dataset = DataSet1D(name='simulated', x=q, y=r_measured, ye=(0.05 * r_true) ** 2)\n", + "\n", + "plt.figure(figsize=(7, 4))\n", + "plt.errorbar(q, r_measured, yerr=0.05 * r_true, fmt='.', ms=4, alpha=0.6, label='simulated data (truth: 45 + 55 Å)')\n", + "plt.plot(q, model.interface.fit_func(q, model.unique_name), color='#00a3e3', label='model at the start point (30 + 50 Å)')\n", + "plt.yscale('log')\n", + "plt.xlabel('q / Å⁻¹')\n", + "plt.ylabel('R(q)')\n", + "plt.legend()\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "006daf09", + "metadata": {}, + "source": [ + "Only the two thicknesses are fitted; everything else stays fixed:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "889379a6", + "metadata": {}, + "outputs": [], + "source": [ + "for assembly in model.sample:\n", + " for layer in assembly.layers:\n", + " for parameter in (layer.thickness, layer.roughness, layer.material.sld, layer.material.isld):\n", + " parameter.fixed = True\n", + "t_a.fixed = False\n", + "t_b.fixed = False\n", + "model.scale.fixed = True\n", + "model.background.fixed = True" + ] + }, + { + "cell_type": "markdown", + "id": "9d2f54ee", + "metadata": {}, + "source": [ + "### Engines that cannot enforce inequalities are screened out\n", + "\n", + "Inequality penalties live in the BUMPS fit problem, so they only work with the BUMPS minimizers (and DREAM sampling).\n", + "Selecting LMFit with active inequalities raises immediately — physics constraints are never silently dropped:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae411300", + "metadata": {}, + "outputs": [], + "source": [ + "project.minimizer = AvailableMinimizers.LMFit\n", + "try:\n", + " project.fitter.fit_single_data_set_1d(dataset)\n", + "except ValueError as error:\n", + " print(error)" + ] + }, + { + "cell_type": "markdown", + "id": "871df28a", + "metadata": {}, + "source": [ + "### The constrained fit\n", + "\n", + "With a BUMPS minimizer the project's inequality constraints are picked up automatically:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4f076db", + "metadata": {}, + "outputs": [], + "source": [ + "project.minimizer = AvailableMinimizers.Bumps\n", + "result = project.fitter.fit_single_data_set_1d(dataset)\n", + "\n", + "print(f'success = {result.success}')\n", + "print('truth (outside the budget): t_A = 45.00, t_B = 55.00, sum = 100.00')\n", + "print(f'constrained fit: t_A = {t_a.value:.2f}, t_B = {t_b.value:.2f}, sum = {t_a.value + t_b.value:.2f}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27589108", + "metadata": {}, + "outputs": [], + "source": [ + "fig, (ax_fit, ax_bars) = plt.subplots(1, 2, figsize=(10, 4), width_ratios=[1.6, 1.0])\n", + "\n", + "ax_fit.errorbar(q, r_measured, yerr=0.05 * r_true, fmt='.', ms=4, alpha=0.5, label='simulated data')\n", + "ax_fit.plot(q, model.interface.fit_func(q, model.unique_name), color='#00a3e3', lw=2, label='constrained fit')\n", + "ax_fit.set_yscale('log')\n", + "ax_fit.set_xlabel('q / Å⁻¹')\n", + "ax_fit.set_ylabel('R(q)')\n", + "ax_fit.legend()\n", + "\n", + "bars = {\n", + " 'truth\\n(violates budget)': (45.0, 55.0),\n", + " 'constrained fit': (t_a.value, t_b.value),\n", + "}\n", + "for position, (label, (a_value, b_value)) in enumerate(bars.items()):\n", + " ax_bars.barh(position, a_value, color='#4ec1ef', edgecolor='black', label='t_A' if position == 0 else None)\n", + " ax_bars.barh(position, b_value, left=a_value, color='#f5a623', edgecolor='black', label='t_B' if position == 0 else None)\n", + " ax_bars.text(a_value + b_value + 1.5, position, f'{a_value + b_value:.1f} Å', va='center')\n", + "ax_bars.axvline(90.0, color='crimson', ls='--', lw=1.5)\n", + "ax_bars.text(90.0, 1.55, ' budget: 90 Å', color='crimson', fontsize=9)\n", + "ax_bars.set_yticks(range(len(bars)), bars.keys())\n", + "ax_bars.set_xlabel('thickness / Å')\n", + "ax_bars.set_xlim(0, 120)\n", + "ax_bars.legend(loc='lower right')\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "show_sample(model, 'after the constrained fit')" + ] + }, + { + "cell_type": "markdown", + "id": "8f438b29", + "metadata": {}, + "source": [ + "The fit lands **exactly on the boundary** $t_A + t_B = 90$ Å — the closest allowed film to the data — with the ordering $t_A < t_B$ respected as well." + ] + }, + { + "cell_type": "markdown", + "id": "8690e53a", + "metadata": {}, + "source": [ + "## 4. Save / load\n", + "\n", + "Equality constraints, model-owned derived parameters (such as `total_thickness`) and inequality specs are all part of the project file. A standalone `derived_parameter` is not saved, and a constraint depending on one cannot be either.\n", + "We serialize the project to a plain dictionary (this is exactly what `save_as_json` writes), wipe the session, and rebuild:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91222868", + "metadata": {}, + "outputs": [], + "source": [ + "constrain(r_b, '1.5 * r', r=r_a) # add the equality constraint again\n", + "\n", + "project_dict = json.loads(json.dumps(project.as_dict()))\n", + "\n", + "global_object.map._clear() # simulate a fresh Python session\n", + "reloaded = Project()\n", + "reloaded.from_dict(project_dict)\n", + "reloaded_model = reloaded.models[0]\n", + "\n", + "print('inequalities :', [str(spec) for spec in reloaded.inequality_constraints])\n", + "\n", + "reloaded_r_a = reloaded_model.sample[1].layers[0].roughness\n", + "reloaded_r_b = reloaded_model.sample[2].layers[0].roughness\n", + "reloaded_r_a.value = 6.0\n", + "print(f'equality : r_A = {reloaded_r_a.value:g} -> r_B = {reloaded_r_b.value:g} (still 1.5x)')\n", + "print(f'derived : total_thickness = {reloaded_model.total_thickness.value:g} Å')" + ] + }, + { + "cell_type": "markdown", + "id": "e9ace3c1", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "| constraint | API | enters the fit? | engines |\n", + "|---|---|---|---|\n", + "| equality (`r_B = 1.5 r_A`) | `constrain`, `constrain_equal`, `unconstrain` | the follower leaves the fit | all |\n", + "| derived value (`total_thickness`) | `Model.total_thickness`, `derived_parameter`, `constrain_to_sum` (guard the remainder with `clamp_sum_partners`) | never | all |\n", + "| inequality (`t_A + t_B < 90`) | `InequalitySpec` + `Project.add_inequality_constraint` | both parameters stay free; a penalty steers the fit | BUMPS minimizers & DREAM only |\n", + "\n", + "Before fitting a `constrain_to_sum` split, guard the remainder with `clamp_sum_partners` so the partners cannot overrun the total, and give the bounds back with `restore_sum_partners` once the constraint is released.\n", + "\n", + "Useful checks before fitting with inequalities: `project.violated_inequality_constraints()` (feasible start point) and remember that `Bumps_lm` enforces them only weakly (the penalty is spread over the residuals) — prefer `Bumps` (amoeba) or `Bumps_newton`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb b/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb index 1c482386..3340315b 100644 --- a/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb +++ b/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb @@ -72,7 +72,14 @@ "id": "694b4e5e-2d1a-402e-aa3f-a26cc82f7774", "metadata": {}, "outputs": [], - "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/multiple.ort',\n known_hash='241bcb819cdae47fbbb310a99c2456c7332312719496b936a153dc7dee83e62c',\n)\ndata = load(file_path)" + "source": [ + "file_path = pooch.retrieve(\n", + " # Fetch test data from the easyscience/reflectometry data repository\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/multiple.ort',\n", + " known_hash='241bcb819cdae47fbbb310a99c2456c7332312719496b936a153dc7dee83e62c',\n", + ")\n", + "data = load(file_path)" + ] }, { "cell_type": "markdown", @@ -383,11 +390,52 @@ "cell_type": "markdown", "id": "01abb1a2-1c77-4c59-b8ae-58fc29c00857", "metadata": {}, + "source": "Even through only as single value (that for the d13-DSPC head thickness) was changed, all three values changed. " + }, + { + "cell_type": "markdown", + "id": "d375c89c", + "source": "### Custom constraints\n\nThe assembly methods used above cover the common chemical relationships, but arbitrary constraints between any two parameters are also possible with the `constrain`, `constrain_equal` and `unconstrain` helpers (see *Constraining Parameters* in the [model tutorial](../basic/model.md)). As a demonstration, we tie the head thickness of the base contrast to always be half the tail thickness. Since the other contrasts are chained to `d13d2o`, the constraint propagates to all of them. ", + "metadata": {} + }, + { + "cell_type": "code", + "id": "041f4e4c", "source": [ - "Even through only as single value (that for the d13-DSPC head thickness) was changed, all three values changed. \n", + "from easyreflectometry import constrain # noqa: E402\n", + "from easyreflectometry import unconstrain # noqa: E402\n", "\n", - "Having constructed each of the surfactant layer object and implemented the constraints, we can now build Samples and models. " - ] + "constrain(d13d2o.head_layer.thickness, '0.5 * t', t=d13d2o.tail_layer.thickness)\n", + "d13d2o.tail_layer.thickness.value = 22\n", + "d13d2o.head_layer.thickness.value, d70d2o.head_layer.thickness.value, d83acmw.head_layer.thickness.value" + ], + "metadata": {}, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "d62ee7bd", + "source": "Changing the tail thickness now also updates every head thickness. Note that constraining overwrites the dependent parameter's value and bounds, and `unconstrain` does not restore them. We will not use this constraint in the fit below, so we remove it again and restore the original values before building the models. ", + "metadata": {} + }, + { + "cell_type": "code", + "id": "1465c350", + "source": [ + "unconstrain(d13d2o.head_layer.thickness)\n", + "d13d2o.tail_layer.thickness.value = 20\n", + "d13d2o.head_layer.thickness.value = 10" + ], + "metadata": {}, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "b7e83a2d", + "source": "Having constructed each of the surfactant layer object and implemented the constraints, we can now build Samples and models. ", + "metadata": {} }, { "cell_type": "code", @@ -440,17 +488,27 @@ "metadata": {}, "outputs": [], "source": [ - "d13d2o_model.scale.bounds = (0.05, 1.5)\n", - "d13d2o_model.background.bounds = (4e-8, 1e-5)\n", - "d70d2o_model.scale.bounds = (0.05, 1.5)\n", - "d70d2o_model.background.bounds = (4e-8, 1e-5)\n", - "d83acmw_model.scale.bounds = (0.05, 1.5)\n", - "d83acmw_model.background.bounds = (4e-8, 1e-5)\n", + "d13d2o_model.scale.min = 0.05\n", + "d13d2o_model.scale.max = 1.5\n", + "d13d2o_model.background.min = 4e-8\n", + "d13d2o_model.background.max = 1e-5\n", + "d70d2o_model.scale.min = 0.05\n", + "d70d2o_model.scale.max = 1.5\n", + "d70d2o_model.background.min = 4e-8\n", + "d70d2o_model.background.max = 1e-5\n", + "d83acmw_model.scale.min = 0.05\n", + "d83acmw_model.scale.max = 1.5\n", + "d83acmw_model.background.min = 4e-8\n", + "d83acmw_model.background.max = 1e-5\n", "\n", - "d13d2o.tail_layer.area_per_molecule_parameter.bounds = (40, 50)\n", - "d13d2o.head_layer.solvent_fraction_parameter.bounds = (0.2, 0.6)\n", - "d13d2o.tail_layer.thickness.bounds = (18, 24)\n", - "d13d2o.head_layer.thickness.bounds = (8, 12)" + "d13d2o.tail_layer.area_per_molecule_parameter.min = 40\n", + "d13d2o.tail_layer.area_per_molecule_parameter.max = 50\n", + "d13d2o.head_layer.solvent_fraction_parameter.min = 0.2\n", + "d13d2o.head_layer.solvent_fraction_parameter.max = 0.6\n", + "d13d2o.tail_layer.thickness.min = 18\n", + "d13d2o.tail_layer.thickness.max = 24\n", + "d13d2o.head_layer.thickness.min = 8\n", + "d13d2o.head_layer.thickness.max = 12" ] }, { diff --git a/docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb b/docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb index cb820cbf..caae5b09 100644 --- a/docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb +++ b/docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb @@ -378,7 +378,8 @@ "fit_model_2ch, fit_film_2ch = build_magnetic_model(rho_m=1.0, theta_m=THETA_M_TRUE, name='Fit: two channels (rho_m only)')\n", "\n", "fit_film_2ch.magnetism.rho_m.fixed = False\n", - "fit_film_2ch.magnetism.rho_m.bounds = (0.0, 5.0)\n", + "fit_film_2ch.magnetism.rho_m.min = 0.0\n", + "fit_film_2ch.magnetism.rho_m.max = 5.0\n", "fit_film_2ch.magnetism.theta_m.fixed = True # moment direction assumed known\n", "\n", "initial_channels_2ch = fit_model_2ch.interface.polarized_reflectivity_profiles(Q_PLOT, fit_model_2ch.unique_name)\n", @@ -469,9 +470,11 @@ "fit_model_4ch, fit_film_4ch = build_magnetic_model(rho_m=1.5, theta_m=60.0, name='Fit: four channels (rho_m and theta_m)')\n", "\n", "fit_film_4ch.magnetism.rho_m.fixed = False\n", - "fit_film_4ch.magnetism.rho_m.bounds = (0.0, 5.0)\n", + "fit_film_4ch.magnetism.rho_m.min = 0.0\n", + "fit_film_4ch.magnetism.rho_m.max = 5.0\n", "fit_film_4ch.magnetism.theta_m.fixed = False\n", - "fit_film_4ch.magnetism.theta_m.bounds = (0.0, 90.0)\n", + "fit_film_4ch.magnetism.theta_m.min = 0.0\n", + "fit_film_4ch.magnetism.theta_m.max = 90.0\n", "\n", "fit_data_4ch = PolarizedDataSet(\n", " name='Fe film (pp, pm, mp, mm)',\n", diff --git a/docs/docs/tutorials/basic/assemblies_library.md b/docs/docs/tutorials/basic/assemblies_library.md index da419f56..d384d13a 100644 --- a/docs/docs/tutorials/basic/assemblies_library.md +++ b/docs/docs/tutorials/basic/assemblies_library.md @@ -8,7 +8,9 @@ analysis by making chemical and physical constraints available with limited code. In this page, we will document the assemblies that are available with simple examples of the constructors that exist. Full API documentation is also available for the -`easyreflectometry.sample.assemblies` module. +`easyreflectometry.sample.assemblies` module. For custom constraints +between arbitrary parameters, see _Constraining Parameters_ in the +[Model tutorial](model.md). ## Multilayer @@ -38,6 +40,39 @@ subphase = Multilayer(layers=[si_layer, sio2_layer], name='Si/SiO2 subphase') This will create a `Multilayer` object named `subphase` which we can use in some `Structure` for our analysis. +### Conformal thickness and roughness + +Layers grown in a single process step often share a thickness or a +roughness. A `Multilayer` can tie every one of its layers to the front +layer, which removes the followers from the fit and leaves a single free +parameter per tied quantity. + +```python +subphase = Multilayer( + layers=[si_layer, sio2_layer], + name='Si/SiO2 subphase', + conformal_roughness=True, + conformal_thickness=True, +) +``` + +Both toggles are also plain properties, so they can be switched on and +off after construction. + +```python +subphase.conformal_roughness = True +subphase.conformal_roughness = False # releases the followers again +``` + +The ties are ordinary parameter dependencies, and the assembly stores +the state of both toggles when it is serialized, so they are rebuilt +when a project is loaded. Releasing a tie leaves the follower at its +last value; as with any constraint, its bounds are whatever the +constraint left behind, so review them before fitting. Constraints +between arbitrary parameters — including a fixed total thickness — are +covered in _Constraining Parameters_ in the [Model tutorial](model.md) +and in the [Constraints tutorial](../advancedfitting/constraints.ipynb). + ## RepeatingMultilayer The `RepeatingMultilayer` assembly type is an extension of the @@ -69,3 +104,16 @@ ni_ti = RepeatingMultilayer(layers=[ti_layer, ni_layer], repetitions=10, name='N The number of repeats is a parameter that can be varied in the optimisation process, however given this is a value that depends on the synthesis of the sample this is unlikely to be necessary. + +A `RepeatingMultilayer` is a `Multilayer`, so it accepts the +`conformal_thickness` and `conformal_roughness` arguments described +above, and persists them in the same way. + +```python +ni_ti = RepeatingMultilayer( + layers=[ti_layer, ni_layer], + repetitions=10, + name='Ni/Ti Multilayer', + conformal_roughness=True, +) +``` diff --git a/docs/docs/tutorials/basic/model.md b/docs/docs/tutorials/basic/model.md index 6b9c76c3..72acc8a0 100644 --- a/docs/docs/tutorials/basic/model.md +++ b/docs/docs/tutorials/basic/model.md @@ -85,3 +85,96 @@ This will create a `Model` instance where the resolution function defining the FWHM is determined from a linear interpolation. In the present case the provided data Q-points are (`[0.01, 0.2, 0.31]`) and the corresponding FWHM function values are (`[0.001, 0.043, 0.026]`). + +## Constraining Parameters + +It is often physically motivated to reduce the number of free parameters +in a model by tying parameters together. For example, two layers that +were deposited in the same process step can be expected to have the same +thickness, or the roughness of every interface in a stack can be assumed +to be conformal. Assemblies such as `SurfactantLayer` and `Bilayer` +provide ready-made constraints for their specific chemistry +(`constrain_area_per_molecule`, `conformal_roughness`, +`constrain_multiple_contrast`), but any parameter in a model can be +constrained directly. + +### Tying two parameters together + +The most common constraint is a simple equality. Here the thickness of +`layer_2` is tied to the thickness of `layer_1`. + +```python +from easyreflectometry import constrain_equal + +constrain_equal(layer_2.thickness, to=layer_1.thickness) +``` + +After this call `layer_2.thickness` is no longer an independent +parameter: it immediately takes the value of `layer_1.thickness`, +follows it whenever it changes (including during fitting), and is +removed from the free fit parameters. Only `layer_1.thickness` is varied +by the minimizer. + +### Functional constraints + +Constraints are not limited to equality. An arbitrary mathematical +expression of one or more parameters can be used, where each placeholder +in the expression is supplied as a keyword argument. + +```python +from easyreflectometry import constrain + +# layer_2 is always twice as thick as layer_1 +constrain(layer_2.thickness, '2 * t', t=layer_1.thickness) + +# an SLD that is a fraction-weighted average of two materials +constrain( + mixed.sld, + 'frac * a + (1 - frac) * b', + frac=fraction, + a=solvent.sld, + b=film.sld, +) +``` + +Note that constraining a parameter **overwrites its current value, unit +and bounds** with the evaluated expression, and clears its `fixed` flag. +While constrained, the parameter's value and bounds cannot be set +directly. + +### Removing a constraint + +```python +from easyreflectometry import unconstrain + +unconstrain(layer_2.thickness) +``` + +The parameter keeps its last evaluated value and becomes an independent, +fittable parameter again. Calling `unconstrain` on a parameter that is +not constrained does nothing. The parameter's original bounds and +`fixed` state are **not** restored. They remain whatever the constraint +left behind, so review and reset the bounds before fitting. + +### Things to be aware of + +- Constraints are directional: the dependent parameter follows the + independent one, never the other way around. When chaining constraints + across several objects (as in the multiple-contrast tutorials), make + sure the chain has a single independent parameter at its root. +- Placeholder names in expressions must be valid Python identifiers and + not Python keywords. An unmapped name that happens to match a + mathematical builtin (`e`, `pi`, `sin`, ...) evaluates silently + instead of raising an error. +- If the model is already attached to a calculator, regenerate the + bindings after changing constraints so the calculator picks up the new + dependency graph: + + ```python + model.generate_bindings() + ``` + +- Constraints applied with `constrain`, `constrain_equal` and + `constrain_to_sum` are preserved when a project is saved and reloaded. + Dependencies created by calling `make_dependent_on` directly are not, + and must be re-applied after loading. diff --git a/docs/docs/tutorials/fitting/material_solvated.ipynb b/docs/docs/tutorials/fitting/material_solvated.ipynb index e64f7f7a..64c62637 100644 --- a/docs/docs/tutorials/fitting/material_solvated.ipynb +++ b/docs/docs/tutorials/fitting/material_solvated.ipynb @@ -96,7 +96,14 @@ "id": "a95a39dd-d0eb-4029-9dc8-41e6e7918f66", "metadata": {}, "outputs": [], - "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n)\ndata = load(file_path)" + "source": [ + "file_path = pooch.retrieve(\n", + " # Fetch test data from the easyscience/reflectometry data repository\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n", + " known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n", + ")\n", + "data = load(file_path)" + ] }, { "cell_type": "markdown", @@ -262,18 +269,26 @@ "outputs": [], "source": [ "# Thicknesses\n", - "sio2_layer.thickness.bounds = (15, 50)\n", - "solvated_film.thickness.bounds = (200, 300)\n", + "sio2_layer.thickness.min = 15\n", + "sio2_layer.thickness.max = 50\n", + "solvated_film.thickness.min = 200\n", + "solvated_film.thickness.max = 300\n", "# Roughnesses\n", - "sio2_layer.roughness.bounds = (1, 15)\n", - "solvated_film.roughness.bounds = (1, 15)\n", - "subphase.roughness.bounds = (1, 15)\n", + "sio2_layer.roughness.min = 1\n", + "sio2_layer.roughness.max = 15\n", + "solvated_film.roughness.min = 1\n", + "solvated_film.roughness.max = 15\n", + "subphase.roughness.min = 1\n", + "subphase.roughness.max = 15\n", "# Scattering length density\n", - "film.sld.bounds = (0.1, 3)\n", + "film.sld.min = 0.1\n", + "film.sld.max = 3\n", "# Background\n", - "model.background.bounds = (1e-8, 1e-5)\n", + "model.background.min = 1e-8\n", + "model.background.max = 1e-5\n", "# Scale\n", - "model.scale.bounds = (0.5, 1.5)" + "model.scale.min = 0.5\n", + "model.scale.max = 1.5" ] }, { diff --git a/docs/docs/tutorials/fitting/monolayer.ipynb b/docs/docs/tutorials/fitting/monolayer.ipynb index a04bba8f..03ec7c78 100644 --- a/docs/docs/tutorials/fitting/monolayer.ipynb +++ b/docs/docs/tutorials/fitting/monolayer.ipynb @@ -92,7 +92,15 @@ "id": "e392660e-6f02-4f0b-be86-4c8ea78883e0", "metadata": {}, "outputs": [], - "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/d70d2o.ort',\n known_hash='3e4750536621be8eec493fa21a287e408d384f29cacb113b71d02690d99f0998',\n)\ndata = load(file_path)\nplot(data)" + "source": [ + "file_path = pooch.retrieve(\n", + " # Fetch test data from the easyscience/reflectometry data repository\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/d70d2o.ort',\n", + " known_hash='3e4750536621be8eec493fa21a287e408d384f29cacb113b71d02690d99f0998',\n", + ")\n", + "data = load(file_path)\n", + "plot(data)" + ] }, { "cell_type": "markdown", @@ -346,11 +354,15 @@ "metadata": {}, "outputs": [], "source": [ - "model.scale.bounds = (0.05, 1.5)\n", - "model.background.bounds = (4e-7, 1e-6)\n", + "model.scale.min = 0.05\n", + "model.scale.max = 1.5\n", + "model.background.min = 4e-7\n", + "model.background.max = 1e-6\n", "\n", - "dspc.tail_layer.area_per_molecule_parameter.bounds = (30, 60)\n", - "dspc.head_layer.solvent_fraction_parameter.bounds = (0.4, 0.7)" + "dspc.tail_layer.area_per_molecule_parameter.min = 30\n", + "dspc.tail_layer.area_per_molecule_parameter.max = 60\n", + "dspc.head_layer.solvent_fraction_parameter.min = 0.4\n", + "dspc.head_layer.solvent_fraction_parameter.max = 0.7" ] }, { diff --git a/docs/docs/tutorials/fitting/repeating.ipynb b/docs/docs/tutorials/fitting/repeating.ipynb index 156a1229..10514c69 100644 --- a/docs/docs/tutorials/fitting/repeating.ipynb +++ b/docs/docs/tutorials/fitting/repeating.ipynb @@ -98,7 +98,14 @@ "id": "7121c7e9", "metadata": {}, "outputs": [], - "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/repeating_layers.ort',\n known_hash='a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90',\n)\ndata = load(file_path)" + "source": [ + "file_path = pooch.retrieve(\n", + " # Fetch test data from the easyscience/reflectometry data repository\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/repeating_layers.ort',\n", + " known_hash='a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90',\n", + ")\n", + "data = load(file_path)" + ] }, { "cell_type": "markdown", @@ -220,7 +227,8 @@ "metadata": {}, "outputs": [], "source": [ - "ti_layer.thickness.bounds = (10, 60)" + "ti_layer.thickness.min = 10\n", + "ti_layer.thickness.max = 60" ] }, { diff --git a/docs/docs/tutorials/fitting/simple_fitting.ipynb b/docs/docs/tutorials/fitting/simple_fitting.ipynb index 27d1e485..2ddaca24 100644 --- a/docs/docs/tutorials/fitting/simple_fitting.ipynb +++ b/docs/docs/tutorials/fitting/simple_fitting.ipynb @@ -94,7 +94,14 @@ "id": "7d851064-605c-4f80-a510-197bcdbff2ea", "metadata": {}, "outputs": [], - "source": "file_path = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n)\ndata = load(file_path)" + "source": [ + "file_path = pooch.retrieve(\n", + " # Fetch test data from the easyscience/reflectometry data repository\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/example.ort',\n", + " known_hash='82d0c95c069092279a799a8131ad3710335f601d9f1080754b387f42e407dfab',\n", + ")\n", + "data = load(file_path)" + ] }, { "cell_type": "markdown", @@ -351,14 +358,20 @@ "outputs": [], "source": [ "# Thicknesses\n", - "sio2_layer.thickness.bounds = (15, 50)\n", - "film_layer.thickness.bounds = (200, 300)\n", + "sio2_layer.thickness.min = 15\n", + "sio2_layer.thickness.max = 50\n", + "film_layer.thickness.min = 200\n", + "film_layer.thickness.max = 300\n", "# Roughnesses\n", - "sio2_layer.roughness.bounds = (1, 15)\n", - "film_layer.roughness.bounds = (1, 15)\n", - "subphase.roughness.bounds = (1, 15)\n", + "sio2_layer.roughness.min = 1\n", + "sio2_layer.roughness.max = 15\n", + "film_layer.roughness.min = 1\n", + "film_layer.roughness.max = 15\n", + "subphase.roughness.min = 1\n", + "subphase.roughness.max = 15\n", "# Scattering length density\n", - "film_layer.material.sld.bounds = (0.1, 3)" + "film_layer.material.sld.min = 0.1\n", + "film_layer.material.sld.max = 3" ] }, { @@ -377,9 +390,11 @@ "outputs": [], "source": [ "# Background\n", - "model.background.bounds = (1e-8, 1e-5)\n", + "model.background.min = 1e-8\n", + "model.background.max = 1e-5\n", "# Scale\n", - "model.scale.bounds = (0.5, 1.5)" + "model.scale.min = 0.5\n", + "model.scale.max = 1.5" ] }, { @@ -549,14 +564,22 @@ " sample=sample_refl1d, scale=1, background=1e-6, resolution_function=resolution_function_refl1d, name='Film Model (Refl1D)'\n", ")\n", "\n", - "sio2_layer_refl1d.thickness.bounds = (15, 50)\n", - "film_layer_refl1d.thickness.bounds = (200, 300)\n", - "sio2_layer_refl1d.roughness.bounds = (1, 15)\n", - "film_layer_refl1d.roughness.bounds = (1, 15)\n", - "subphase_refl1d.roughness.bounds = (1, 15)\n", - "film_layer_refl1d.material.sld.bounds = (0.1, 3)\n", - "model_refl1d.background.bounds = (1e-8, 1e-5)\n", - "model_refl1d.scale.bounds = (0.5, 1.5)\n", + "sio2_layer_refl1d.thickness.min = 15\n", + "sio2_layer_refl1d.thickness.max = 50\n", + "film_layer_refl1d.thickness.min = 200\n", + "film_layer_refl1d.thickness.max = 300\n", + "sio2_layer_refl1d.roughness.min = 1\n", + "sio2_layer_refl1d.roughness.max = 15\n", + "film_layer_refl1d.roughness.min = 1\n", + "film_layer_refl1d.roughness.max = 15\n", + "subphase_refl1d.roughness.min = 1\n", + "subphase_refl1d.roughness.max = 15\n", + "film_layer_refl1d.material.sld.min = 0.1\n", + "film_layer_refl1d.material.sld.max = 3\n", + "model_refl1d.background.min = 1e-8\n", + "model_refl1d.background.max = 1e-5\n", + "model_refl1d.scale.min = 0.5\n", + "model_refl1d.scale.max = 1.5\n", "\n", "interface_refl1d = CalculatorFactory()\n", "interface_refl1d.switch('refl1d')\n", diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md index 7b59c331..b98db82a 100644 --- a/docs/docs/tutorials/index.md +++ b/docs/docs/tutorials/index.md @@ -49,8 +49,14 @@ These are basic fitting examples using the EasyReflectometry library. These are advanced fitting examples using the EasyReflectometry library. - [Multi-Contrast Fitting](advancedfitting/multi_contrast.ipynb) - -## Extra - -Additional examples and supplementary material using the -EasyReflectometry library. +- [Polarized Fitting](advancedfitting/polarized_fitting.ipynb) – + Magnetic samples, all four spin channels, the spin-resolved depth + profile and simultaneous fitting of several channels against one + model. +- [Constraints & Inequalities](advancedfitting/constraints.ipynb) – + Equality constraints, derived read-only parameters (`total_thickness`, + `constrain_to_sum`) and inequality constraints enforced during BUMPS + fits. +- [Bayesian Fitting](advancedfitting/bayesian_bumps.ipynb) – DREAM MCMC + sampling through `MultiFitter.mcmc_sample()`, convergence diagnostics, + posterior summaries and posterior-predictive bands. diff --git a/docs/docs/tutorials/simulation/magnetism.ipynb b/docs/docs/tutorials/simulation/magnetism.ipynb index 63e49538..68071c07 100644 --- a/docs/docs/tutorials/simulation/magnetism.ipynb +++ b/docs/docs/tutorials/simulation/magnetism.ipynb @@ -18,14 +18,7 @@ "cell_type": "code", "execution_count": null, "id": "644e53e3", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:34.890141Z", - "iopub.status.busy": "2026-08-11T12:33:34.890141Z", - "iopub.status.idle": "2026-08-11T12:33:35.320450Z", - "shell.execute_reply": "2026-08-11T12:33:35.320450Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline" @@ -35,14 +28,7 @@ "cell_type": "code", "execution_count": null, "id": "29d5d62d-af4a-416d-bbe2-1338d32b30f5", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:35.320450Z", - "iopub.status.busy": "2026-08-11T12:33:35.320450Z", - "iopub.status.idle": "2026-08-11T12:33:38.194648Z", - "shell.execute_reply": "2026-08-11T12:33:38.194648Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", @@ -75,14 +61,7 @@ "cell_type": "code", "execution_count": null, "id": "549734c1-bbd9-41f3-8a20-d7a8ded37802", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.194648Z", - "iopub.status.busy": "2026-08-11T12:33:38.194648Z", - "iopub.status.idle": "2026-08-11T12:33:38.202401Z", - "shell.execute_reply": "2026-08-11T12:33:38.202401Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "print(f'numpy: {np.__version__}')\n", @@ -117,14 +96,7 @@ "cell_type": "code", "execution_count": null, "id": "0f95d620-35b7-4b47-a3b4-9e33d5525b50", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.202401Z", - "iopub.status.busy": "2026-08-11T12:33:38.202401Z", - "iopub.status.idle": "2026-08-11T12:33:38.215900Z", - "shell.execute_reply": "2026-08-11T12:33:38.214943Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "sld_4 = Material(sld=4.0, isld=0, name='Sld 4')\n", @@ -152,14 +124,7 @@ "cell_type": "code", "execution_count": null, "id": "2af8c30b", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.215900Z", - "iopub.status.busy": "2026-08-11T12:33:38.215900Z", - "iopub.status.idle": "2026-08-11T12:33:38.223629Z", - "shell.execute_reply": "2026-08-11T12:33:38.223629Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "two_layers = Multilayer([sld_4_layer, sld_8_layer], name='SLD 4/8 Layer')\n", @@ -184,14 +149,7 @@ "cell_type": "code", "execution_count": null, "id": "b0259cd0", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.223629Z", - "iopub.status.busy": "2026-08-11T12:33:38.223629Z", - "iopub.status.idle": "2026-08-11T12:33:38.229743Z", - "shell.execute_reply": "2026-08-11T12:33:38.229743Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "refl1d_sld_4 = refl1d.names.SLD(name='Sld 4', rho=4.0, irho=0)\n", @@ -217,14 +175,7 @@ "cell_type": "code", "execution_count": null, "id": "f1500603-d85d-4e16-b697-e1bf16502991", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.232600Z", - "iopub.status.busy": "2026-08-11T12:33:38.232600Z", - "iopub.status.idle": "2026-08-11T12:33:38.237388Z", - "shell.execute_reply": "2026-08-11T12:33:38.237388Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "interface = CalculatorFactory()" @@ -243,14 +194,7 @@ "cell_type": "code", "execution_count": null, "id": "18010202", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.237388Z", - "iopub.status.busy": "2026-08-11T12:33:38.237388Z", - "iopub.status.idle": "2026-08-11T12:33:38.244064Z", - "shell.execute_reply": "2026-08-11T12:33:38.244064Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "model_coords = np.linspace(\n", @@ -282,14 +226,7 @@ "cell_type": "code", "execution_count": null, "id": "cdf959c8", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.244064Z", - "iopub.status.busy": "2026-08-11T12:33:38.244064Z", - "iopub.status.idle": "2026-08-11T12:33:38.717693Z", - "shell.execute_reply": "2026-08-11T12:33:38.717693Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Refl1d\n", @@ -334,14 +271,7 @@ "cell_type": "code", "execution_count": null, "id": "bf311973", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.717693Z", - "iopub.status.busy": "2026-08-11T12:33:38.717693Z", - "iopub.status.idle": "2026-08-11T12:33:38.962554Z", - "shell.execute_reply": "2026-08-11T12:33:38.962554Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Without magnetic layers\n", @@ -391,14 +321,7 @@ "cell_type": "code", "execution_count": null, "id": "18cb7037", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:38.965319Z", - "iopub.status.busy": "2026-08-11T12:33:38.965319Z", - "iopub.status.idle": "2026-08-11T12:33:39.181473Z", - "shell.execute_reply": "2026-08-11T12:33:39.181473Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Refl1d model is inverted as compared to EasyReflectometry, so the order of the layers is reversed\n", @@ -449,14 +372,7 @@ "cell_type": "code", "execution_count": null, "id": "7033f755", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:39.183451Z", - "iopub.status.busy": "2026-08-11T12:33:39.183451Z", - "iopub.status.idle": "2026-08-11T12:33:39.188618Z", - "shell.execute_reply": "2026-08-11T12:33:39.188618Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "print(max(abs(model_data_magnetism_easy - model_data_magnetism_ref1d)))" @@ -485,14 +401,7 @@ "cell_type": "code", "execution_count": null, "id": "352c35e9", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:39.188618Z", - "iopub.status.busy": "2026-08-11T12:33:39.188618Z", - "iopub.status.idle": "2026-08-11T12:33:39.424010Z", - "shell.execute_reply": "2026-08-11T12:33:39.424010Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# The magnetic SLD is set to 8 and its angle to 45 degrees.\n", @@ -539,14 +448,7 @@ "cell_type": "code", "execution_count": null, "id": "239e2a04", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:39.424010Z", - "iopub.status.busy": "2026-08-11T12:33:39.424010Z", - "iopub.status.idle": "2026-08-11T12:33:39.723594Z", - "shell.execute_reply": "2026-08-11T12:33:39.723594Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# The single layer model matching the raw Refl1d sample above\n", @@ -599,14 +501,7 @@ "cell_type": "code", "execution_count": null, "id": "f70f3a34", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:39.723594Z", - "iopub.status.busy": "2026-08-11T12:33:39.723594Z", - "iopub.status.idle": "2026-08-11T12:33:39.729761Z", - "shell.execute_reply": "2026-08-11T12:33:39.729761Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "for key in channels:\n", @@ -629,14 +524,7 @@ "cell_type": "code", "execution_count": null, "id": "23beb5a6", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:39.729761Z", - "iopub.status.busy": "2026-08-11T12:33:39.729761Z", - "iopub.status.idle": "2026-08-11T12:33:39.739748Z", - "shell.execute_reply": "2026-08-11T12:33:39.739748Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "model_interface.polarization_channel = 'mm'\n", @@ -666,14 +554,7 @@ "cell_type": "code", "execution_count": null, "id": "b6975f54", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:39.739748Z", - "iopub.status.busy": "2026-08-11T12:33:39.739748Z", - "iopub.status.idle": "2026-08-11T12:33:39.834787Z", - "shell.execute_reply": "2026-08-11T12:33:39.834787Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "z, sld, sld_magnetic, theta_magnetic = single_layer_model.interface.magnetic_sld_profile(single_layer_model.unique_name)\n", @@ -702,14 +583,7 @@ "cell_type": "code", "execution_count": null, "id": "6327bf82", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:39.834787Z", - "iopub.status.busy": "2026-08-11T12:33:39.834787Z", - "iopub.status.idle": "2026-08-11T12:33:39.843123Z", - "shell.execute_reply": "2026-08-11T12:33:39.843123Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# `experiment` is the raw Refl1d experiment for the single magnetic layer sample defined above\n", @@ -733,14 +607,7 @@ "cell_type": "code", "execution_count": null, "id": "b087e848", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:39.843123Z", - "iopub.status.busy": "2026-08-11T12:33:39.843123Z", - "iopub.status.idle": "2026-08-11T12:33:40.044974Z", - "shell.execute_reply": "2026-08-11T12:33:40.044974Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "# The magnetic layers attached earlier in this tutorial live on the `Layer`\n", @@ -788,14 +655,7 @@ "cell_type": "code", "execution_count": null, "id": "00c25554", - "metadata": { - "execution": { - "iopub.execute_input": "2026-08-11T12:33:40.044974Z", - "iopub.status.busy": "2026-08-11T12:33:40.044974Z", - "iopub.status.idle": "2026-08-11T12:33:40.052467Z", - "shell.execute_reply": "2026-08-11T12:33:40.051582Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "print(max(abs(model_data_no_magnetism - model_data_magnetism)))" diff --git a/docs/docs/tutorials/simulation/resolution_functions.ipynb b/docs/docs/tutorials/simulation/resolution_functions.ipynb index d46a76cb..46fe1fc9 100644 --- a/docs/docs/tutorials/simulation/resolution_functions.ipynb +++ b/docs/docs/tutorials/simulation/resolution_functions.ipynb @@ -101,7 +101,27 @@ "id": "609174e5-1371-412d-a29f-cb05bfe36df0", "metadata": {}, "outputs": [], - "source": "file_path_0 = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-0.0.ort',\n known_hash='f8a3e7007b83f0de4e2c761134e7d1c55027f0099528bd56f746b50349369f50',\n)\nfile_path_1 = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-1.0.ort',\n known_hash='9d81a512cbe45f923806ad307e476b27535614b2e08a2bf0f4559ab608a34f7a',\n)\nfile_path_10 = pooch.retrieve(\n # Fetch test data from the easyscience/reflectometry data repository\n url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-10.0.ort',\n known_hash='991395c0b6a91bf60c12d234c645143dcac1cab929944fc4e452020d44b787ad',\n)\ndict_reference = {}\ndict_reference['0'] = load(file_path_0)\ndict_reference['1'] = load(file_path_1)\ndict_reference['10'] = load(file_path_10)" + "source": [ + "file_path_0 = pooch.retrieve(\n", + " # Fetch test data from the easyscience/reflectometry data repository\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-0.0.ort',\n", + " known_hash='f8a3e7007b83f0de4e2c761134e7d1c55027f0099528bd56f746b50349369f50',\n", + ")\n", + "file_path_1 = pooch.retrieve(\n", + " # Fetch test data from the easyscience/reflectometry data repository\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-1.0.ort',\n", + " known_hash='9d81a512cbe45f923806ad307e476b27535614b2e08a2bf0f4559ab608a34f7a',\n", + ")\n", + "file_path_10 = pooch.retrieve(\n", + " # Fetch test data from the easyscience/reflectometry data repository\n", + " url='https://raw.githubusercontent.com/easyscience/reflectometry/master/data/mod_pointwise_two_layer_sample_dq-10.0.ort',\n", + " known_hash='991395c0b6a91bf60c12d234c645143dcac1cab929944fc4e452020d44b787ad',\n", + ")\n", + "dict_reference = {}\n", + "dict_reference['0'] = load(file_path_0)\n", + "dict_reference['1'] = load(file_path_1)\n", + "dict_reference['10'] = load(file_path_10)" + ] }, { "cell_type": "code", diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 85009c8b..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=python -msphinx -) -set SOURCEDIR=./src -set BUILDDIR=_build -set SPHINXPROJ=orsopy - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The Sphinx module was not found. Make sure you have Sphinx installed, - echo.then set the SPHINXBUILD environment variable to point to the full - echo.path of the 'sphinx-build' executable. Alternatively you may add the - echo.Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f3ac980d..65c873f0 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -195,12 +195,19 @@ nav: - Advanced Fitting: - Multi-Contrast Fitting: tutorials/advancedfitting/multi_contrast.ipynb - Polarized Fitting: tutorials/advancedfitting/polarized_fitting.ipynb + - Constraints & Inequalities: tutorials/advancedfitting/constraints.ipynb + - Bayesian Fitting: tutorials/advancedfitting/bayesian_bumps.ipynb - API Reference: - API Reference: api-reference/index.md - Model: api-reference/model.md - Sample: api-reference/sample.md - Project: api-reference/project.md - Fitting: api-reference/fitting.md + - Constraints: api-reference/constraints.md + - Inequality Constraints: api-reference/inequality_constraints.md + - Bayesian Analysis: api-reference/bayesian.md + - Calculators: api-reference/calculators.md + - Parameter Limits: api-reference/limits.md - Assemblies: - Multilayer: api-reference/assemblies/multilayer.md - Repeating Multilayer: api-reference/assemblies/repeating_multilayer.md @@ -211,9 +218,13 @@ nav: - Layer: api-reference/elements/layer.md - Layer Area Per Molecule: api-reference/elements/layer_area_per_molecule.md + - Layer Magnetism: api-reference/elements/layer_magnetism.md - Materials: - Material: api-reference/elements/material.md - Material Density: api-reference/elements/material_density.md - Material Mixture: api-reference/elements/material_mixture.md - Material Solvated: api-reference/elements/material_solvated.md - Data: api-reference/data.md + - ORSO: api-reference/orso.md + - Summary: api-reference/summary.md + - Plotting: api-reference/plot.md diff --git a/docs/src/_static/favicon.ico b/docs/src/_static/favicon.ico deleted file mode 100644 index 4231b9d5ffa3194b8ffc3338dc78ca098397ffe5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21690 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 VCF>e^DB%DA002ovPDHLkV1kC1I*b4S diff --git a/docs/src/_static/logo.png b/docs/src/_static/logo.png deleted file mode 100644 index 6555b70d1f6e942876aeab5396a9d397be409693..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 395415 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

7OP{NJ8;65-CBCL?qler4ps>elpyr*;kNBDR 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%0DH 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%0A@0`wXyZ5upX;V)BayjYYJnfdKnWn!y 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

dT;y6-~!QE z$#Uo;z)df9yA~5F1i950_ zjN{+wy{}nIrCovO8lT={uXAov&%OhoYw%%X^1PtaRo7PmM>^bnb(Zxsz6G2qlPwy# z;G59vh;3Ph&s&N2l)*!Xqo;;KmFj!H9i-r+^`g~!4*k2#8<4SNizV~V+P)&MpO_!{ z^{es(ZsN}gscOqBmR0?LZs#M@sBkB4U&Oj+C7v?NFjtMebEc|PV(xA5S#BNgqmqT# zxolHarNdwt7Zk4dmoX`i{r=f#O%U)_qS(GE+*%_@(zT)I*uAO({9fz4ZUmd1?udmd zthtLZCm9TZZ`=omuO=yfuRMoWs(|6@p^FGDcv$dOF?|_eIjEhQ=1|wCM}TnRCc^xh z0lE63>m#t(Qm=4frmBV2u)FW6hT)c)u-qxnI_@dY{%p}E6;0@a8dmW>(40r}%*PIE za8Jea4hd>?rgUV5_&Ti@SEfb_Nh!av(X!Oi)s1t4Usl6oFF^hf2>hWaKcOl$p9llJ&yVN z4I-epN!M#)Ee!mvR;hzKhnDY5bZ?f~W^zH=y%sFx6Eoz+E0seGhC(O;Azs)oz(o4T zzJ1HPfD(7GL6*P_xM|SUx$s_dxylavD}*fNu^69Br^2nZH2)Xj<{?V#Od}6-!2)>p z!X3Tm@O<3)WDJ0}3RbPij!FdWGc_6ve~VJ4ye~^-P(Q)c9&j%-_00Ssc&bx!J3PA`{H9y{SW7<;lA0XeLPk3y5GeN2 z%dk)W7XL4^ueTRThU0tGPg%c|*URN)oYM{?qkX{a=HnT&6oZ48pxR$0Q3_RicxHE4hTN z-P*IQ8yt1g7z0N3-Z@mQ#{7yibfgme?j?*9)MLHr++bni_h{YVI21IMa+hlvq7?4+ zxS_w6J!Fif-WK#8j0-{^4qiL!7wA`q(C*tixu>Ksn*ua<_QeNSt4~Qw*wkm*uOQws zV+tA9?wxlB%n1K4&SbQ(Jh0(OR@))%AHFZiWOZDf*v26CCdD{}48W5!E0n!XKx|GLoEWBZ~`0koPaSQ$Qw`CPxCDGpWVY4pNNA^ zZl7Sd>4upSVwTEwtA*=;=5uFvzzdJHYF#~_>82eAb%vFbFm7n03*&}f3q^m18YBQ# z6fx5;H$*PAHWrqS%w;@Eylag2DT@5Tw&`KBz}NNuHNkXuz}d(!LEy5Llcwd+W}N0Q zI$d&Il%`_$`4=0{E8kXf639mYLdK=KYeT(BFQkC2(8+igBfID0V=$tJ{!vVUolVIh z{-+xVwAoBLBnbT1ZLEhup07@sm4A-asLhZHSy5VePS>%0Gs@5%49_W|#W8TAst`aDD ztt_DV4$6WCk)DRhWj9}o`~6oe#0*ZdXX0gBmp(++{~F_T@j%MqQ5E<{LkM={?U=+- zIEFF#?81?1Y?DnTQZCK>oIKxlK0Ri-&Y+=A&+}xjRDbsgXy~jR$D~GsWCNV@%N65V zPco@qVfRcEEKR;M=Ktxn`o7`|jTETq#T^gpg&UuS3J9k01NXh7?*{bE%+yZ1xwpk{43fUxo)b;7?F1D7Lq`*K5oq2IF04<0)ZN&g)_j zcsTN8mE~38o-nhycKO7KtSZfRTXyC`AIdyZSF(Ya&3^9l%c>Kx33ZZzOK@tmvE+rX z(tr0RpN-S_^xz;~Zf9*^b~35tw(3WTVz%v-7YrV{-wpaAdP|8Fxm`yb2ln2f-P_nv zOW0@D&HLjmH(Fu0Q4yD<=WDSeQW{68VJaY{GAro6S%4e1@ejw3 zYZlW}vVssV&ZDZ04ZTL*=Fk&|&9U`G+Li~F#Fn-}w7;)#P9&^hd0e!Axl$kG1|!+c z9Q3w`ts6IxHnxb?NL7D>gAP@$p;dCf?wsLbZfNX1jm%u%`m);rV>648%I>+P>>buG z7f(5Cvw2_mKT#iq4wR6;E;c|ilz#kvF@sk#gh6=f6^slPJ!@$GhF+b11J%we5hHBy zu%~Itd?R~yR83Ddq&WmAI^zLZeR8)P2C53cp88X7my97 zr}olzD&z!2Jxn<#u3!$rD~tia#2X5X!AgFD9bo86MR>!sDSpk38-_Bo$+7Ps7rJhW zr@E9(aAt>@-r*b=n!V5tz4`x!zFo8r>0y4My$3cJLAMO|?wGUa3(paAygN9GY}jn0)jB6o+nU6Q#^DX}ZYn9(TOJ;S(#T z<7U;YOHQ42)>AN0xJZaHn4?M*nd=Fi@4yKPwYp}ehXAGcPJK5i>wtntN}sRSj}vJd zY#(1eCh-v;~~)c!g94s@jq7l;J2sgcE0n`lM6j`W8;^w zRdsdAE-pxat%51)8Ysdv3@u8Uuu|z@4X#f`FR-_<_;hjAip*uKQT1V`{Ro zpCUTnG+72}4E$Ta_P{-8e4^#2J3eO1WI)661$eQuIf2+Q_^Ixnis8Y>*bbc#g(bJg z+kH8)+kZ&h{b*X=k{`d(Xr)=Nd>Dc%2{Te-&YcF|g3qXRXz64LKj{^r$A_29J9nYM z-<;2qEQ*yEI%T9y0={nY^!~+{4@e=N*TwAU_NROVJoN~>X8642zoVR2IiUak$^=v*A-aHO8_NDh!Ge=QGDcVcDuy1G{;s z3&TqP?SeAJc0o0*45G(=bA_feSSf2HuVdU=Cs5$o5?0XMYkjWv0+PMcome%*T-3D@ z*ktAbqaj&|up6Y{9CK3nD~QYsRv^eHzLjF#_h0;=%I!!ot|Sm+V7BBZP#&bFv;~@1 z+Ujy(_J{gGCq-AYRCyr{j^$zss-VYRo8CJufmdQ<6`V6*iV~b3M{XERo zkN&wb_lip=LfhK-`SnkYQ*|cd!~IX*K?NHw+@&<_Wg1O8&DsqW>`I5@%f+as_5Ys; zHjyJJ08Zq?^tJK(aEvt2>_4X|VZ(tC0VH}RH9Zx~uAUp1B2TeE%$r203{CC2D^3cQ z7f!~QKTqYTIm?{X?nViY{>H&xHuz?LWHPd1|y|zb`E%QI=!c#`#fE%3B#I)fz zm;w1Oq@FPO0RuZY=`T_+3Je1Z6wBZ^J+-^Q2Xo(?)(9NlYVJ>NVdM&g<=+}#&u-nC z9IZR8uMnh!K!0vi#d%mpVd5EP;xJPA{Ot4|^d3xUnA_`DFmu9Hrle)+29{hi-NZBB)`fOPBR&_cgDa~FZ!d04|VPV|jHRMO@VTp&-g=h0b zVN+e5!F!C$H>1W0!aHbQ<7Pv-_TWamg{@ zRd8Q7K2J{^oBv}T!&KOGO;fv4jh*P=@ty-l-QlL;jNpjqn$mr4*kG_TEgT7ua)dlz z^9A!JMQYu4N2O|$gSU8C#}10-fHV15s1N;o>18n3Ke%YsQyYHQqYn*}-fep79zwxz zBL0xAvt>!h70XuC^>#(kK5Gh4BSAug1w%s)e?PnQ%r z`K8cP@j`!35OmtD_xt>(Ft|Y_8+_%w(4%gj z+)X8?wf1l{qfMx4i*%vAAST)>rk&ctSp0iq;Td`P`;C`dMvZRYH{2fksjc9?(a;h7 z!&lA8Y4#oMBTe0lxo@2BW*hfPGK=r&B$QdlANCo_DVD7Ctq&gkuAj|sWRPt`SkYJa zXJ`EMuC8bo%Gqy)4DQp_am-{i*VscgIlz5|iGeU_1o}l#&W!9XTj84|ncSQ)sqiBX9=w!eCURZLSV03BfNvf`a)58bup~Ahx%+c|)cr-AtM~M_N*O&b zYE1~bt8K9F03%J*pMNtlpamxX{LM9Z>Tmua>vtlK9W_nyf{uYijtyM6r$B>mn$&VC1BrqbTv=4-oHbDO-lK0?ntLha z@<+9p>lNc}GQ&IJ@G?5gNWHA1F6yc&->E&V4sTV;8!^io?o}a?a{g;J6_PY&loLev zrMrJL!m4bvYBi32Q2LsTyP5O;@B$xo((YUqm!*{12YdISbYohtFMlhHo$NWC>ipgR z%{kAX0~-H!UkkEUI5L)?gKl+{2zZ$1tVZM+qnArX@ARghDj>$%ysf7>64S|V+VpQ= z{-HTK__Gj%53RxPRZ2W~F3s6={FZdO#6Cn~zHt^|`SK7atIkYuajkOJFQMwS1pF+= zd?@ZGJ7I&}{U3DvoN;uADbv^D``5n~Z#Q_k z1C5v$M#|{rFxAr=eVYsiwS~ z%KU5EBu5C%xi97CS|)3<<=9)Vtocu+z9lB2ak?$IprqMI{^d;8O$`eKxbZYhA@NFHs(?5yZo3SLDKBzV+46OX6JbQMr6^etlUnek`Ox z{D~i5vnV=fJMZhX`Y);)5Y%u%@qtS#TkcTSuqiT>U;c+*#cmr-N>gj?yQKsuTH3wdOsa$T3+an^dzksNf0j>F;1GPOm=_nLp)wWb^2 z^WJ>F6}LT_VcPrjvXnH-UHtaLucuN=xcIMSul(Hl!PXNdkYE@8?W6R*P(< zM&a1Zs65uVC|Krx%~2huYMQt~NcYv4t$825ME$woS;S*X6|NOQnAeE2h=i3@+b7E6 z?_2QCx%pWn>119OL~{V5nK2mFG*3>|xWx2vrjyOD7Zn_wvHRpRJw_KAM2Yx$Bm!zo zB=JDliw&ahnm}xeLxM?)Bs>3bw%o64{-QOtIZPo? zBkedBzlP6;{umPzOWUk6tdaQ5|{Crp`#s_iwZC%l$$< zZaE|IYt0Zz(b^l#a%S-_v9NTyV{rN_UFTbGySPA#i$lu2@W6n^9v_g%f9OWD{mtEp z@^c9CdZPl~jCR%^p9d%su6&*JdG?^h7ejV&Ks1d?Ndn+{OiQdMGI0|F0zOIBk?;+D zk0vZ(XSc)Wm~?s$#nLL&W@qqL__zVGw&3Rkc9K~qseRw_TGd{XC=~E_GF7L&_w$5Q zPvfgKw;hQx4`%J?LXBjqImzq3Y#O#tr>g%?zMeG>BmB!cG`j{7fRCuht{jk64QTZ_ z!WXn4|N`q))hM4sm|MN_aIp z!TRM*IV^J}G1^4;y#%Q@-xHIGtHJlNZ#B;zG(SW=*8ly?MFbHVg+eDm z$VTI$i2Lz-IKl!1mQ$)k!cSw%=N@B?bv(QQW# z>skpvfzY5kOp%(@NX>Nl>%DUil zI!DE8iIgW8JkXl+^)*wkL`E7UQg1Bk8z?J`6iaTA^m|VzHhQ7cOp!T@lf}(V8tv01 z9RnzvX70dr*QKjt6v5xqK8jvz)tLE|-P6z;o7a56R_AbF&$rui15ZOrF6e779qmg# znl1k2vB{{@)`Zj@zEeD3B0Bj#&7TjubUfe`IoLC?vQXZ&zoI*)=m%7)tb9LGInjq( z%7jdA&!+XGA9z8`$YS%DZ^FY7eUQexUR+Id+CM(wS~<4{akfeh@+NcN7Mx2RE)31R#H5Kr1gfn1+!Tk9kK} zHo?Sp+)@r;#no(G8?N1%RSDYIt$@B|MMs`?uuVu5#%GVQD-!5JhqNv{16iA!C70x8oX&yB^+#A_gBnW?td*UWXbD zDt9awddZ{4LAprN0#6R@#LZexLU%S>(qy|*QIrNd7B6oZqPs+sQtB{DeFeMTr zevgNc*T|DJ+@3?5B)s_a_))60QeYv1x>;EK%$b1b&W_>w)#Z^IGnpz`3@myuuxN?= z72x+v-cb>;=!gas!1rkY2@&IukZz*Z?WhoG_`kkIoG8$-uz^IX@Xs5@!M{Mlg|i|} z;z9K9G+xJ$_^QiI^b*vz)%Q5AY&P=}3NqdWaL z*>K`RT!%7=+Ws@Ek;vE2Z=He{=OB&nrR)`T|GRiHTBvEhmH$Suwy5l&@+;?(GNeTB zYSaSlN082&fDE15kpK@hjuue(zHA-iQxeqx<$xt{aqaQplWl0CsL`$Jjh8pARf@q@HYa9DVsz1MwK^%@Ky5p9hFhFCl*ooLX9>+?#GvZ#`|0`EU@+UIs^|Asf4A z`)SkqhgAYK-R65|-=7LD{~V${dOnbQ4(P+R%l_#=ABz6Q~3U~qh?A{>c?w#7eo8mlwnEwGPqA3ES zAv2i^WV|xsZV&nRCQg%C&gx_HdQ+Ep;Krpk0BSi&nwH86w z;*h?611={Dy2`@Lf2Ui~g{>3oT!bbu5dmI^@6?CiZ} zg@C@j0o^KRrVll0wY@)FY*@70?iN}Cn#2rLEh1f*A>r3v#2PqQBumY4+ZbKLM6Wtz z>0yjJkZ~s|g9mPi(LLW-;w)Czw(u;f%$K;NIYYbeTG-%8i?fe8V)JPczdpvPDaLV6 z!jX!f4L;R|o*RE}5ld(P;P~>emmnAl%)y?Rbwd0ngKm*W^J|rOY6h!TzORvG9e+z} z5kAq-7Iu0CdeC_O6kET`*P>IC7t;E8t}9AC!QowzZS7J2)yEA+(A!2Vv@nd?-4X>(s2zt)@gxaQ{+yU_J zi4j7vY9;ZFgyU6qxPvAtIb1>&rjm{)X=Avno(B|PtDpt~ELMUQ9KWj|*~Hq+sFdtA zoe{y?GF1V`%>)h`=7QuvW?uJ_*+g-z1{UEwoTfVHS4TYPXTaMK;NHhQv#4fr7_+F7 z%+}%7#RK7OnJhn-bRG*ZSyY^kgs=0J*5Jp~m@jdD1h+84EQ25VBN_y7aEdrk0;-?eQg5kBCnGPpb^U zO)0LQUbK?q3X4iNygFOrL8f**aRjHy;eHuiD-lOJ|L{dI=0xXt2W8C(^$XbQrBT^z zonL5Qo&acw3gBelvO@t9ywQtk!U#1`gy1H4zG1rEol2hpJ+iOxznmQ~0Ba?xBcbLc zUO5vZWxruIgG2i!1VcKCm^FJaDUv4_z_tA~EoinDAYxC*)vOQx#jx#YbPS}L80cQ1 zM^h<3CZljx81N?rlCu`S$4wZ;q^~$$1MbX)Sab&jaqb%~f^T-Hc_o?iMq-@zU2C1Q zRjv25cR0b~euu5f;uwYvZR}gmjl-Kff6$OTMe;v5mNM17U1GVL!`vKYIA-9#OMkWK zXF*8i>ixp#qJ$$O1KC5(g$%d7PnW${4b|@ZA!1X2lC!pPK+#LgjE^>G?8Js z?f4v9TAeL5Mt3yOxp2K_#J63i5%bdF@SQ8p`@$RyoDrdSNAU(XnLio(z9a^ab9Tvs zVQ=y45H3-PG=~qpc#+Z#IwAa7moVOgaFYZ4S7QDjulor}TI!AonL$i-||p#IGW=5G}Z0Mv?WOY{a%%YOQW?v4 z>jD2uq{V#w#2SB}YFi8yTCIWWzK=OU2wUA;CGv_}sj~Qv#F&gU8YP;L499%@SMAcw z%zjQTd~STWX6M&xFq#+Iad57uyf?w26Tsl6zi>}fKbyi#l=%-Dk+1$^S5#Sk2{k{x zaAJJ#(YCikxVq<)#(VPicZ3nGwnx1bQtuVexEstAN4_iIJ-qJ9R|H|4YUX7HZai^P zs#%G_C#q}Z^U4E19W$swr$sSdKxuUUc=uXU)@J-!2v`^&I5#$Uj26^Wt_F0`u`ASkh~nE zowQ-$C;;iI!BVvXT$+}rxO`X-CEzreQ*pl7=f}7hf`YwsTHf{I7w{K73#5CVAk5xg@^$V`< z!B@5NTXKKA@Q?6b31KX_OI8YEBFB{y8^qwS^CKf7a4qnGjw_SofCUwE5bD~O{SnzX zqC18FVv)G5e*pm`xe4M>yZ+42rkyFw=h#xj^i++r7HX(S`b0Cm{?i*hem%%fM&c~N z0t!n&co&6|0|uf~Z=K-nafDf17+j?=2CjF2(6*U%^g%uMfMWoxb4797Ed$hFsH3PgkgcW?T}sgaeKqcJK85TI3Lh3;5m zEOtk1G4#`p8>$NYBTWT!FH???joKPaB-Cvfx)?7GZA}mzPA{$uhkLqcYVw^Quj3JS zQ3p_crJ3L6t|EJpYx=8rV(0FEvj7}ULH^zm{B>!r2dK6DzHe#k3}v}G=Uin9Zpq!F zc@W`@1vdGTY77AD_cQSgS`b$ycQ{r6``C+~L#uOvcgCuaQgEzFq-NYSPQ2*NK_q61 z?AeZpu^js)7(C{oh#*M81V7$Kl(2^16C$WuIr_1<>$QF2ue62AF>mboe2$fg{t@He>tk9(duc) z5DyRR;;`3K^Sy^z=za;9xJEvF9T`w;@rW^5k*j);oLCIKPllW>NCp5EYCI;vks#w=sI|%yLlPs$5aFKvi7$fk zB+#Kq>cfpg-N%{re?{af=-9WwJ*JbCCaeH&9hd`3G!GK@mz-rv0FuC?Oe%!p-_`x9 z?mh!N>$%0=8|bRcViv8D=`SnsEhyQFJPE`IbnJGM*ZRG9SkAm&uP{Q8)U>l0Xi<>@ zhDUQdXAr#{QNH(1t0;(FZuoUi91MFXd zlK`dd^a4MA5B##+)sHw3=Y75e<&gUH~zt|zRH4x|hxzggHaC=`o z;lu`qik_~1BaW$9z3S+T2*%lLGjT5&_6&$WX8%l5wE^B~r0v39 zfm!FLf~HbVwVt=v`>T^_E}JTfq4f$N?mc~7te_o1^`eM%>Vci&oE%$EaK7ry{}|VB z5HMNmbTjU!-CZt_&`FF3@NiC`o8eLg@kgBd&L{DECb9|JK6`n@7i?$=SC&H5!&c_# zd!x}_Q?;|`>AKYy;f_@L`0jdr3^ghjJ|@G1R)=-s-^1 zL=wI6V<%Ym=;VBN@Te$GTb21Alz0WNtn6UyeDE(f6O;|in%VoC^(PQyXlfp}q)Zyd z`^XX!Io%819GpQxe7$+E!pn{DDa8H?_rqUb;=#W3wb;zc5fck0u(qMa!5ocQ!0}F2 zs3+vJK@|t#a2DH4YB#Ef(b=aRNe9eDx9t)0MDlykabxd6rm%xQG~gYMe{aXtMc(yx zJ{a$4gVurX^<0C~T^$RtD_W)@{|3U1AWp5&9%C(9*rO&TbCoK~WF)f;#CqM)h4$F0 z|6KT#hPq~tmauH(N}|0^U!H-uzbE5-vEt9+AGhuN_Qw$AhF>qNCTf#9S8prI>$P5` zZ3<4okbs`^7BIdC>`+zs=&9F@D7exDGUA-XJn&eG#XOjek4U(SJY4y&qGjwujhI)$ z3IHTAt6pMAVq8=qjYD3iPP4n;bKV!SD)?Pollx;L2V`}MQXJDUgu@anL%OZalCrPE zu4AHGaREb>f?HR8HOf{r+Sof}Wwb}T%_3S^3MKNrhR#RF#(t@xUDrB8kJ7ZpiRn$GXXx4(w20+~q}F(|8$z_V5&HGZZ~ z+Qm@RT!J$lF7j{w#3P7M;@142xCG%Q5S+1I{5o(>$b_D+7zG7bOxgfWlvG@8dm!qK zBrHTNdMwRYS0oX+Zfl_ZP=CJHn)xkm^5WN_^oXxu_vsQ_ydkq3*TNG=w=l3}5v0-# z!h|n!<$EW|yXUe_Lrc2Cyi&11s7Ys#n2LHyDDKzP>=B=P_=?nRW^*t zLEL;#n@d@TJFi6u-}OE{ceKCwc@zJu*CyjiY}C9AGyw|6+{q@NFYDGw3;z!vQR?s= z4@hl0Cls706?7Q&5+~TQyL+G&mE1_w>kr{xIT8;7t(CD}MpR+HI>>u70k3$7i1kJf z?x!F@mc#w3{ozgnT`U!78=icFkX!>QgnTrU@pMuCKN!fDzYrZ^l}+V02ClzfyF@OO zp15sz*{?WZ%Mn2W?G({o40}6|52N?;LlL7pc?zWam}HN`WkZYVMKQW_11mMjxbdt| z^H@y$%%p?(;Ux4`xe#CVj|jxa9q=%+B|X6ide=93C^V}2H>dZ9NuKGDSR43^*-Wc- zA%U1!rz`b;qDxv{v!0jN>u;eYV5?9xgsD3DQg~&AnDTMMM-0}gK?a{vTVWx=hmrQ) z8A2d1u$L$B2?@j__}Vz)SKQJyk|VzFgbPJIP7gDi;-K#==yV-r&BywGSYV7A6XJ=7 z@q3gH>Sbhm>w)(`0C z7}NzR84m*8sl5ii%N?{UhP(;eVn&j_ef>ND{impxw(20k?ZG?}x~E7A2ef$a4#?TZ zm`ycgj-{4|S;3~duVZuHh}Tm4V98&@m4X$dS}-@hX#-?wLSAh(bK15W_pxfn7aA3G z2<+NU8661JGOxWX9l+B1M62)F`uD<9_TG$=+=73^z8e*CK*BL*({fCQqn4q~uIls> za0K}96WJa|=U)T&7s0u^qZ=F*!h}||`V7Gl%{f0$>6f+{D`tN;TuPQOmBnUs73|~` ziIfZ*MbaLo5ThP!;qtFLm;}@v)=`h8dh-w{H)0K$(L~lK=bSAAIC%hniiGXs7IJ{v z{jbhqIYytzwoh$1`mPle0ixCoqpn5F`O%(lrAX4(;1I8XB5Smdk&IlNHc+08KFdTC zu~ywT=q8<>dWXx0zx(Ch`KF&4r@G%`>6Lehem7_9O5RY7>$AG@&B~ovR;gmq#9KcH z7YJXQ!he<26OM$HP=&o85ybm7y~4$1mLO!jTOxMGGV%t0M8Rj>Z zYUiU&ZGPUDIYrlaH$-qnk1$38oP8eaTF>c1;xa4-VJ3;A2soPYQyW9bf{QJb6~2g% z3<=?Mkj=`!pt8IIcxYz%--KbP-f_@^lqQH~Up}{?fbRnj3MjibiO=m?_pRA&igT1o}SHr190cV+8%V zAvoSf<+-jg%~W5@i_j0sDoyG}zeua~n~rJ)eQb4b#av4c?vIDBQ~75B!HCbH}DjL$%5mxJUXfv)Zh6dLw*{zAvCy zN!3U^RMw6rMj!rAr(Hgug-e7!%OlzREIgIpm70r$pYeCConh;lBf4XVcTo?l|7+I? zJ)X_xVJVNwW`-gYeliKfH1H8d-qZNXSMW5ApJ16;nnUAW5Q#J@aZ~Pp1uy2{ent^5 z;`j8?DoaPd01T56qkkFE5iN4#kzr*J4D@@~NDIJ4CLR#;RPG+Yw^quNoQp~I56*pFL zJ{0co<2ga83eDeGU2azml{q`@)qj3#Dqk=1l5^Xo1>LfDK9()}1-(yFy_m9Cp+sFrDY+UT%H?Ef(6$h--q2A`k}OZnnS7tG#u?0grxFJf?#W1L7pGJ{#BbeqCgjrL z$~IO__PRxw!&yCd0(KU9%QP~A%?molPYxP}{8&(^K9oeYK7O)o?|_6+Fu(b#-A6u~ zv#z}p4NOA0)=#958-_51d`NNae$xtrKZqYKcr8EJc>+(aQg(V9Dv+r0d%(wQiew03 zJlu3!%4q(4v6l#exmblNsdX%hxC4VgXK?s-1=4iyQ9gEH$S%(RhQSl)5rlg<57*Db zohVVimK`o7(=@FQ-@S!$a)=vGO3Y+`#E&HsYw$k8goP4uIsbcEWIU^c$Ro)0XyC>} zjlhkeOv_E{lW#=lZO89J3E#>KK~^__c`IRM{5#$liv1$T4tL1$?gNBFbU3etkqPHYX$=ed20%$0ezvULN(Y*}kI+m1VasG%crjSm{c3#2#r{4}^bEZlXRf!#H6~iV}&cq(=2>3+Am%&{lSLOLR$rw2M!O zxVxq}mo(PFxs^;K``0`ba3eu%!mN{ice4}wyXcK&7!mWsD}%*_$-o(_l7Cqjea-gX zih=zr1+EE*lGMqn*?#1vxL5B}i$LMVHAyskNrlDtE(SZR?}>qZoUApk zDpOnZP)(^d;)vIH`@I@ZGv{^zn)R*iv)ctX>%5(Rzp~d3j-p7L_cFSAM_TR4i(`d0U~Cm%H_%p39C*(D9c@ zr*M^kRMh+mo|enzeqbBBdt)Ps^ymZ3-dUi$99vzDq&1x91xkvU)WhLB=9po`OUPH3 zuYZ{Pk*&ivx&~vPez&T^y^L>feR%XigI!#b0Mln4s2QhtZYr%QWbcOAX0lzrQdc)y z%l-XOv!QeT24joxy066)ex-bg(u_bMXR5)2g0D)n&3UAM37AFv_KSjdrp<{3A0Puv zqe50NjecQ1`Dc?jZNPRj%Gx_eP{1sYX%2TUG&}opRD~vH0$k#P)@-K>#t@QxG0&pX za(I`H=s!LXW9ariv-qIZ!;X>YdotYO4p$)fqye^Y&V}ndb!8Cz+g#Ht*-*pBw|j&u zrtgpTiXZ0#k0@xqqov{r`xp(M3H1-{+?wRjQ^ry{{7!3t|&FS9hP}4BJ zYFrFH(Ly0cvyC2n6_m<+_mxIg%3`W9kr9o1v%D0@vvO4vxdtZN4;*lf`~F%liB%^f zR!;I%i4ru!y(|*H451P*cf!WNa&UKZ_D>YYt3vWM-TW!-)cgU3LETTP{Ty4dz-N=} zSrkbxAALC!h9}MAWFuhWr|+awLk^S&)_2Roo2@~*ei9I22t1K&Des#8&iZcANMboK z(#`!a%|9%jP0l4=He7pRW&2T3gf+H1 zOkXmnz@ciLBXJ<2dimk(F^+HB{p@8j&qmheobe&XVOsGkiS9akGmibH8)KK8+aE4e z$pp>!x70+NjQv%x@A#;&v$d?~A>&XySAbo>bIAMvy4j>B-sH>|x~*&Vc?!#<`p2{% zmj6wPMWsXt_`|-k7KtB}@kLn|{zPP9%Vv|gvNEW#u6sY%68;XRs~Wc z&g`U1!<&1`qz$|eFNoYheI2T{A+KYJl$6Mu=3kKizKwklqdt6RR|dOX?|G%ubh};# z072veLBS^#V|qc$PL~(%o#3lTlC!5SUb&2Khp)UGgVf(o?ui=uKhHZ&gSzT_>ANgRzw38xJ0cm*wV0h} zg#N;M@toEU85P4ij-RvVIW^4`2aW2|7)1BaoBd48H~AU7bzgtvlyTS7GjaCBf6F=o zPJ`5c#ckh~F3_Vi&=<4534cCPkhq3Vh#_*KJ6BOzMUpJ)b)Ik$@4_({L;Qdri=B+6 zHB2Oa!6!r#3++>GAX-l^p_&-IJILM5k570x=}6cxEm!iDBy6KQtau+W!p$qzh|qW< zdqoxa12r&_&M~8nPtbs@-{5f91##PX9Z*MrQ1@Zkbs&QS#xw&3c4~y3n!QlpMB;Ji zH!D$?&OgLPbN(OT2()r&>;EtO;Iq}(fv5fdq7TectALBZj5U~GNhGPdK#BdG{TL1* z!SC#ELi&4ez^dRozNFxw3L>zWpP-^H&!>u6{nz_(TZz3#^(RtokQYs=cMHjY{nID-}9a*AF3n+QziKLC0P@1n{rQinf2USnnt5K zU*4iR-Sbao%dqS8^lcO3tf{b@pF6HD9K<`E{Oe zrYdtsaq|r0$0*~Tn8`T%nSaZMVLJvny_YkJp0v5eX4}BkAb&IXly5=aS)f0k5v}nu zApX(qaJ?8J6@HJ8kk9UZ6V1ShSDvLu++NhFkaU40nyld4uahA6Uw|*Oh$l5a^8HC_ zTnqh(%w&i*UkX)nfY}Ab`T!g+3~^^(LW8o{V4?+jOp<6d9|4cwa}KDZ4lLD8B8jV1R91#L?aX>!RBQyAgtpR_F$1(2|Yx zhMtd{;$<+}_M;mIUj+bzA6fqY_ddOZJMQU}TGty(BWi9$}C7d}E*IeSW|9^Lg_}pOz<( zJLf*%>zs3)>zw;On7eq1>_0v1Hb2rjuF#c$g5xGC3@P|lOJ6ZJjryb(@bymodU&%3 zJ55694d3^IS*L~_WxbjeNhV!EtEa==YNsxDP1YarKJU|V@Yqth8>Sv-^Uc704A*B%VWUo-O+t<`bkj^CWbPY;?YnbvpsyFm0}gg zw!{ZXF*~T%k-$p^Q`tH)i`ls9C>Echp|H8YWR?`A6tr29=Z=Y8vtzD_VbOUCGHMD; zCVp)m*YbIQkyENz>9VwswU_aUmx#*%$iQ{{#>>NZLQpI~DM(2fFAwjB(V3rJ4d9G# zAWC)s+w&}$lAcb+)a`lx45J`G4#mKPOo)o*1NJ1M_J#`mA&ZROH+bg4&kz(riw*PRzbWu@Xxm~eoLd5GE! zuWm1n4ts7_X!eGlIGnj^UzdhMNYRodnbiN`P_49Diu#73@UH)&au_mkz9qB>fiw2u zxiHrluWc!QbP%{36V{GXEMBQ8Kn*BSe`>&kCJK11*1-GukEJ47$hU+;s{RHQR znDhFRIZt2gfcc3roPt44Y9W8DE2wcUfL- zEub4QqGm;I5OPzat0#Yj&W&>E{xHUapPVICs+ecrh~#^;Zo7f3xo7mKiD*>o;YX~P zK9K`{gad7faIKi*#It`P_yK!*yK(rW4}Q%A$yM0UBH6$fa(wv&#V&&!5^4FM&vVq1 z0^CCswKJ;EbIQlz+SM-VmsX*Wl;zwbx2>q4GQ|ugTXE|-z`y6q`ht8GV+28y!;=cC ze<56(RUE%P9Nrr^;R7q;L$EK8A8Tjdx~2u_)JQ<}p^D`rb{6Lrd1mGQrT@i+fa5Au+GgTS zhf#9OIKNq-jH94Xq}}18saO(!J`@KWL{W!{r43bI4CStz#hBsR2z?zc$tH}wtumvjzhaEBJHrDBhc8m=>is6 z&bB*$_FEE%Dej(8!*n|qYR_RI>q%uA5e01e$jW+oK}mCh*W4!lt!-QWxOMLPqvm{} zzkCV#^;XW`2NJLi%9VQVvwg-+L7d0Rq0cIJ>ner(a#wUkOn1v&xtvYg4Q?i+|L{vf z{l|o7ElaEJF*07`Wq}3iNoNxqs*@#fdjm(zxEY=v9Dif8qb1&`%yieFmZquScpY=n z!<)2mzpEi-5Rw%GNVm5?5ctTin|j!DO-I$O=TU*J$GeSIoUT{Oi)s=D2O6sjH^HoRTCyWI%wjARG$M7B;!uHWfL8i3i#T=+Yll>x zhAU+QJfxj)aAtW8X!!cVBRAF-p5r9Ri&sQ$RH6{M`x38G!KBgM3$)iRu@Nv6P`suC z=^X^YtWY!zNW4#nyV-hoUb@}G0v!XGr2JA0T$P(O8h9LQhv{71+p4&>v*NFNHTNqw z7&;!2NMe&&J7(+WDWzrV=WnIJXN3gDEbr;HTj}O5j+*rse_p;~MRn`3wt0P0BJKfEp^7qDIS(D^ehJ*t8>}i-dvmO_1kl)+X<|@fh{kicUZm&8s|3t^mpda z8iz@Ci;a0sjcjGL)029GVm-~Y)adB?%|eLt#^How||-8p-crrnWZWcl!1-(1UN7cTjSXWSUa}YKfouR7Jq$5 zy+A$0){jI1GG6ac7jAfY16HXzAIx2wqwM$S&ljiG?VHQ4W7Yv)TU@G6H>f}ItoZ5Y zCnKNPzNtykd9>8Q1=WD>=uM%y59E^q?oG3^gda#{{k_^f^5pkBc1>n;7Y9{s#;mKf zoi9*jj-<@c(`fx9f7VQT=u(3ce3$Ts>v)L-wlO6@Q`wGF{u^_h%I=%eJ%tgcy%F~> zObBDVkB?`oQUH0XDV_J(J=9WdW*dGy!GUBc&X|OXff&(`a;6QthoHeL)(T8KM z(Ln(<)cF@Y$9z|~x;XcFOk*Ze!P>U*%;4uPX0-}|(0J2dGCtl*7izqI!8Vau{jHUXe+cp_vYg@-t zeF-eq6v~P%@=5q0ZDx`*d`BSgquBEl%_Gbp(j^S@cg9a|!^_hJukV1yrY$}(j!nXs z-)C3hl1@zD!5JHRwBVMP-u#Shn|Xq>gCiLMj1iGRy9kb2htxaJ6rUnCv= zfz5fCP=K%4{_!06BD~~2JVF0*mksA0yVwGaTkHemLr*zdHV^K=kHg8obIsOqylm^; zsYqN;CRrp6ji?f{UDsD86oMv}0>;xuzB|jY_BNPiWw3U=>gx-W4a;~*e?e*&7p%`r z^r$|za(h5!Th~W9;uDQULD?`l%L8Fy%b zXVF3`1yV(FZNL+V$w9JAeoDIhDA4u|JWHoS5~Vd~JpCA_P+z-A!>*y0zX_U)rMQOo zwTlAS-4>i03?OMq#IgV8Y-tUHDUk41@JtNb?_fg7rN+mwr@!oOjY>W^6Bwlof9sgq zAy=_2*ldJAGrTFh;Wg5=gKa3V8a*RZkT{8iM+i3gks0gYFZo|O=-Fd#aVd<42Uyl` zVTi|&ql`R-+|5P<4E@_zOIrG?mpIWhDU1A7YCORB#G$F4N9KkoDKlv598W4;y-Q%H zxb(+Ht-c*#-*k7G{w+P`q(q=YPS1Otmx;a~|MG2h*RP%P=m`T0t~Sbfoa-CyICu6- z^!MjqY*Qs3BXlKSzsLj1Y)LE88(YU|z6DQc_GoM_>tdL!?@3Cf)UL2@$;OV1LLE^?x@K!;abES7CY(acicXrUHm8K34zzM_`#7s5w=5e$2C&tl4K{-R>6 zqvLS;g;#kwmOCXn)CjC(dyo)dL|0i{+kLhc8iC2jHJtP~b#5odI0;5frUVqi_s+7X zXzpbu;TCn_=$FUE%sh&c@jXG|hsJU7$NV)KkKhIa0RAd#aF#m zxIqU;TQ^VlZMB2)ULgNH>*ix0E44w zVFfjNWdZilT76)z8{ z)I2>fBBXr6P+#~&WWcHIoU6Zg-r8kHYS{d2*kk12+=VNZimz7>PpE+47~R@|!wGCY zq^^#l1jsTs;U%IR49^K;x}y0qi$Zw$G!zcVF=}?cmXrwTB5md%wN!-(=ab@)217#{ zw&JQb!*`OYnomF&r}T*334yvjhJ6_6u?{S!;^qyf+kj-RevL5q2}Ega@Gx-wjmtbz zQPCaM1KJ0Zx+Ic>E_IM`9cfZXs_)b`FN(ri3wRh;nVf@X9 zlnrDL*xTi+Y~Lx-gMx}XqBx?P({K?drrUAAN|{pDDQu_Tli_4J7*MKZe6Ilb|N0qM zWQ^1n7bpf)>p1qh!Spg-BGDnxOicGl4W$%_aIN|b!4OO6>zKg#n$9c4w8_GuSmk!eg)}mrNuk^3Xc)=uR(?^CA?OTtEB|IUrvu3ud3CY9Yq9h9Iek+I_`=(~C}D`-H^XZ`goIF^ zaGVeOsV$Lx3BRUK+R}KZlDcw+qGL4Ohij2tw04yLap@ke`qOj1Q=fpspS$)ar3Ctw zwuyG)iszwZfD*fL4ySMuXv2=nCPX6v#L6^7%uw@HgvPM`zk&C^p0*sB?}9`~m`EWC z7IK5Ly24%7;*4L$ciAQ*Eg1+K!CU_N7{j7@uvWs<`Eq;Sb zxL_M`rOnM?dB(|0b1SStda=A+p`Rxh&87Q6!1zthckNl;>b{?LFw41GwzKYmRi9ztSui41g z7Ph9Mx&$)iOnlIOX49jiK-@Ip787s^ckVV2hs?p$Eb`%;VU_UeNpy!ibcSvtC zmQGL%(xgrt^mJc>nU$-eKBuPthC{j(Kru$4;-^AIW(+>aLVSbg5lv`NC(C?;S#oq+rpl7e&vke<=|AJcKs!OTD;Ggf>43iutn|4ppU^iw?Fs69YFmiQG>3uN?Dw)Xp>F8%}+~BT?oJ zT$JphIVjyAj@18SgY6#!-e`0x{hG+nUDCp!QEq_lXQSkR2gu(% zP)LQ6#~Cv8tK?JDG}aEe4@IEotgpvqrjtdsRVea>yuL)i0fNQ&1P$N$63M(w30Np* zf9_&Qzv^=xp*9Pq(O>vfXo$zk#(PvhU+se!kQ2W{Q zl4GQi)p5HkdI$Tm6MaZFk~;A5M7H3G=~NIVhpF$$qA)yvv+yi;}FBZaF z(4~MjKqUTq7N1;eD5W!3{YLjb%qM=lbtR0?-6gMzqTeEp@ z-lscTCklKCEnFpZ9mhxa;=Av&V{vXLr{6Ca?Zy~q0@RuC@Td&+jYEH%P*(HAqlaCLlUib{+H#fU1;l zZqjcfw)Ea-3DCR>mP&j}&~oXhNmtv@0dzb;?HF>D7ZboSH+iDMOW)*M zppwf4QN|S~fQ7Pfm!|WBD}J>qm`Eqbya4(|o>WqIH1_25 z46ibfe>u-KnRw_g9{B#lDg)f<&dU7@tG_%pnG14JnBr>PDY!*;?!4nP>(z@=vR4S@ZL?4k$c48HXdYkQzoHB*LSt*5N{>N4z=?NXYjYg%$pnhfiQY7$0qZuS(BG34FS`^Ge@&9lpXhYVa zQr|O;zcp2gvT~NRv23OYmjq{zipr?RHma^2i9P|tA2{K+m)Trqia3-|=PHKTP5Lrd z^w;p-ewTlT^3+{zagw`#y)v#Wa@X?Fnx%7$x9N=|-iH*{$<7Wur+SXcfEn<#yf5EX z1$48x=fuENArFeIKu8ZKk4A|<8^@0+p6H;&CqPu}fDWvyy1eqlPUwEO<0MZ^f5BCQ zQ`Po=z^wxAN#SWeCy zN!-=4DeGG13qcW4RSJK|j)mD>EdxCjB}MA?PrVN_kHt|EKzHeK#DN3^lXflL@^FH% zX75`u&#N_q-F3DGK0zW`&i)lLQB%cYteO%%(e{(a_48e_kxP|c80|3+b9}Sx+U+tt ztOO{EZD+;ejE$xrpizsDxG=NLuu$*gv{VRT=T?Peuq8%qshUL5Mi`Dj0@LbQ&zrCqndxEp17zr1{ zI3X5|x_6G00@=l)b}=mEucd1ge_3B2$f>q9<0bCH3WfkR>X#O&ZycCRbx&4%G4R?8)0NCXibv4E|JO`& z2V6v3D$!N~88;hnpI0ysNE9qdgF>A34Upt+LDe0i9n0HF2o1g($_mIVc&w0GLJXUY z5-JvwC4}Fzi-s@Kd3A(b+HXKGbvo4diGo@^!cBZOtJnoFD>k#RyGw?YfQ>ZmJ`(SZ&?kBBR z*LT3?I{5H^&)VROF2OhW>46|SbVZFqh%!Sj;$Dy@DjpFs@R*0TJ;@cSK~Dq9wlt}# zi^XsXqvMV3viAww1=^6R-UiMVJB-jF&78ikN7X2*GbXEDvq|(>-k8??e;R6c+rEWKz!adR42nb`aM7()aUM6W+*_mI6!ux zI#UOwm9uZ@z?@A#LB?6atv_TBaefeaX@$r>3J=dv!AtfUv2EkX}4bi2@bLt1TjTUGTPc0NM>SL89q+ z5_=Y<0v9wf5savmj}}tGgLD{j3x8=e}MKg7dioK zcrgm#s5d}jD9r|P%g6?V?AR(1wgEud0=W`~eQE_Jr#JqQgAHk;PTwdFIEJAFg1JCA z%Zuu;WgC{Is5;RaqZ+)H)3iE2WTY zme6dyh{%oI5%vlZ9YeiF7Z1F&?!w1=B4cW1&(3&7R7@dLz zBMQ>KCx5KCv;i9mJAkNFB|QB%IAs&B&?FcOuYDiF)#s?)e0uY-xDMn@n)}eOTzJ(C zjsG|&5|^1thB)+ur}%?m4&v@qr-5Z{hQqhg4lyinD2^bCS`sP~k*t>-Y_l5yC729` zT0!df8t{&R-qqXr>EE!2coY@Fvk#U7cviY*MUgaz;!Yp8$l`37vq)eU@n|ecK(_k% zbRGr6ESVKhW%ozjo9J9oR>kSG+5#2{T2VE>T=jwB)G+bjKZB;|I%WykN9yDZM)q4w zJ!1S?OFBjGbsxT;bWFhKI1Mk}@fmVTNri-h+Q2T(>0M$1GxVInYH7Gk#f{an!2PvQ zoo~erz`fd~wQj2E&COMr9e8?*^*i{_9SKhnXYrfhg%7ohn4?n??1#HFMPMePb;n zZ>K-Yd!(ekQ?p#`jpxv$QOx`0v&M%3okVM#T}UR|=K4!%qs@f~9r?XZ!Q8>(amR8m zj-whlypKJQDc`zZsmORk0y9Sd!~%J%7N8{Or2!2ma_H*k z*(z`I9?8`Y;Dq6`6NC?7;hp~FekA#h(mZwhFWUKPMNH}G(7emP`_4XK`q{bHb6&oT z_gvaqRC8{GW+IarW4}-6w|hSq36gK?oxiPKUU}AdWXM~=yt!7cEW38Kdns$zXpL{d zrC+M|!G$yuB2G;g!Gx+ufcrKKqjDE)bf8;;g^AegJ!s#I z!TO0wF?;yg*mNnWy>~cImk{Ij>_E9OoGmzG1Mbj!Xp3iup_~-Mq~>4ZlAsNDntWpV zE$7w_q+sK5N)%_{A2fvj+WJ_Dy$iKkG9(+9Q>=pM^>`);izkx!NnRr8bx0+ zCOt7t{$)W=+OXz&_)d>%{uZ-TFFoakkIWbK@4Ojj<_LY-Bg8X0Tw--zY)j~v-3v%1 z{p>f*`P1U8ysUW+r2yBWs0ef#NXn&)jFGKK-qOA#N(rtl+QHh6%rEnmfm1j!4Z~R{ zrfXnUsKMFT1J(=TDJ*;8y&U_P80(0Hm76_mnxtxcds)nU?<6!6@x1UWoa$0+=Kdd_{AzE zOv2oz>HCp+s{~=4$!B8O>p`r2U7u;qWX_D@V2J;$T!D^Xj+B+*=<7L!<2+Z|uw2gS z+pn%NU^5m%ZaK(Wzh`s%!t$MoU2q{5A!c~nXf_R>XbU^wbbYL&*zfQ`@=T{YRv~?{ zMjnn7AK9WK%x22MQ;Dn}GVdvz^?KKaG^>h7oT?2w_Cu2vs#(C{C0B}Fgg|t&mI+#i zs`O4=(P7{SI7rbefEg_0M?}Dt!wMKW%8U|6n&5}+U-?eJ|G+E!p9$OsEN~{6XH@J2 z1#~s@5Y`@sNvl1FI0|I^W1lr`<}TI_9Ab8LRjp?;u1x1ic3UJUxhGX9Eq23ZxAxiQ z5vsqWHJAsLBI^qbs>kHno>g0lqvW>A3!}2n^fmIiwGyMD?@-N8h-~Xv`eFB2gD;_@ zKnW4xo7q8l?MDvwKUVAltW^P0F&CdmVQWAb%j!~P#wpL(&UD}f)9d)u$76950F-zS zXNn!d&CfWs61Y(LQ)ovkH0)NdP;_1(3_`X6@>2hsTqc1-{gqLrB=DQsz2kX*;NdE5xUrpdg3U;BmmW7C9rsY~B8B8;wTswtw ztuFVj1068eO@9I5E2D%WWJj<$8ka)RoW8{tTsDjHs+7TE&`{|{i(%BCOtDitsrfD=_qeKKMYrdW8VEz)e0G1eLkYl zrX$vTtXu>YuR;RrhL<;(uEvw)GWd#za*-nLCGEcslRz1h)DZTn&cqmg)}B`atgOes9`t1%W%&OVX4Jdi zK&LPP=mQb4hK0oVo>Ly6W-mi}G7zX%^U95J-wx@h!73MxG-a7_6IEPKk2$!Hpv5E2 zxfc#PNjIptR58-&ME{-v!c%v8#XcfjCQEaVXkmjk7Lg>nz?ngJ)vH4sb$Es!W_wO0_=Z}~_U zfj$q1mM_3m#(W~Q+RoU4)RE^tGu>!O8(>ZxTLF@1qBl}l$TiZ@I%W8}-<0(V-iUa5 zyJZwbcV6xYBwEjE<7UKs6b)*-wEd^fSm+V~Z(HZt6l079>}6K-AX%nJAIKVt66jRMVR#_Jt^+y>$k z*fEVXzZivQ$U8haVCeVmyyGn|h6lGSd%dAW(MWHAKGEtM!A| z>C{&;bO@Q#tND3P z#onaC(JgmCCTfaiQG!2lq!IB!qNm;<%snIP)d)-!t}HiEMvQ_p@|e# z$0^_A&A~|j*hV>6q4dTRbp3fS!1YZ1{NHrrY3R`d%?;!Fo?osX^J)eeizz_z+j@vK zA)A(Jcp-;3IK2de(+)g;=@f!&>ak7z*riba06rTiNg2c<0BAULI=r&9Daib@G55dlT>Sw%~9=TZgPsUjS?Wu+zDCD&pIcj3m}icOGL6iLEbe2 z@l%RRKu`LBM5MJs28|8o9{HU)LX#r?!egLACuDtL%_4ltb8lW0XXM5BTF8#g1PZk0 z^GJANZ+u5H?+A?neNSA}Mgs}DI?>gjU<5u=6>)EHU8xCpTiDzG7`DE(q)BZa&B9gK ziXY89gdT^`JNFG|+Veeq9-&^z2Ncd1kcf@X3ML)%S@~!znV{eE zRj_7dU!Co?Mj?LRF{1!wzw(N?=TLvzo`vn7?;0bsrx#bfd?3 z`W_103bHVezj1pAl6wVCE}_TOzz6)6hhj33ulv4(KVU;^!z&k_IGn*KioC7@v+`JU zC5&Zmu9+}Ec3651g}JU|sRq;RTV$#o`5x~IvWL?ukPx+6;st>?B&H?Mc- z(o^hepgmc41>o1!2ec2;1>0SI}Y@A^6JvueepNc&*gviclc218dYHfGx!yB$KUFt=<4nt2mSGtwmK zqeJBXC*>|RK8ZaEX7MP$&+bP)llTDEiES+NbR_|yBFikG!MA^3hp#8!Ju{RNC99UN zzQM2FMimR8cf`jutigwb#~~ro2%I+^P?EqhQoxmUbhiGbF2H?=f+G-l>PW#n}R+Mc5%@ul& z_FwWx|Lv=0#GDM8de45$0^=w?VyhaLcuHIw?K?n!C+im-&zE>C%DFyduE#odpxQto zqjBMsL-e-!sZyH6f>QTRkh$*9cD9=;1H5D0f=wek@e%}a_>Y-Zs3R$wn#?ZBilw9c zEq;qjG%D5{gk^qRm5~!GxW?nE{8H%h2@UDUTWhHGEVTYZ`(yI()|HMlpq` z$6ADX(BfIYFTRta=4p$|;!{#6sXfc}YW=O1GOwx}$(t++uUC*|%JKYN5xJM3awssr z7%p@0(?8YC+Jvq_d-TyFx5A_sy#kbWU?D+^exOMAfM+KF&kP6bbeOd7F-n89T5{p0 zi#To@mes<&-_tmSTzk~Ujm{s5PU&fOvNgWbrR+%g(13?1HYi=YM4{czQq za4C(XhEp)pqrck+{OODf9^_f`C#M^6Zui2RvHcUaFzU(PHFergBnL1OHVROn78{2q zn8GPpwN$9AfOmm4jv6g8i(`O8l~oHtKgzKVAorMor559YP`~M(N5y!4R8?Lz)f@H* zh7O*Ev>BFP0^d9WnesqZKDbf)15j1*c7Q>GTm{xy^@U9a$iA4xPYr$-q{SoIc&I)+ zyeP6TI=H_@8hEaap5Ls~Z&xuBY8}8Op*XI=6i}5eC?55l94}PyUFd@fO9>gLYkr|~ zCROmIQPDkco|}$;SNky?$qqehc8LCJ|L$JPpEt`ET4Q}!QtIO<1(c6FIh})ZJC$RH z*oyRr5wfsXLqo>bmSW6;Bq#TG=3QtIGU4eBA#)%g2Ms)INP&`AI7kW2rbv0YeAAaI z(zYop69jEM!*H2Ud?psd;w3;AHyCq(SPu&y;GsEKqZw>YcY)txFUhrggE9^72V=2I z$i)I=R*GRr6!S6vU-zk}KNCGy;6@Qo zr?xt+YFDV)(gh<9fJC71c(w0Rn%>1OrT}hU2juq}=7xqBDp!r)U-CIpsD42*R&DoC z@eaw73vrq`tMRyh4b=AM&GLnX_Cd~akQ{t0#t*X~qLk8C>TuuAdIVBhWPFK~64}K} zygY6DhRt6WYOViXPQ+FJQ~<8jj&no&DLlUdnu3pKcsX*WfrkOQ>A9EDGjr&u{bfSc zEQT}oo_EHUyeYyc{$q{%5K$pttcjMj9{(_?*PhSYph4{IG!9V_Lkiq)Cd%L;6KEeN z03mI>OdQ9*mL#L=1GPQ$u~!gazvclpFf1y_ku-R-HH+WOa65)`W z;o52y%=q?|3&^HQSU>_qOHIk?o89SAAhld*0%nSSRz$9RAb$6Goc5c*Zj=ML{8Qy} zzS6F+V!r5}ls|6-(k5R89+;q6f-XdceFa!&7zhP1w+lYsd^2AtbO@q=&jJbL7i))} z@1d^@D+3d}&r_roSFuu z9{Ne-XyzB~@$_^A*S*42n4dhFS-KyUtF>;Zt2&hAX69_aK6Te;m^UjICw$%M$woz{Wj_oedl*hKByXj@U$5u;i_lEpx#+dfY zw%)X9M5KWpnMa zS9JU{xP=@2_G$JF75D5--tSn=7e`o1tk!&N5@GYuzs+b)k?7WzG=6@_(c;S>@n8(O zsNi@**UhmFq4RkqtDg-P5Hw?sZCYtM8Rgzy@Qt@;mHGK|4O#9i&oos+OGNmFf$nV9 zdZ!wBPv?g81R?WaE%h5|^>mecWTIn2!WA$5g-XtwCZGmJn~co};p7mTBhQznnsz8U z$pHyj8!MUCtqN@cEF=a^^+q4oi603)?)kmy$fdt_kHLpWn`tkf(-Jdv*(1)EwQWGu zQpns6U=)j%)P^L(vChrhO$_laFeq50;;EUQ;g%t|M_g&J{%3JUr*GR3Hdp&)4mJ_{ zcC44Zn8_@3^{ToVXF5^z)U{Q+_-}6hOTQ1a6Xh~+K_UXXIp&y8Kr2$@;nO33S$Du8bACIyMBFGkJ2E!p{ z5zXXjL5^MaUv8f-Q7*^oUx19(~!RA?6nc2dDwG5 zC-g6;WzrK(w50Bu^7#+4IX0|JGo7|s3(;u1;4?OjvtMkh{El)}lcl27?CiQcz25$~ zl*$QAhS+J5*K=_xk+U~huh&R0Ehu+s+WOWeT6V1}aLkT1w6r7%4y`yTb z1GfC2^y|okgyDo0Km0%KCGK9>wvootBmu{UzGQxj6D?%8weUq*@iX7S@64O_oN-$Y3)(HR8Lu% zjb~7Ev8h~CS=T^%d#!VO%+@_lIVU=Z-%HlOz1Y*b<`bvLABZl|hjfiQ$db|z!~9=m z3>&`AY+oE1s~%e)E#|}~1bg?_yX%o=>Vtho>Vqo+2YZx!w_-FOv*v%k_E?BpYd!Zf z;*N6OUbkVaSiH2g(8}|3|VMcoO$9_D|+ zpSzPK86kMxX5k|zF0E$|sHPa-ZvwBKEcw@Ki77~-;P`qyJuzt+q~9ap`;+8!I`3X& zJuc03D(kfO!Wi;mD(WHsc#RFAFN2xaLC(mKyJp8_8Ma6xTAb)!n(j9+v}JeI&a^PI zb#--zdNGT@!J#b!AO2xU0#=do(J>t8C=#}5s?;HwMrbXz0tSpH(djz{)BPni0V}-O zkxL)?M~ro>Iy!TT()9A3$Cr(Co=25Ul;+!hwVuw)-mTG+a(LnAgoRoApN=fNW-OJ7n{34Y$(P5dPxi0+!%}ltXw*Q>tbb7+?DaN&v7KWJ2W@}m3s-UR*Bs|P@SC+2J*&N+ z$9-zG)cdkR^;4Z+;JM@bIXX$F!(1h7D*ZIp7=G>9T;Zr9&zV76*AkgJxnrE8iOV>Sd{G*}!(|YLjV&Z=|nO9Ium@$5@heD*eX<`lVjcAfD*R%;q2mOs+ z1eN+V4gC=9-Vy=M6!|%y<4@}mA1XjF*lXn@XtAxQ-K#89R6nftLGEz@MS|-K zTV?edtd4^at)qig-8z~^)~Bee@1wK}5A3hWo14GaF``BIL=bml&0M%Ty_fsUo)c`R zRP5mW&A|UPsz$@Uq|q-5e%Be-O8i{v|Bm@y)IR4{hyC}G%{N^V_gs^hi&o^nhc2X~ zplGR#vtK`qvAwvc_*LxFpNs7O@s&C$-seXQImvz}?0Ca45R5DE` zeQjpwht_{2U)Wj&p7+{PtGEql8!2m>)u;e%unvQR0OELI%JAXMZldCEO@`bx+HpeGU)t*u+;{q9dh~%X=eExIgrJl2 z8l1+i_2*3a%~?T?ONUi+&-I^@DY*Af&dr9Yo8$7zlbWMS=QjV0V)b};rl}odL7NM+ zmnv8qX#|4KUs{=dC%KKBR!%RcoYhaud|6(yRFn|Bp21qCZHwzZ1q!;u`Knqsueo!d zrBsXAnUuX+V$=rSxCv)bT@Y;B+kii&6d9aK^5#k1_!>NKn4 z`T(1zs}Yck_0xG@Ey2gs?sQoo%JVSKcTi|$HLX1z4w&nJLv2MM3ljUpRZ&Jryl%PY zA=hJc2n*3D(r@;UtUT)}kCJ!BN?|_a7#lE)-z6^>C$?pxlnCChXKh#J>a}s(*V-mN zPxk2LW&qx*XAlN9`a9LY831_hw-@$lpVFUQtd>+#hw`c>kJNdc^x;XeLF<+X^{1|# zf35qC4N=kjcZFeP?6K1N7JY$(rLdeXEb%g8lN+az$>yZ=kaYTM_|zqeu{6R{j)Z*p2?Z1;=d zm#m#M1-hUZb@O&_XD&HqqQg$Ra8s+spsDd5@uPNTT?NHNuPu5Bht-MrvhygCEss%I zS7;Lw;u}gPN5R$Y5W9y72wIBZxY1h7bn*xb;kt*ipj8lvW7Kw2Hz z0>WX#*m@_ObiW`{Q>)nA8|13js_&uP)`HkVP=&O0ueh>0)@wu-7iYXF=A8-1@80=l zVhlfDH|}#5LDeSd2cBiQIZb?W4+qV{$mD2urwBnJeZ`+!53XAF%~cm$4Cv!-4eO&r z;d|-b=D}k>4nNQKHPPxXR38XwlFt2lA4-nvaQjB(-&b@;{K~;1 z7rAGLI|Wn%d}1H0`b2D1ZbVR)@L-(nZyww%b)uG5+3@$>5{fYN`>2J`0%e&0 z;j(=)HG^+CSB~q;Juy~YU4iQF-4t!nIkAn~%zBV4MLceE7~s<4H&rt?y2M{v1xu~1 zAm)W~5iAJD*~5gZ(WR2KI$`hGDp2o3Ec+K-Ul+~q8R=^U5BKqy1qY6txd^`np1KH& zBoDY`uBH2VaNly=szB5UWrEb25BGJ`4!LltxbCRiHvXpE=%yXDkL+kN$x^NU`>81n zoti=zajx{MUefqenwv<1(%VJV&jR#4Hm{GXU3{MC8z5eO!HemvcCqGU&rGy{SG#+d z%Zlp!z;(;)AKV-V5BkL;hrR##nqBK;*X84L{aTK-CuH@)6P*qdfbC-hUAxr$?%=tH zRR97QZG2^O?=zkkCPY(06tvq03XCYeZ z`XK$E;7nW71k=DP+`ep(+hKCQAc#G?V9~rdN~O8I)IOvVw%WUJpX>c^Mle{WHPRQ& zGcAO#-JidH%e7kfBR1jln|CY)f>3&**(!^7r!dDxHE{OdoXCRF=XlO){nzT;(3ZJ6 z9u6pV72E4)E~grMkIacDo7`Nerj0bK-lG$fJMBHSMi$h)W*}mDG)6uuAIugSdR;B4 zt^QSoTCyigVfW>vv$q7Yr(~!dRd3Grub!*)$=uVHG#{-bm4NQ5|3s169_l@kEmxnV zVA^w9>y^6KU}i%`P`;l5_rYZ~ovh86g=A8Cw`pK7_ZX`Q{5UCXExp*;`sb`9qlWWjd^jm=5uwD*G5rzie(j8)XSdRj#B&dyDjFDqT+ldQ~WGEcWgqI0q*3)dy2I5I)!LsWjXWyPJybRUu!~&QSiiCQ9|Id}# zV9W8h4bq?Q{;$K@*?gQ0YG}CZXWS)hW~C3qHQ=+&L8|ktb7PuxzlEV&d%PUDb|}xX zwXp%{56nf0tc7(_(LmMOf}MgG`0F#j`LCq9{3dDVhf?{F$=V61y(texgpF22pstWk z->s&7{wkL5h(&U<+S#Xt9QzGXu9aW4Ty=BevT}~wgp&Pnb@b75bxaoRxPDfnJfO>} zH8-A<$l|B1o346sb-;bKV>V%}*IG;`(ri0ttNyAc%+nLC5=_bpihV7_vkJ&*-8O+^ zLqGJou390~di7G>6D-SVV+A4&Z7~0Z$PcO|5g{${(phmsiWxeXc zwP)$237F}au6Diu4K640CiK>bd9Ih#eO8}xo)}JCm<}}@EVL9O3t76&rwj79tt>`q zfs>XP4`Pi({tt+CvWGwHlBwP3HO8n`u~D52=QMb)X~18`3N=*CGWUqT{zJ{)i?Mwv zl9L6naL$c`yXde1m<&BcS zCxV2X+p@DuTsS>O+{#>qG$7nl(G#$~7`Twz5?vrn-Sf(8{&X*a%%yF(J zU5ghjA9iFHoi(cpe+8i#8;#?q6`1Nr2%bCVoT>fE>-y&kTDO(WA$UGpNEtUKWnyP{ z&Pjl-+?+FACF;Q_>t)<1MqWM4!`?4z@#$e!v@0n6fFMFZGUL(uLHgaL&V>>& zo{6)9Z7o+%08ag|`^`+6R}HE+XFRCX=6t_{S1E&+6rjv%4BtYW0;O0Pi> zP@0s0bQA?wDH@bsf(U3r?;R8cqclq>(nKO9G^KamIl;Z3|C>*Wl3y}s=9+8DnV~sx zW2Mwk=F!wkgVYZ=(G!Ju%H??78q&n#>Z?fp&ypdBPvLFr%>3a z43vuTRo>R5*7}T16)vi?>8xkz2d6d{42?4yXq;hOMy?I|Esjglk+0_{tgER4QLR4I z-E6Jysd}c+%wLxYIi7!%tRNQq?fyGV?NoVQdFGP)$Cl$Na>{wr?e-06U}v>vu*)g% z+icn5*bZ6*siAC4hv_c_G%3B|F0fq7ltkS`l%2p3PcM#z%wE6oaoM2@%~UGC(2ra%d9vk6JUEy{8lu|9un179B12DovCWX zgGuH}MI-WJfpB{ipOu|xsEB<|tf{FkPg7{Nwa_<uvw;Nn2}vANfi1}0z)xf;n8+o-I1Ryw>NwLE2HNIv>xReQ9(DLORn z{sb1Ux;kWmGJ7TPm(M0ZIteZISq}{-uCsGMCqkyPOS4rLhtK$p*G)*{uiTwC*Hh=e zfOzxmuqle#=pc61Jj1Z>#h_OFQnPwbNl;1VjORhq>iad4ov)J}jVCi-=hCzqHh+KR zbeEp*I7GTpsV3XMQ_0y!Gd4vZecn~i3$shtAayYK@+8ATKfrv|=;IxeO_(op#d`dm z^5#|3E!vsdIeaD4?dk?0T+f5P%}q1T&$^|m1y(F%v0gL!gA}5A2591$%BS$RNp9z6 z9g!&r$2SXeU~&(e>ncePrAWm4KVSN4dU)GOvzTpjZT316!Yu2&7cxD1FC+6tsj2G{{WRc9P4d2@Pph1A?s@<$0<00dqUZ2UT$LtT6tGVv} z#Y^Ab@2NF2y;NoCzC|xl2_HzB4PyWjMYW1hu_(FTA;F*JZ;$F5sX#!%BYq{n-_)2E ziM{~5G~h=1G8(IPLbl;oZ@pyEEBm_(o)HWnK%{NhtS3y~U5&s(Ezr_+*Vq z+SYr=@}~|NwO{VeA2flwOkn5KFiRb5K2q6y^&g;N98@T0C3b@Cwbd81x>=7I)6`3} z0h+CWxRdvo*LW`8&{xH{s&eFcK<(I#GeuVJTm0Nez!ch=cAVurkupkcXCrOhswSxp z@rQ#3oZ;b6Q~EKC8T>%s;HrUXR?6{ysB8Z~R~ib%gnx!&>`IUllGk38K~Ppexf<}V zgJ8OMrVY{2f?@oK^3{I*c`=H`mnyK$_<1uoifz3}qexzCR$B&FWQ)zG*QP%gTSHL>b8)MgXlDYYZ*sQe~OmCbO3qmuRW95CdCT zzsafh*BU@bilAJq;a|RRDVtY;jIic@p!99859(Q<{NSqoH!3d#>24PxnB0@gn<2A0 zwkfJd5E5M)2he^z3`Q2vZSD?GD?QDVP*5}1^s3X~G7x_y9)X$wQ-X(=C$xJitP zfxaAQ%|whXDir`lY!GErM2YasBnA9sN`{=-=uZ8CU{+D8fFZ94ax32oX5Wr3M@E>R z!du1cwlLwZ@CN~1y)?_T8r>8UE*7h$6VUqWYII2Qr*tjeS*)<9G*M^5X+3ZEF1grl zmlKJkj@=>Sz0z}W#(K@6C(&olteVW?Q^w!wcgpPjM=7H`V0M5J5_|yY!>2h`9mAeF zWB~ot)Fj=iXWk~HPLR@06~om}NJ`B-SXDi7Aco}&N$)pl*!@%GiXfz}^6AnT(jpkg z12z|ArU*#v&c=FhEF-1~yqojqp|V(exxyM?sO1L^e8$p`>ZDoMNuVtXiSYmLd^?3O zji_vfhhH90MBoa44Ol%(f+XI7da$}}c#x>uw_S|=|4%$K*%1~WYJ3OZzUQf18GlUb z6oq`#xp->E;Y-3;#d2#|Kl*C;r~;o?4Y=f8VzHT*b4wN1-L9etK8^HQC1iX`dd}G7 zf3k@fpd|y9y)a3uuEJ+Sd8zH%@|z=Yq<8FTH8lS+Zx2!n>xEW?qlKQIL_<+-iGTG{ zFJRL8WFn&mg7pxC2wv=qIDGk1@cTU)!`Q{xZJ6A~>@0IB{AQ1V1y%?oLy7Hjt46zg z>VT`mtM+U~8IZv(ZfID>iDC_4CEvrJbAi(ZjizMVdn<8d7WqJ)zk@!+$c%>K7Wyq> zvMCa>RTAo|2iUV%zqq=F+4Yc zDc%~Skz9NX|B!B4}$>*dUR!FsqNS{kWs;zCtipr@8Jod=k4Fe*m z)$AB0D)s5z(xBFRj89u{1fHVqn(t=WDX3|OgmS?Om z1|wBqxQvkpn!h*RxH*1T=$tdd0{MMnsR_GeT;XCkdkM+vzBVGf43#aGs6 zbox)vd)oqEljy^ksFzpS@O~g>el9bFtym*ZAyt+Mo*w-|u8!%!*VUzyh^fJgIN?WG zsz6yE=WM9{U*>RpUJd@0O?@-7*r_zvF7I!%t3TA0l>no5I8)tn_#ok34~>MVTG$iKJR!{8==pk;H8f63AsD^E*YX88$=6B=5lkT5`_e89KhYQp#TtW>Z>HeAP}J6TSPl8OMv?Y2dEyBTY%T^V zA~!Hyfbwcf_gCAXQ0)Z)5-*hO03z+}QJuZkN7>~n|3c&AROw*(`8fP!@caD^g=(_x z1>re$C82=jsFdTs@b7Ncuo@S4V*VGQWqD$lrjY01LZ_U96!CtZmCiisQ=T!TPaUT$ z43w_AZyB9%rV1NGWcTVi^2P(cOAoaHA-Dp&$aZIGQ>&|&7_2IWRvn9&PzTL55Y=yn zOjiR)iS}6re{0oa4hoF!db5KlQ_~nqn0qmQIRMn{mJ!w50r*B4h%AHw_Nbt|*`H-Ox3zQeQyU(MBnki2I!H)#2xaMI^r zL$EUVt%mlIbS$TWmXloDh{kTonfS@iRI*JjFFcEAm8Gi!?Lnm3UY0x~T0h`qDUhha zCaA#cs+cO};VZojKi>b)Yw9P%k1sf^?d0u}vv-x4IZZ7A2){%V2F$EU&{gt*t_|2? z*Mri6Hg7AaHQ?MTe#|mPz|0;XNQ&0pC{kliV1bFl!Y)j1#|fvm-foIesm}Emg8=;{ z9*#bDX2J-IT4H8K*l8@xnDqZ&bsQNv@Jvg^bm&sKyX*S-*+(da0<~c9MW5N_zzd|h ztS+CX6+Z(dHS*X}Uyy!qv{->&Zsg0GwsDcg` z^M6)Jq@HJ|3~eK5Q2gCI40+6FtT70CVWcToNmS!pH*O0i(9Gu|BAH_^p|}Z^nj>J1 zM6pC!HU?u1@q-1O2cZv+m}e#du72po@9?PA8x}3zUSEcRAuP(j@^+a(lQ5ySQc_oa zoe!lMK!p0{mwF^nD^dM$;I8W)MN-f^`GdL=YIaT}@>(x!!QmDwWi71@*Cq2MN;U%F z78j?Db6t;WerUpirj?+oGks|ST%SKc;_efl?@E8_r9cLXMoJ?|NephhrBtf4EbV;U z3#X{&VK05RB6wzZ^Cm`1=!sBqU?3ZF0m6w*W~P1yv(H%KoF(uPr4}4!GZ+yD7Uozz zr6g``-jJG4;qsIuQVaggo+UAC;gz&Wud(AwQ`6yxKzk5Gzi`s@2q&$4sFPi9b*}cI zy^VYC0UtInuUbIvg$2wRU9Z9yd7BkkA#ZjfYn;JJGtlcZS1QF9d)v&qF)VZqBJKs9 z{C=Mi3DEm{yskJu_B7mN2bZf1og1$ZPl7xOpif~k)#(s2v3F(8d*9iPqF7`DVlaVe z1tcJUbT&uQ3F9IjKb4M{Hvd6Da!aM_$i`LDe_0#QpoN3FnoWxo{y@>4xF^1gY z_T^_~$8zdu5HB05L+Z&0n^`|O?Pk6r{&*u^t2N$nlx`0U_=nCPTvr_C>dR9-hs?L3?z? zo`d+{N7W-Il^_`i)nklCe<2%jcCenH^2UJ=9N^_OT3U{_z03IN?=2GM-&Wsf1^qxT zqNVRs@0Op8yX`$H>WjdE)h@Bw(REuGCDuz@YY{FdEr%nhuG1sE2ul^=?oZ-ula8Qq z$cBL*6A_7DYE$nmu}$>&I=s)2FOqJ?=_66r6H_Cu!nvuUgqq3Yh{#3^wokVQiu23k zZ&?*o@hoLL0&2r5tX4#SU+c`v;t&IM1D!1Ye2Cuo!G*Ohp~BF zeCA62h(=a1mciit*~yZzw`UCcTKU&Tr?Qn4p7;8BnR$&4|92 z@=o1$r`zb$UVoz=rbnU1h&d-iI-J%Ne>S=?Tey3^quF<6Re@Mgaxzy(D6)cU?gP`q zYOQ*uH>8oGm)3M$1vIF3Z1F7%xP6DMoPl^|lD^p~wgy=wKkxkq3RbUJF)R%#zrDB1 zx5on?DAZ`(2x#xsVBKQrsH?XcSU-I41+=*mK@fuxhC+lGUhy0tvH;M+Q~~g0+mIEx zjciyEruKs*WI9oc)&0>y)(4=)mFl|s5IX}Bp_pG00a2lH4jycF#|Z11*TGxp1Pym8 z^IBQ>uKj0YDPzyf5gquUrPUs8zAwa}4>>lM)3Xhre;Y!YXr5eo*Ctjrkq(iqX+>5q zY0*7B=$l(|&?rv>!=~h3PnLSZ4QrK|QcjNuj*?LAo1L<;-K5+Ip%UKF717|&femFU z`j2JnR^j=Wg3%Dj)U-6y_)JzKd@Df--VFysFNMYPBr~i5r2KkW^R$*w4J!iHuqqFK z!~K^N&wIi+-$G4{&cdt6NK-P$~3yDo0@2>;ty`Y~(rB_`uBV6hvVavg$%kV@r)c}Is35Li(|rA zQyuwY$iIuln$4>W@>PBR^(UX+Twp0xHcC_h$~ZW0GG_KB)L5@KR0Wagz|PrW);r{` z*D{yBvVtHh4UOV?sY};q-=-g|@2T)6&W&cAF#vr|_Q=K?!yu&x7_pj#Lj8`&9XZu0 zeQULC?|IZ#N1GyoE88gTis`KR1}jass!KULgIcLp*3hLPP9~B!*by2n`{opC2ucBL|GZ*h3*N z9IW%UxRd0>J6CVzXsg*?L+FyYHCuv;zQmIVewJy|V%S8@h=ZVx&s15Rw+xd1P{H{XYLJ|PG$twR2MIXr6( zobue;VrZVRb<3B@wKqYUEj>wQ%~PFe+IHo?eZCJJN+n!lm-8H+_g=t4muyt)q%Ndq zC_1hJ8O+3_fU5)j;q3vxt#Pp-B9J2Eq z5kAC-vX=L$mxz6uLKS?Ok@WHUm4J9g?DH5i6VlgESP=>bw>agG)f)P1)bR3 zSjaA#l}$fnMbk3$8f5(Xey~_i9$thKd$tBd#qcjVeW)p)wupMYW&@W%0uG_BLPwM_ zbDLCmJH4j>Skry5LJm`16d{yZTeAH-Q2@FCyd65wts8J)a1BD^#YVcZ!?;HgAlKcU z1eYR+$CXefaL{XH&j;-SCe%T3 zKDig#w%hd+PjN;7>A?`!e4z}cKTc)6qn7NRz}WN+!iTFht;mMhLI30H_k3on4zurX zYBiSd=YY4~q%qu1a|90`3LgH$G%W}rv<9^Vr7(ySt5)G6v1S$qiOTrRZJpt)0d)HR z<*YwEU7809#D?fn<=&Y}PD34Gy#|y``m$~{*_?-o>gm&) zHtpYYMm06~1$Wq6;s>mPTRC&XJbn*2q=m09PJMFr{gETb3ijST z^5^C=l4|=;c26%4d=5?#)0^*go0uP~vdAe)N(<=553P=w&`vGO`%H4$rGydub+M=I zm0)(%aKd9kF^?+_)#R zxQK(H=-#7dJx4@#%N>L6gk4rutVdGw**evg-oC;d;7udi3VJ$A^}i98LLb%XrL%#Vyc%=Uq8la|tP%rVwot|cOw zgf4IwzU0Rl!2p+d2{Kch2F)-xa)mEhT5TwAx>1YGpP9BCsygQbIrM6!>PxS5K@*q@aX+7(l%|*PQsex8s!rW-P&`vaG$lgzw z&M)X?OwJHq9KfkO+xbK?TnCoIZ*hMRKQ`(Z&orP3+(_6PId&2IUNS;x@dEk|Suke> zMuOFDNlIp-K@$?ZA0_uM<2tDu-e7p&yu8GIA0eZEfOqU{1Ub0B+{3<2DEh5}f7>@6 zBokPgq-wurqbZMZ1CVk#)RduRPohtIIaOfrTj)vc}z66da=fpk*@4geUD#9bP zN#3>V-zLx3%_K?KEQp4@Y~8Bma9@g7>L5CT!7kW_0s4|@PL_`JO#$&dl1JhZhTRnW zf}JV@ay_eO$R;~{xc%3alxZ(DmrHO42I>#oXdE{ZVC)2bIFc~&3`~)tw}zv0z!WW* z1SCa+rtLTN?wYUd$S~}Web(BGFw-qe=J(M~Osh?{7g6Z25#krE|H#3pY~z+^uwiv5 z78EDS{tEj4pX-cwU%ImE5c)HQ`?fmSw-DA(o}%H2+sA^MqKqG-Tl+a%N_cTs3l^Jyhw@p15d?(nz8(*ml%$=u$Y`VyOd3|nb zGknrE3**78oHwmo9X}u7uLy2L1el^^{?HochQ-fj<6uh4CYk0ZppX=|Z0)ILoR6Tq zGF=7daz{X?Fo4_#~ z@bkdA(&I>L!mR%j%cFT=7-?}@B;ld83+WYZEQSd;^A8QY#Mx5?VvP7?w_;@4mh$pU zuP^SNZcXK-UV7^=END@x;MGA^&loR~@aoUEc7IxKUFxV1pxx{7soHC4u1j`MEor5GpxjKwtKo2sf%rx;G%1Ni@c{6 z?Ave>6TRF4qyDZi_Owa{BwOo|w!lU2K-@r02+WPcx)I3EXp>Ou`GCjF47SGHpB%rM z!#fasA$<^S!h#p&lG8mcYD}B}70QY?%kuDn+8sjlk8I>He&eMSBqc{*W9(72J<+r< z&13mnHS9=)mug=oVQK(`K=-?d{*I}obd#9{j6H=f^$FTJ;W4-61Y_Y2x@Cgl@Fx}{#C0+&UZWH}JgTp;h zP2KTx-XD1#WqYhX&JBF@suB*pRf7Ff4X!hpBz&gGBbrs%@&P9fLxzr=y zv4o>mB`ZY2auPrC;bi6|Lfhj5k~@pFreM_0a7+5gce=;yA=w*e}2z@P8 z`z*o~TTqV){smxK%zm88^Ki<;aCEO9Uid4HWPVsSe6Fov6Q!#95?0mrr*-}1DO8pN zh6AnVnBh>+yw+=dIqYAe(kR*2tElCyS;^=M^Y#_`*h3b%U)J6lXUmsw->hie z9vUwDg*fXyj{Sb>j_tBq#T$u2%lP8gWxL`JHd0t5eS4I=W48jPg6wb(%YxQz+ zoH6%cWhVv`@m^!b*k=N+>L*&LggZV1lG<9xm?{8x$JRJ8KT@wc>3 zaLRtJ)z{n} z;`jYLI$UAY{{<_gMihSK^`I`d*_Mf{=3}#wMf5B4fJ2Vai*$`>VYjnXjo`lb6{7E) zvDF3#8k6)AH)Gp;8i$)roO@e?0epsJ9n8@)#yyQm7@ZMOf)h0ybI9=`C{68_iSttv zI3MKFzZJ>vGm2rt3_XgKe-tx7PyIm&&Lzz=u|${Cy>&SfhJ3Z<%K4GoAKZ<#_=?IvTA?p*r^w9Q)RBG83~t(m9&Mt1PfI75 zfhF|9w<>%|Ml6X?dW!xp8p^WU<;7`(6PiE@FS|Yd#NRIxWy0hlZ3s z8{pp#Tw%iZy5rhViSx+J{>&VNspI#ZNUx_lKIBO_ED=xcaVC zu=d#j**z<_SJru$L(cJjBrhIi(*h^Fu97f&VdJY0d^UYUw6H^BD@>5kBG-|9i%ajs zx@eQJ$WVIWE15sG_5$T_NMts`CfpH0*oFtG4{aEx)uIcG&ODh}3z91sC5OUiKgTKb z;{u`r7-D+&18bQ47*=RO-Va(;t7;1R54XMHVgD%$jNs4m_?v`j^=^UL9gJ-Dt|JU( zazY2qrU;W!LDT@G{~L@$fVVuvS|zjrTyG!J=hP%Rk>y~91osfzl_#eJ%B-?nW#nmiRhN<$t3qCa=iZ&kdh5U(f8BzQ`8l*TG~PfD!|aWNookw%Si%GqVkCsI#*<~hZ$5+e!9Qbm-X`%w zuPyTWcE7am2l;3=jT6>jwyplDQ%G*{-$e+;IpLH&e4113j6I(YWr6?VzO555pRxFY z$_eu$lnNymS9<8d=A>U4Q7u~9Mh{Q8|NFuQ%+_*pe7at3{!O=$cJ!CNSy{AsfS7y@GK>cSu5)gx5FloB z3CFt;CvyKXe@0SIy?z!jNMZ%S?mBe^|vQ>EFO1OYAI;ORV(u%5NHDk!?D>;o~cd7RJ-%MSORlA<36397dP+c#{{ z&VTwdY}^SBnUO+>Ladm>1<)k%$5&(!vh)eZR)^+Mo~L!7D{XioIt;JO`Zf)Mz^M9^ ziQju}OoqMQz~JmK8>Z9Z1*Y}#E3B_mSFG;@e6NQEV_-|(smR)b+()Z+4@I`GmEu#o zNxOyGRSeBuNOWBDaD98vdL#klVW{Zo(Yw@L<<_0_j&_Nn;~mefM*Jl*$vE{g;O?}E zgaJGn!)G6XTh>+ojtgLu1d3iU;*{JJn1drZzwmF=E;DUlHo|~d$TBH+#^f;ZHV`8n(wh1TUy^tv z8_-ocTuZ71A-Av*D3Inmm}*q`IS*)yJ=O%PN?Ytj2urE*Phi3lv_qq>*uWCF$OfBT zFv!{yz;-(j;gy#!aXFB-k?e(~Mc2Qp+F>ZOYvXNtizO!Gd;L58=%V&i5$yBv2A39RM(6r_c45S$bP$d`)IYhZ`NVK(QpdD&+%J+MM#a4>k%yb0C$__FEu ztlxuQRK90E-A{kJ$PjFD8RL65tvr~*KG z9WnvKUCbomkEKQEzlZ~zF_?`c`WcR`s z0d>be4f>qlPpN32d6%jpUk_CXj>be3F6fpkFPfYjRLj-4-O32i?oV_ulJf0=qV zvJ$;1r3Hq)wc@iy5QbMPMEb(OJbjpw+jeI%phJ9{Lo*e}pA$~J5t<$?;}16mZiciw zzTp(FId0$Xxm>YaWJMn`2wN9#LQuHLo9>wNGM87aug+TH#syERF2Rh8Dn-zjaCU0tADT-Rmnw|AzdUC(a1ix8LS5d+XI{rXq{>h$tP8Q+ZDE;5RxC%<-}wSDA;a;Ieh4MG25CUyb3va4MTCP2aUn9 zfqbUH2mXBLHKF;K@4DNLzIYqhrCS7R({}H^y$yr^Hpkh&)( z)_qyx`;LOMZt4S`bO9R7;3mZXSaLHbugxM(xJ}v|Wof#r9 ztoG3*;761f=nD&06rP@z&#`@A4q9CJ_8j>jP~&R`C^-&FeZ5b|)fqVSEE&gk8h)^; z_ouO`2AC!?c~NRd{>)^B7TH47+d1xv8JZK!Emr-A6B3HS3X?7CYZEP0>9<+N(Y{`@ z3lk%d$&MK(2bEC8u12MLnnacGmFW~G$z|a0q%eH$y~pfo>MhjQ{Mx5=?N8Bo_I0*P zvnwnN-;ndyn~Jqx#t`AUFcu!_{B>@kl=$Rdb0CaZHv@7Fg#;ygQ4ApK1i1mCFI=cj;n z$>9ecgX3x?0+0ITKB9{ivh>dY;PxdrA0P`^F4xIent)xAGg46`tty-Wz_sDKNJU7& zq8+p*nl+-puz(>v1So3V4k5Dki{&-%%)Pl+U@jvho`T=mAv3sh-)Y4wdx6ekg}SPk zN1B4vx7IQ5I!4wxzE?QDe8McjldmAa!{V}IdWE-+cF?q#$ibUEZUL9d8w17FBjCN%S+S8P+*E;bzX8KE!^(ktvo78jS0)-6||ul zqj;xJM@k=68dT`Xy6UR@rsh~xt0uzKOT=$5$1eB8oi+rIuh#4>d+DSI{Rdl2N{^Vp z+TH7?$zvBFdf!#VhHzl6nqB3%b`ZriaR4jh0YStP;&5^sen3J%jK)QIpQ*=zas28f zI@9IBrU#-^Bka7|{^mb#v=r>z-jKJtO%?!brC_yhh(%%{h z2X+s|U**@HVpk#*?~IaHVAGW5mm)&aMeL@J?p}aL9@kJr0-Jqdz`BSi5b&e6|6!6l z;8mjJq2%M6h*V^DX<(tM7N?Nifw>pf|B58Uo@a>OK|%ns9PSAlAwW|5Up1Wrz-%Z1 zvar(|B|41BS+?I-!wFM`JiaP%qoX`E(HwlXh0#w(IG{Sw77_OCmUHhTpY}|e_HoY< zKYG}10|N<7F2jV%8%9$0PXV`WBEp$PA!BjUyE{)7sAEfQhTj<3?#P&#G*-wXXg^PD zz~G;GCo+shQOXIMV69EsQZv)e)~!d@PM|Th4`79(TcmFd{f+w#&KyT7&nh%#=xY=VD_X)N?tL+^)F8bU* zj=2rB_&h3`f!O*Wi+U+sE58tgd&my9pky)Kcna(GBhB(4ghK-2&}15_YCsYitP)pc;^=J(UMw*PqGboxGo5F0VFurU1T-G`PVG zcAn%~N$>J?<)tkCGz08E4w=!uS4plVS$(7MF2ia)ag}Hp!xZ1Q4rK{&b-Zvw{1+g8d=eHTklX9VX(P&xz%^5Vftxo zEduviiBi^_kK6k&m7L~dB~B+$wj_v`s=*C|I)sFC&K+n%&M<4RsX5@rM6dkSnnWon za%he`*EQX1qm{wuhPbpig%n0cidJr*QofN3DsqJF-~#x;V4MXl97(KHoNj@VZb^|$i1{UU!Jdj%J7M8i zHi^ebH||y8K>;?g0QN-z&3qcNB$^gVteS|64KnLozsb z$u5L5()TIcHQUIloKT+bNgn*1+-Hz@apGYZy(aT~n?t6UWz31#$jZyh8xb=PmIrF{ zlQDgYzLO{76np|FKZzWkIg_#5MO{d7uuY^GOtLmvkcZjy zM&ca8vSRI@cG)PQvuyoaG1%p{T)?su+CPobA_t7pJi)pFaLFzP;-SJ1_dvCr#Zabl zPIGDooKT+mqaTwomKUm#*G75$h<} zJHO9fjffC}c=dl0^R8kF^p(r2RIEP)FFEgguKK!Xeu{zFO9>+x5qYmO`2&?K@*pZR zGR+$J9w?QV=qCPE)Vn415?*HUxK;s20}2EKV~Z`wov4~Brr`=@j8^Yu6pqTgs>kJ^ zy)VBBxnCgnK*}JfIR`Lbms6(o+~JZ~4^MIg@On(`KG;TL0r|?!%czRBMe}_-jl)in z7bc`(T2JyK=LdYC6=M+*q9mn+YG!_>)3us$C^P&&A?2esg~00T<}H*&@B}h#KwIsq zG!RTd?Bdc#Kn0`?Tili{YV(^1yB24c(jo-oyKmK-JYt4!s$MID>DiyHPSCvovr5>Q zWjQq*oL9M?A3yaa6?TQa?I}XJx*rAG7itE>g~Uyg#)@*wpL(INrUYANq21vV!rHej zYkps9B;MnF>b|*1`a2^_G{%p@6ZgWJ=F-o6P zm81JH(+7bxfuQ)92X+~C5%?gPd&wNtxOYw4IWYfP55sll!F`MS&NeGG`z4ME1Neo> zDA3;l5?1}^C0(ot)vy#H6+V$NGRKb_ptsQn@Rt>#7p4S@Bq&kZdrs0dIA=Z=zFQ}_ z!i4TRM|PFAqr1SyOl)OvcY$SNL7kIHq-kY&`7KLndz@GQ2WIVSh@Pb(MPqQdRBA}k zHs?f)5)B{noaJcaZqB*;&7<_LJS*hDRMS>zfc4^_QCTJGD6QX5kn&Mv@Qnqjb@Ku5y~vm@Qwou3iwLZGzE z5?GhvBjy#vQ7s(Lxn6}?%&tUo9aR8BIqi)YN$Yt95RqS=5ZZiI__-i*Oc(VT+(#u% z3S;8jMud*R2qOHdN=J@7t_^6(c@Z|{FlGs`&)^4_w(A1Md|r!Sv+E1#YVtfR4_INR zWrE-DHc)v~Q!~-P;8E*OS0|(P8Dd8$PW~Ffm01kJa(@%vHHd8(OMYx3{!F|7UvCqM z8=+kMl;c`uK@BMH8EAPOHdzzM%Vfy4{tvQdiR5VEMK97XIN2mIdDbUSNe=}GF2YxaRQ|_AU zsT`G_mVK-6fz6Av06agT3Swl6b3(iHIe{OVdx=xp0A{n*hvHM%sr$!Joy~5ySy7O{ z1g5*10ZzeacsO84khh1^eiLN8s(O$nyq}eYu5pfxQ3CSzD?C z|HNv8G$=3j;H(&Xa(^`uUJL@Ax|LJkRH)hVE)ueZ$k$81stVru65^89-wrm+-_~sT zm*4^QV9XK=mif6i^AHBX!Knsi0*XII(6dwzDRd8zZ0oV<$ONdjjryV4-f)QE=MaX5 z6$g+7P(nSuA8^Q+PI?Xh7&P5`V}k#qr#IPgdBHp5RqrW>SWuzXo$PdR-Pon}z9`;_ z${M`YF$f4sVbrMWdRzD5mU?~6J z4W>M!Jy36NIEl7uLFQbJ&Id$+;3>EWrN%H>1v+3Cyg4zLP2mwrsby-TY8Co7s8zv! zy7E8haS%dZslW-dWwnr5e+2T-u;DL_U4FaPqLUA6#ag+A1v>%D+m<(iC(IQ{Vgi{T zsCtg9?$B7;>m!+=N-yDQ+rGi(L8uPi*g;O{Y-m@=h{!qP)AN|Q7r?P@gJ|)N&Q3iP z33yb&XKa%APj)m@WFDtNI@WRf@pNRQK5jbE64EIOPVt`D`NFZXo+qqFv5fdZO2pwW z7I9NYua6;#ai90W#iE_F%zRM)y-5flTQ?uXEy5O`2sgIT0Z-5ep0JGel3ae17zghC zZn%67I`O5k-R$A+S~1Z~$kNvC^DTnR?s8a%JGibur(r?VGBi5&o4rVGTBfbE8S;N- zd|IwvfKk)I$n+j?g1bzsqH+XQdmTfITnazik)^2+_nZRzY^xB3G4KGIuu-zS6yDm0 zM0P}nk^YACdgseq9qSM&5+97+)KE!aWVCn6(RVD(7#DVNO0Svz*|E{FWjX(5Y1d-y z6}c--+m;JLXUX>`v?s2YzYxldiLZ2Bw(gP#d@Ag@RkoNYF#m4He7CYdRjBh0#@y*W zHKD?>Qz_SXA|v7U_R;5jfHO*FnxI6xBR1Y>6K3XJCr_VgNdNar7G^kizJ9AKqZ#%T zyE;DyC{GqfLWHDCX9U?4G6s83DbbH2#e#_vtv8m1y!_cgPU8bu=4Y2!Mu>tes+y@O zR&`*UCtiJ%YL_qU=4kdJkd@#YQ;zZ>7deN_51D0QZ!E;80uIp#_zQatXdrONT-b)) z9Wjn!hN8gmID)Bs$SMF}+&6o~Tn5eQJxvH7t{Y8Aw0^ofN3BBX(DEUvU)ctzb-h8& ze)`2b=eNOm=A`pwyQ-Mep2DsAjt>NS@M?Sz zg0v=)%0W3QrkxM9;CgnT;|iJ2L}?LJWd5JP8W(J(C|KYEs-sFu^cRdhwXHsmzgH|s z*(Lo7=zyu6CwBu;7$_6pj&?r-|IGQERbS-l%u*e6WxhF%9Z zb~~-i>o6K$qj2`B3#tBspX8W8BvR@CzlTfZfBZK1 zFZ7>4kfR2+V~Mn8=bIkFS|8#EzY-v)vMDkbPv3>kmqvEjXdsgQk^<0sHG_q*y9-X$ z1c8hcYN!Awm#vLZI&r!l=P`o`Z1SN%q!Jfz-468JxjG5$Cd%@JUO|Z&=*EZcO!h{1 zOkm3QBH$pCtsT?9VeHbEk)-_C6xDdbS$b_nB_{kM*+Du?WgT{X&pyB7p#YtVHju0a zDkXfLY|ngxY(al^R7vWaOA!cFptPX0sB{Mda4ZEwzz zxYW|*UVI^>``$RrE(ID}&9gNNP(ATFv_L>%s!EA&hPXQfiGf_pDro)FaA^U#ks8h| zbsw!xdt#;hkl7618cK%iNgFMIHTP`)et*IOG6+8)Qi3KoI0~Cu$R!qj&;}=X1OmZq zFM>Ie<4ltwSsYMUUgN8@AjaWePB2xZAAnXzP>LSR@W;wQ;^h#u4-H^ln+*9xcsq>5 zKGW&*DwRN)F~seBsO4*;ux_|pa1jctVI>kDeYD@nuWnCah-tf$NL!jNyPbw;btz+V z1=rr~?+iLs8lW~MP9u}77=k-#Y9-p?onky#@Pz5_A3jXTZQ)w18k&s$c7ITBNMgWy z?@l3^EVXak2nSlvS0Jn^OMynb+fdGhbtl6~>`kNeg?eMvusj_9Gmw*6WP%}9(pp<5 zUkkh-RJGt&If{i)O0;$hbooHeFy?lP9%OQz*#So&&v#%SS%uKZcIcJ2?CSSHTvGW? z%(x3OyBU7a1V` z`dOHCaC7w_^nGaz;+JFcd!kRg^MmjpADW0Yl{*i&|wv4@r#UYW;KS zk>WYp^;9)(V}%wS)VY!byEsj!hZ^6;hKxlOBo){mXm&Xg-(cj}r#adxKUaI5W^P-g zzXRlsFNn;9hS3OI8EllYtJ=^hhZA%Ks=%AKI0b!m)sP-&$3qpHSGWp2^_~9?A&dBr zf%4=Zr7O@N0Q3=-hhjl->kLZ=QaLiptdIkGOW!^yECKLXJ-5u5)%S5bd zLYF~K6F3beXc zZpGMbZ{II{4AS;&-_p8iu0D;w7nU3L{Yf7Q`Xz9y-Vv&S3UNW2Jlw}3(@ih#F};t{s}DJ3_eMv8vKdk!>0#-- zMcKO18k~!pq!!$KSn3!2v5IE)j?b|T&j17zI_t31zkuUJ$p=3-WO?qcZECP$}#yzO>*m&;e)9k7ZW@n;E zBvL&Q*h~eFCQqPW<6@)Z@ZD)}iIsN<=16MOl)vmA*Jw{c1*1uZ4PEYiWz%cEtfelh zba1^V-}&sy8ptTfLZhvvk!y!6kWg8?6nXDl=TYcbt;sY*qcjDfuCw!1;qHRIy+}x! zL@2Bfpz8pj3(}9@5p;2t%Ft_2hq6DA2sU59k|8F$I2OiZvBT2lzy8z=wQ9 zrmeI)h+_~J(ThiJmdB~WvFc3kLX6*3sZvSH-67Y*R=hVPk1tIXE|(*?uiXYULWs;! z-e8(9$=U{pnW{kVMQm#+VhFJGF`sp}9&$Vv>|}VWRg>2i49P$lprXr!js@@_-VTqM zVGx(!gkz)ZwZVBAu>%6l2XMf{nMza+;u~z3S${Yy9@kqMX@+|IUh1K+s@gTW_#<`#*P64jn+Qz~-M z)iyj)ac1hNV`OY^tV7h*OK06aRV#eE!Z{i1RgkVXq{C``Wn^=t(vUbO*^)GDAzMzV+(+5T=2Z6145i=o zI_}T+*Q39Z_qblycwWzIyeCefNjVIOsTk@Q27&N8&Av8Zty>;2GVwFhWjHB8LEUOO z1dl*O7i=jprrU{`7eFW@>Q>jbj`d-hqy*fdByc(rGFv^U*4pt_1L(S1eYXHltfD0 zh5ct(+V}g~=5X;~b7gF#ZM@JEjD|xM0!IjeBlPZn!jKc6?%ewL?ORi1JNQ~^G?zF4 zy7Y@;HUH2}hw3schuAFXp1|bjsiZAgH}OQ69-o8jx6tgVDOW7Swb_3M^2h znV|~*mA3w}eefUpd|e^FzP5puk^~<@Bj6G_>lC|ta1&xtU-Te|Fww`7K}Zm(k>X~M zMmjbadO}(Tj*&Z%NlwK&9-Z8oVVUpj7>PO)G;{-(#SqhipFnEw=SH?d2C7H}?gft= zD!kDxz~t2Cq}aixx|Qk}T%F#fZM73O#xb`B7#7*lTkNTCu)g%H{OVBR_`@*SM}rd` z66gC(t=^?29ZpmW>9HG-%GRy;c1*RgDT%XBY0X_DAAeJtukw@0-#h5$q@P_u0kRBp zoNC6kp~K@Zl1DGjQS@ye`z>R{=v?mnjWdK1COetaN1ox+Md72gFOVIug5&nm;MEf& z^aD*RK=(p+7e%m!G80@nxTFeRt6P-@g&y7`AB)D~rYP{aMP|(CJsBt==H!yAV2lgH z+Wf!wP0RoKas7;5lq&@_bnQD#9?Z`YyZkZXYUe@psJ=6Dhi{z>{j9U`m1z!Q&29;@ zKOz$;BR!~V4phu7*+ye8O_;*>?hG`j8bj@)hEe;FHV>GuuNtY0vtO(L_=~;RcVRBp zp5}fY(#}yWirS@+xtA-nQ5~O#*(5nz^C;EJ50Iug3>WWjS7A?jwP+c!J3x0Nq-e}Q-_ruDk!DqUn9kB%pV=HFo9y)g<|`;Uo15CnG1?NI%Mtsz@Dn1fC?JGtt%~zYy<&%v@ASLggt# zsXmVpLZGkYhhN~N$OocDEQ|$j#@ikvpJ$+XO%W-Gk8Q%xI nhb)KQF&BLWlNr(AV20Yz@q7Ux#hzK8ZSM0_^c%(yKTRq+8^!jp_01_R zv-288LIdo5K2k5dmR50eDs(aN*-}eZ`-Imz? zaT0WjTm>KA2AzToJk3dtEQmS;{*x4r_6=CyffN9^a??p@_JY`21wQ*XyQp^%LyVBDh@LL+aBc`?P5orZYRy^id1isJ4`w#-Xfn93+qYa3{#vB(N|8^L`oKLf`@6!V9!H(3OdGf&)O-XtR{TUNFuf zal$ne%M?G^6GET@)Rr1E2;3@0c26E)tobN7veoI{h3UUkJ3r`rmEF&I+S3p0v`my1 z_a7!b@EEMxI5ioKLSaZ(b96k7(N|=NWEBRw<1pLkzNxl#`}->cx17a(ai3*0$|(LP zG+SF5n@rW-^M{md=)P`NNy(M+faJv=H}) z)^A$_l$Vm2mfhjI$x)=MzpY$OHxbud03#Hh3K&?1Yo=}6aOxB_h-bH=f4ZzXnUa-I zlASa~0aWUWqy1%|(yJ!$TfP?p@@q)bWk+c&kXrwEONatfl}KLH(|az5_!jh(#Fq?D z1%nE(g2K(xN=WmCzWyAvOTm+TOok8RHp84;Ed`MOyEDq5k!~9_#)t9O6Q146(w|zB za^9||Bz=`|gegx;W2<)sWlfWV|K6mT8UswQZH#nMnWn1G@o;rZS?Ddl=Y06;YTlM$ zbZ1POn8WkZ=4kWj>rC83hGaeD9QWAda$asWB{)Ett7fDRYLMFUE{_*>zw~YKLh$?Y zzfIp?L!Yn9ZJ6tV6nx^9D;Y9PYM-(O`r@>A{VT!}>dS#{j|-;oE=>|FW{a-qiv>iig1YCE8ee$K;6yDCNzv>$RMXaTFZ#Y>a%ZS9AKjGPi9 z>kTuiFuLqre{mz@l2!wF%8;fZq%fCzVRVFhund5EvLJUP6Z)_K4w3yt z!F;_!C?`h#c+{!(U_CLhKYlk{lf7~X-n(10ok{kFn?B4QY)UyIUi}m1pSNR(6ss82 zjVeDQG}aIc1f~_-o;wwKRu}q4F1ZsWnqc<^bl~a5|M>AnhBDH<(G7(HUvLEkzLhvM z1_K*ie`JAiH&tDNSD^VC`Cv}Wv=Fqi#d!TB#i0~)tTfW$(Aawxj4bX^U)2%Gkcrmb z3I|t!>tz@9J(tLXyA8JpT>koS@J^37SG=)RtCW|6-X|}vPAM7n%S|roR|0$tO7*(e z%-j>1ZkaFgW?waj8%Za?$&>ATKvsl#iW-`801WS&jpbK>WbPMscUMP%IoUN^Y;_W% z4hZhwMTFtO|DPDVPjJhOA}x0Ar6n;OJ0y3FD!BiK^@(x}q`*l4%(`KHQ!#{8fL9oo zM}~l$s)ET?IdHeTeGlzUfjE87!6W!Wj+u+U{`|M?snWnAqFiHQ0&4$0FO3Ch6$8qjt!tm~_(Vv?bzs=eJDz{GU zp!_a|3{j5!8)ly#!;DgMJQogHgy21H{cA!nfX4c(ReNaGP=t(<5r?6>Vq$3`BE2a>^g4hZ7grP)iqvH}35GTiIM-iZdAb>TF=h1)=6NAPJ}zvIl**VU4l5wyUN)+oa1=)WMkbR7p}GcF1}T9a`9H{n zmegWJnk3+cWFc}D5^<;9Jqq-cTpxCg9efX(v2SStOv@cdi$TaCSd<1xQ+}v{Py#-F zb(n_69Mblm7heILLC<_*3S5zQ<7YG_12F5}Ux4phhEY?m-kar*=CZ^~Ir{MAoBB4` z#KLW%Uam*~wjIotW36vBp>^p}Vi{8E(bG+>kS$-%87EJ7*-uYaO}E_h1cJt~`*PjD z@hDfS32c~!KLst%VY2Cc2h(Vc5Lpm`Lh8(lnU7}pFnH66#THbiR_13#_UZRUZm0%QG+<|PaI+2Yf>hLu=?5$Fx zLDUA96%2(VvYI%J8$3^1vka)syBHuX2m0a>;7=?(!2tZhAbSl+g_0jgq5uMqNHg4ZBI8k%KGUf1@ zN4cjP%@T7gQEOnr(L^=3W1jS0D5pNR)za2xkb450eUgDPomqItANO79Cc+H$ZB{Zy z#FZX+aQuiF;W!Yq&?54n5Yi$M z3DlyLBB%bb9A1al`aII12wYb>c&X-7r8domZI{#pFt@r!C@><%M1ZDo=;^`SRc=n8 z&5oi46PV!Te29D_?S$R%7)ev{Xs{PV1ced!Bf9|OSU@zScwZv(A(51>0laANV>CZ$ zhEKM$>fPXmGs#1lF^1f<4rp^aKhDIF{h@s}0_6ugOyXo+gk>fDd#~G7_^Jk%dVEUo z9}-(?)aC3Kn|IXDzt7im`%2n(bT^E|hli*R`yqTxlT97X^pP}X+r~)@C-l+B8O;uH z1bU)r5yduc3JkHdMBgI^G3upO1re{l|P6 z{Bv}q_{wPLp96o305?AjEA%$;TyM2&uYlryLtYX2X^HMIO7WhrNhQZA36;2WRyVzi zjEZzwmE`Y<%?7qY3h+yuZr*%r-&Fu4>eiNjFqF)s+L z=u9;olOMxkpG_$X6*#V3-lq2U! z6hm)yK5yaQSp)#wUNP_|2pl1#Qc?X$U{W$84U2u>`* zTjwJ|HEUcUL(&JAOB_5wa4?O)U~uK>(CGj%7s)Za@y_zPX6SFl>Bn+!T~^sKvYKud z)rv2I)>;o$`rG@|YnoLZP;qxEbhd(2#i3gVYCp86!;;2=*|MV75>!?5L%syhN3IPV zEPBCFd-Rm8fCti3412W#qKA=pPC2ZO+HAp-bi}qZ-3K&<>CFw*h3=P=wbu;hZj9g7 z_jbwgP0@QHS&nj14F3B;)Mf_x@S1Ykowo}yD51Oazg05=V@fETzr(RX2Uqlw)P|F= zq^v0)bU?AY8`Y$#Q=EEe_!A>bdn9RwxPi$vw!V4ySB5(&Gmx}EUZB{$e{70{gEFRz zdNa}UHt+v>>hRemg8PFL7?Sgb8s09)!A~fzu8iUK6qxKW0btdGh)qn5`-l2=m2Vbt zgSUE2&%a=Q_#vA-|40J%YeAdHZr22$gpW8ngGSwtiX4nybBQYzd}YH~(V@tfumBZ@ zGIUQ&B3-ME7|%UlL|XSdO`c-NC@!2uGVcCE;w-dQH<#JM9_F_>xF&=u#}Ru0-Yd%A z^`C=jd?za<{0Tmra_Sr@mH>SnQ=%Cb1rxBFho2nbONEB@6XV;^Z^XJM8y$kiJHz7Tjg4N4n3g#>_W{Epxbr7 z&yzz@=38|XttBYCXTca@Xavjp11Z@Fcy1!r3TO!KRADcVi^jc=&~hB_ugMFXN^&rX zQadzYiU)yPNPHzrYhhrdeKaUMC(6m7UI7xv5gD2WkTe3p0H+(Jre*x(jBkMXdo57OfnVT z&g3C^kNg`b5sY5?V?fOh&qJ&aUjD?GkX`n;*KhR+mMqrdBCEgC^_+*PA(4=uNfKud4Tu1*yRJqC#@@k}}NLeQ& zm&%*jciz-59x$bGm*_j_XM#e-#0*lcN@x~bH8z}m!56i(wX!HkJQ#B*7?D&zgDh_K z-=mauv0`L9^Nw%tA^)!zAe-~@XB6u+KVR2=aD>UKNt8wyM&xZQJ zs%Nw;l-FeoIpRVqWVOFm{PPNC|ocX%{>Z10eOD{vJRiJ4}ogZ_n4th*r_RIGO8G_cZNd#0i ze!&oD1C*RmT~x;t(mQ=9+^7N+T1<@X|J8ljXS06AK3XW!M|fnX7odYqPYFj8p-pf* zCXFybwuDv+g?gGlZ9C6AC(i!^NdFiGX($J(B8CK+H;t4Ct8HsOp zmoZt=HBQaX&E3&bqEXt*aU@^l^*#ov8L+f4j=s)}A>~!VdKgti#Ie5~5l=vQ09C!> zHAvzhl-4;MVuEvRv#^TPss+V5ThHH$G)H7qX=r{GIyfSKlq02~S_O%sjz_%fV`U7; znDo}y9^Se02<Bi-CkRX1NGUKv?~s8u{~xufWh~N{K$%P%`1H5Hm)8Yy|MSy#qP!KXJ4wFa-29Y{MWBB)TrR%`qW)e|B6^^y58NVGk8%J7GM0IUy zibwAjgiE4MuEE?}^rKO<*#S^N=L0fogq!|uNUlQUjYKC0Dq9~jelG40?xopR1eyTN z#Kv-;!jK(v@3(IRYcSR#kRg~FdUAc#+uay=d1H-F;=()xf%!R{7sOED1)y(a5CZ1Q zx9$*vc zDEG5JVGxy}iQy{A505Qy5jX*3khUx_-Z2jChJyy~@2~_G)JIj^-xmt|xP+9!aA4g| zT>i>KK7_m>sPdM_2A=TxDa+r{YF2;;wg3-Yt^ZG-Y6)&YB;0z(K{yKAfh`Wf*CBb2 zFzN{3Ogc;**^?(sB^_l5Zq`UhNh+u^Jos&%+!8aMIGv;s7?2uBb@QJRU9(%xEzN0U zK$6kKT?k@cgKXgz8bJf(Lj)Mj|6}FWw>g?JJ*U-+>~dDXvpiJ)O0YAEW5PB9E zlq2LpfUJy9>`#0nlzjq2Wc~v+tYIkoc0(zB2I8w`TWZ1}EQHcD&?yAWq_2v)uRu#3 zVDAke4Eb-c8wo+VL{HdQqaSeUzY;6Imob?O&hf;zKV!j7Bm$#{l=_iRK=-Kkw_Ph; zuw-pNc4Son*b{P62NZ@UmwFDUcvjjqeifDM666^b8>hQr42oT=$KtvR0I?s8ivOuB?QRH(sEdgFq~^Z4~ArNIv^1D?qv5~ znj>022EOSB*)QeOAPko4Zdlx42#<&1*N-cGJ9&k4k4*Bw7SOLb0-u})st?s%2(bma zw;br+VY1zaM96^qO4=wdr1h>nwz6uy6@%mIvrqc{XUE#eDC8`1FYqJGq@{BYiukjE ziW^Pd(5U;hDPi}QKO$i$)<*(%0@H(>A&{Jt2dxG`i%-0Qr~sveK@GI`478C0uZoHy zvQfUc1ei$n^|U7cg}A&WG<{d9l8TKIedh|F7S6^*TQK0?(tO2AkIoeIC$Q3h{sM z*5ly)o*Qk_!xY>Bk}*2N_snNE7*{w`9!jo5QMll&15BSfKnN>n$vZt)4h21-=t0r; z9lk%)JAe5fNkOYrFdS<=%D{s!Ix_94L9a!pFfX$D_|z$cV?*yRIn8eKvomLz@OPvfsu zenP@ms?=|ua>T>Ul9K!y2b$)y#j0T75$)_Y%1T?7ZZ~}NwfJ`zb~;5#=#33$r_)5_ zIW~sBVt@Pr;i$)8h%c)|uLiP1XZ2^`R~SB>a>34K0*dFV35f_{ajY;p2f(pj9MGRz zL9f|E{tSe;>Wa?iK)bo*=)so^dnu#nQqWe$hh2nqr~ushB1!%YePIqjlp6!aAO$Yu zfg7vSA#+LkTl#E<=^mf4+{ZU*iv0P1pIiw^fQkLhi7?iJq<>5B@gT6j@-yJ!{&VYu zfz$4xC*#@caIo1J78%^Q2Z-q_6vY@nzx-K=3==?1>={5A!ghyl2H8s;E_Xupsa)Nn z$w#rsWevZkCETM7WoPS-UUz*iZfF(X>z+MME^(zf%U`c-F|ZnIVqy; zozn#{2_!4O`@^wW*t~E#0O%%D0F_CDs;DdJa;F^Sb9Pi&Ukn=kyZmn^YET3%|FN!k zC$^D2U5L@gGmYXR5NnYwI1$~sXohbu>uVt70hO&HqhE+>p3U_O!^3awe)ah+9cu@! zrEm2jc?JrA%3!H_WVZ7W7|q94pcqA*ggIDio|bQr$4Oe4)|dv2#{o<0u4IYrfw}fm=WU!kmkQ z5@d1yjGcy;KThIC#Jis25#Vw21(@BCKtV+W*$e}HG-%uq4{a{EKokLeKYhz!FU%za z12+RQ9es*)0tTQrDg_gS5NO$(FVUI12?*&#TJ4e^#M_wfoWDS@|8XtFP2#{oU3ZAY zt>}>JP~1paf==qhrT4f{M&YbE9@umcBKyN^N)UATU1QLk%_6$))(O6UMw*H?3ZF&J zR-TRI!K|Jv-O;-#juMc`ly^WSVd+r!HG}@y_~7|>5!0!z4oxpN5CgB>y=-h*?a_87 z>KVT?WZ3lqxC$Chzy<*r0ppKH5Sv3)aglA0^j-Z_$+kK)iUh}pBlIT@K1aC>Oxi%3 zqHm#m^~=9~Z|EEkW(O z5Iy)CrpSV6BQzk(*E=!A$6fC86*~bDmN4x&B93i1CCZV#&+E*KjhDV<5>H9zNf*^#>LlZkmGy;N>sn(2wH*30mJ=gKXBB2{8eWMz8uXo!<3jCLv{_7NOt;$o=`4f&*VleeQEmxpDCrP=YvLG z$Tt~l@FyKBfzBV>s7;tY511op2((AtS(xDY7F-96B9fnyVfuv`?;uapZtW2h&Vt?I zawt{BV4{KU0rZ)+zX!MC8Q=wjkP`2ZZMu%&{-O?XOmn<3WAvZ(T`q&Yay8BdG&!1o zFuz9pMbJp$@AlfI#>mX4?J8LNsS9)ML$r$)lFJEJRwvFM>ORsHqBXZTM4M}GS8z>$ zDSOm&re^mdcfP?!iWV2L-ovOtqY^o+T>k}SWZ(Fh!F9qLNH*AWA~s4ym?W2D;Nl>V zVv>fwz$yp#&yh4=7qa_q^`%K%sP#gp)M@}it#FZ=k(9A&8H)=-%)W=^XakZ!MsF|c zYa%C$csa#evPrI(lk3FGOUhV?UV6HP+T^v~kg1ThIw6Wnj9ma(Wo+sgUW(>37Cf-l z_gbFjPJzzBx@@v#;@~jtH(XzS65Ma%;sQ=ma^;%OX7&VKN-|}T2BC9^iW|_G>%@J! zYk^U$D_;0m5ULdQr}FTWP@6@dtQge#_m}?t)o9xVy%gjDk)}8J>g97Ei!zt`aiK|e z6Y!J*%Z8q&0bX7~1DlBPRxx&`0%nBL-hIK%!27!CU)@%);$|=xh6}YqdjGIruz%S! zLQ@;U5=3Z^$~H3W#6)5w;gp{HHm$j5ow!_I-KHw`ZFb@7FcW)r3$Ai#dHGj`A=#?6 zXXJ|Mx_eDNA0e+vWO@-yr6WU0gPyqX^^}!H!r#rKX@3n+UrORN7qTI4#T4nGrdXcd z+zL(`y#CP9)P6pJGY*Ww=Rdw5=91=8XRIH^sk!9q2?=F3^6i`WNY0lsQ1k? zl^3rerQ&&Uu6lyT_9ir8?tywEx z5nEs1O}O?ZLZe8UCPTJ-IQShmpgSi*aO>&3fB}Y(5w&o%K(b)8ozK;6>`8D%NN^M9 zsS6*AxC{%+K(S7^Qu;3}y=jdQuyOLmxq(lKT?Fh@f$9QEzo0sGffrTBdold- z&$Y%L;mT0XuHZ_f78|beWicL&hWCDhwss5lt}@82ZkG-Dxg}1_6%hL#tb&Kmli64O78mI8nhbwT23eCm$Hfd-@ImY z<}?V8BvcO4Cdg_>XgON~K39_=CF>yviPNB0NYdY)+$Tvt%!DytoM5!-=Bzzi<`?Ny z9?+kuKB8Chha4ma1v*j84_#^`6^n0>kMM%xV~XG!1I+L@xE&qWwIVi!KRlY^jR#Z5_^RwLnm0ldd6 zZAsYI!#q7m8S9e+r-~sRyXu2>KQw1dQJ_JN2_C`Mwgwj(*RS?rf*hnkv!MJwOD^YL zh==+{>(qm_4+~2xtIIfdojK~30KHN242)O>1S+O7dbpdT-NjcR!#Oh!xC5XAgCy2V z{`+RUqUtYOoMT_bD=3IAOifTk@?PepUAdN+AXpt-c=^*f`C3DYid0i)Xh+r5?LS_l z)7#{Hyfg4)PU9RxSsr>dap+QXe=pBME5LUi(C7^h(_tBInFq*OH7h^I=C427bkL8ku ze(vL&MIUNzee!f?CetaF{dd#kSvKuDwf*F#v8fkECpBYsJ&Lx-CupI%juL05rf z!4>Zt=qf}1CbTe-kEaL=?Ku|E4F8g`RdI3Ax6FI>(D=kt-NLb!Wnz6`%dRYW*Q|?_ z=>mJh%OjO}vVE$>7vo~Wl>ab$pv5_x~LEZFWb)CbgQ&l2=F+{#PcUgPBWPTSxcm zR{BLt`w!~WjM2?QU(dA8)QGl-l098{XC>(hbW@^DVYx{4*3M03oBhJ&FPDbeMdJuY z34v;O&GN5I$|5sT(dOeJbojpK(AVvBYvK^dxV(hlgMW6bhd_Zym!)JLvm&eXo?OSr zTi7>C8HulP7wo|wcYe-6+M-#?IFO5QAYX_q^ci@Kt>4Y)w?;QRZBY;FxhiGsd}kM- z>7eC|(WBn2pKnLOt?ieS7B|Q1{7D2s$uSU{%EL%+D;bb=U z&ABmfW5c$m*oT1@(f#*yocTJ6b~$aA2JH{%UAEHwD2V? z{*Lgd3%YPl#47!C+Z)!83)dS0G_6d$_1`RxH$Upr;u+GGn5am7L!GC- z)zo*10y8j60Ber1XXOd3pfpMVMyYe+Ww$8G~mZk=8o{l?nF@@Q0 z;$}VVa%*5x7+lyqga3l?1%Y3z1wyk}q9c)gR)M&wS?Z_q@WI(#3GGUn;5M zEjwCAvYuk$-_PN>Z4y5z7&hK%{5Qu;q&C<69dq5!tj(rln^l*?*Il3LeknR(ag9&q zwrvUj+u?(Y&+;#&tZ`FM0gl z7tW-|59#yguxqjO`F-Uaexi)CGb;MSd0{7b?Mn*88RQJ=wAl77cVo~081g3HwKbs! zwHD2u(Bb)FLXOI~Hzt7<3%7uq)_;&IdvW?_HzqE|x=q*lD40a9kcYU(NpKN&i;v9u z5+%mU%}P6yuTNl-S=Bb>@b#wGAI6r!O`V_Y$jHC>H2nllAxoe@$V1J8V^(=~hb)`l z&iKu}n=FrY&v>iOVrDnZa?YNvZAxLC)JqH#H@UvQg{k!)XPqnF*(W1(%pD}b<=(#_ zR3XmM0vQk%I5<*lHbd7+NpR z{3%tqaa0+1=LPA;4<;{g^6nNA-Op{1HYaWJ2uG;TR(Oi_93BFzVo`YD9aebh_rvd8 zXZu+%Wd8g3#9tpRZxUP}W4Hg-vVHjD=69claklI*3hyDr`RT+|B^ww$$z`+Lrp7Ue zExyyRzrrU>JIqEq0v7q(pM^HD;~W z!VX7l*kP(@#ZitY|oxRjDzY zYxk+Rm;abBUAcD4YW3LP(;{)!68O?w+lX&im5|RmCFbQKu2+Jy5B+D}mx4~>bMhv6 zxrO%Z?@grkZ&*`2<9x*Ht{DEW7l3hAz7F1+QH$6&7K2T%LSr=EtWWUK8#kU#>YDs4 z_}wqJnW*`oK;nYn9TjFh@q|y#${yUavSWMG)HswgvFq%SEZuCSa8~wx0KGEbN>jgq z$P!c(@tV#BJRYhHcT@-u}V>)^#D*BC`34M2(Ef}l4 zC-{isomVivOx0jM?4x@4k)n&;n-_-k*8V({n{XKwq4I*!b8pjyK zq1Jyss{c9g!^RICQ@)$#HD~Xb(YAqUY?60`d7Y!f@wgJ7N#!7agRX1(*Z34} z=S_{LqZo$%W^PZt!`aL;xgDL-*v|DM8t{)Fc-a>sqy9~ZOUL4N^_~NteOs_6|F%fr z_SuHLAwj_{37WwSypXXw@csh3S$+Bdb2Xd|s}e;TODQDkw$63}F2%*h9;4o*dJ`+@ zk1khIq(}I-3+*M4A6^6q0O!aRbP#|1fLJMZSxZ}oLlG;1%?49Da1a|7%4~=k*y47l zcLLe=QsL!AJfRR&e6N+_vTUa@3;D?Oc}55zaRyN zEs48DoB!Y@o<=IeLEwomz0Vo*#kqs&0m6-k1%V#P6@NUZFHk^dNHvfgTH3D?RSnQO-Mv&3`Ww|7#D*6Y)+QI(0+2nXT_ zSsspBfLZUzrDHQ>vhyVS4%kdNr^F=k+~r-{I04gUH4pO7U^ON zaU?{RdeuFlC9mXGL&U<+jl)kn0je!beUZk4gt%L-Vs_IM6Q-P41-ULi(}8 zDol)FI=fL6<(=K^q!Eu(c*ZvSmo!3B|Jg+w`N|~0jD-^;kdHVAfEwR$rh5|mMz?21 z3f>aydMK35Y)le46`@mKHJzb;s0kbRBVcJn3%iWLhO3_IPi2w57Zv^J?OCe0<{JDG z^J}IMj2?69o)p^TZu143TGBj8h59N({h84tq}h)y9i*R5Qv}eLeTX$GDk?frR?v^M zpr%uw6q2#H#u2WX@jQ9+q5p%<=A*pyr|=%fMCbHJA-^)1t6hLZc)sN$iCGGEW6^jFK%MmUc|Jl{6G|@2n z@bjwoVF*3ee{dBiV{>$+f-1ROT7|~5(yuI(caI8IbqC)&YM#*lHyVZK zr`SffWrYa=0=YTL`CEm709anTd22$qzH4yoIe#@fkr!J#^EZo>augWuIGV@5Y)G%& z@MyV9!X7~gJ+ooL5qjgo{6MGU%`A;=Wa(xp)u~U7qRx=wNCWhaJ0EkH^)|7B%Ll*K zEKj@$zjs5LIz@d6a@JGR>lQd@e@dJF4_!oajlP@E)yAY8S)BUE;y1n}{z=B-B%rnf zSL&Fz!42#SRAu=t?8l$X+Mc45|JDI}9wSgUET73U%SJy%=h3usi7Gy~47#k}DO~o@ zd)?Y-rF@4U4sgahB0Yroht8S#&=e!VBnIA1;xnuC-T!4a=HC?PkqGf9WSFItMlTf+ zTFK4?-*h#$T3->f;q72KDKwR3Yl^|E%ZCHc}0k_JJ(q6h59~ zpo9IU>K-e_X1!~3Rd7b;XShn6A`(Qj<6gFMOl(qJV&J&qjBz+13pgORH>?qE_{f+) z6{jSTK_aWsRpS>*`}@ikP}_D%Y%s(9o;#Ctif!<-F2uLH4x++^4~p5E-%v%ow9^d3 zG@kR9ZdO)h9AoPdRW`P^gGt@Yb?{)i%GZm9_uA7!sNvLRc;r&E`w~yf(Eau- zXZ5Ho=P3Bl%n##XPvJ!Ko41v1>jXPG5cZAwhg#nv+In7j$G{6@``{#kQnps@LO5KJ zDo?)xFyhFQt(`3KOvzk_M3N}}>LgViN9K&GG9?I*$2D4KS~xZ`p3LKau)iQ7F*;kt zYRL_V_Bps9xa80Au!E1|8~zmaM;g#_PAx*x!V(TL1fL)iewdL zxfaP8v-3IYufwy4Ja&&=5>aS6K%Lsi)iWaf_rNQj6roEK?}OQ}tFc*bM?ih&?B)Vp z2;H*n)M>DlqOkQ>D)#o`M73ry{7L%>zt*FNGn0L=ZWsfcx>)QNwUn@@IwP0|k||?{ z&YuXg(3udn3b)7*58kW>s4MuUD``ua;pqd~r9b3?>}|A(f(6>%x6hrlU|Pmv!DjMh z*%0X55QI7F46`Jt*L~j2z1ZKZFYoHd?7S?{`ne{XWD;&sPvB}5jtBf`bVm;%f#be( zPU6IW7Na>I_6cOS>%#zv1ptn1R%S%uUtmWv>`iUB;`Ea~RLhN=dSM4PG@v?5uWS*| z@;0H)8q9{T%a#Ru-{&v7^dRcDcXh0?)8~C97OpDzlFa)CWSq(MQ6JL|XK<0YOrv(J z@l!aZ*HkxzIi7QPNRJ(HfS;H|-4Cl6y`Aj8AFpY2wt+40cGSPG{%rg8N(&>fYxYN` zlOtr$@P-HC4_)Tz)``^pIj&-U~U$G^jhluhy?8N8JupPo>Kqk2V+ra$sj5G><+ z%$DWjfE|J0&T@w3H)lL%VT)T#VIJo%m9fa*hc%ZWaq8X{&*UN`v{&j|Kj1>XHwLTK z_hU2d z-T&XY*$Ut1Z2sFR2}eK^Bvj~JfX9rrpC2U&CPHSPIl6wlyEU)1XFIP|+5;cWu%l6L zx+xjQN|E9nsJ%olK*T`_lOlv{{;I9X;2{*gVdz(hZSdHqN?B&Nd#D#^dFYCFZZ6{= zc?!NHcsdN^wgvn}m=x%7WT23;I*JPGRPJAbXX?WnNHnT)a^RWG0?Zv41qe90bxbqp zKnhi4V;6eQwepxSsbUQ&OBIVz4f_+rsIJr<8KI8*yRBN7jO6Zb5@u2a-}hy&FP0mu z+2Q5h++$W8-do*bwm#uk-tvp>3{US~g*rX@^^Nce`iVKp6b0cYan_Z{I$MjLTM1OC zOk~+|*Sou5_CRH1{%gHgy(YFWk3)91GDlMm15AmkhdI8@IPJAxRs+ zI;#DjOG}u$>`hv)^8h@!8RWOy!;b-t?_Np*D#wgn4T#*#Q3nhqUCh&EfH^2qn(D^# zOW8RU;vI5{yLwZoH_(X~FlB*DXO+axKqjJpVN02SGWRHN@7~PW5^*anU&(1Unc^vu z24H1~Ht!EolK~KINg*YLAza4idtlZS5bup$VU?sJf=!RO$)m#=a{Rtnfla075?#Xp zVVrw3Otuy~9eYWI&PhL%cEaP{!(B-jN?3N-lebdO&Tu4SEN`x5?3_8uz*-5b(Myko zcp=!FiUJp8PL!k{Ywcpqv;=?nuDtPpOz5Vgpg|0;PTo3bR3&n<>nnE|dztVk99QP- zSMfi~>i6a2PT!PcF2js`%9>O8VKFqgoo<>2UJ?D8Nwget_Im0bTJYo#$028jH#1cC z`0%61-(%_b=tI50MeZ^u;h?I0a2m}e{EB0o17ldLD^IT!%bS?UrrRiSc3(D$>U8lz zK;U)zwC}@>=h->^W)!2~JOh_J)y`VB`Q5jU%Q=Bs&*qG`O>(MjvD}ukD~YnLoqB=j;Sm9+vs(=kU4=GxD4a+`bk$11 zs=u0O5VF-)mE2;O%$hF!ypLZ}t-n zpR-e*m1#Bm<<3@m(t^8;hnH6<-BJK}^VjdJGiJmlz$Bd?onUg$KBqFq)Vo#iAECXk zsnS$C;$`pKUyf@t-`jW%5GAt!mn9E8!U;qAkyih^ArRus9R-O0ILVBEeJkaE^+*8$ zPUfWV4{Mp&$FVwV4k6ljB?iahTzx9T-_KzJN)~Srj4Q@7yR@ z&z}d<)Kmz9cP|CgXBm!nSHp5T*7LGCU~012rr%Qdy>&}Aw?eOR6aAhX|B}H%RoRG{ zcgMM?FHpEQWewqnH@mP|P>27XsJ+#$Sugs2;tNPq8rp6xRE($)4;2Ks7)&o5wye0})OOb7y*hc?yAs-BaCSC}XQKsW`P-7sBqa6Lu1-w$1WZ;Hqw}fRlPpWz82O z+n=8^4j6OHsSdXGu;}l4DYORu4ChByZ z=a|H(ZS7@s5lo_}zNUWo6=WJaq25|#nc4e=Ns^g6?Cq=lNRa22*0%%!(sW8t(+;?` z$3F1{D!_cn2%yi66~(JLhIpTT9$dBAEs-rhbymB&)VPRZqGZN0TP z{f@AF}c`^3-nf5tjLs{i=!j7~ckd6W1T5BAoInNj@3zmF z5Yqe){-V8Oe+B_U4YjsW55naSwlmi;FJrHOlXdPsOsz!!1oGx|5f<93aEXJsHLo-~ zNNC6Z$mv}zg7jZ-mMj%m$C&A|F7U8MKk%3}X5jR`sS{&Zv6A3_PsxkoHO*NCC`*At zm@k_UO-L{*58x?-{I>Oo3irzgz~$Yob%gU-h#jB!M$VM`F9uma`q>aMq7f;5HuW4< zOtp4-tu}h8MZr1mXiE31+S#yVWrv*_mR>BZ-amN^H;%`srH@A_O^bu&4UkKjY3r=) z;G*G%Et6Z$ozfm`E{4D)zNqNoWn)zqe^Sz3--Zsw zW2Y?<%rnU87psh|bR=Ya8pnOGz$!oR9vdT2JzuFfdmNCJWgHLq_-{z5yU9yReLCIe zCB|wvYezy4XbfPUGl#MSynn9aEX%IU=EtJU*KwxkyB+b2_l^3-KB^Rmct<;P4Rb2X zwYs9N1EE)#&tsaRLkv6K58L%`rRE|)HPm`z6ywB~%cXWap#OWv+5JhwSd<{=77Kc? zd2DT#C_8n%*r8ZNmW5Zickk=3Q`GnWhu8jH-=qdk?sc znJbH(f(npPzlJB*={=w0*}N8S>JAkxW)`qf3`A6Nu8rMU_xxfY5=dQ zkHm77ybNBvZFfiQ`_gZ({+^86=Z^nMHLWIA_BZEBS7;qwMd9t+0n3;GnGv)G$bJ9)Alc*$L_T z?y{5x{wtxDO^u0-RKAuS2Xt##b^``H^;Lv-TQ4^9wtkGcy)j*pcwiPAWccNur=%-C zCo@malmKG?e{8*ZJe2+S2RsX=tSuyaS(064oopq`Xw%rTYmsbGmSml&JBpA&$sVKZ zRJJS`Dcun=2}w-JzD>!#J?GN>{XNeg&+FxW-C5?E>-v1o=bZO>pZ7U~awFd&xsdID zSw|%pgt0=jy>FA3T&6gODmC$r`t7;LTh3ciXTfyI#u)D<_jg4ss@qA9aQ&jPolH)q zQD%roWP+^_#tNf^jrjDt#?;*IGwnvzX`95cGYqKQ=b^gLqL_8IKU=N#F_3l zzN3vT9cV_IJbrIQAOk!B6Z5B$GU64(YXy4Py^kLEpW0FgmP?@U{u^at-WG`Br?jb+ z04#TY`;t}$3y>H{QOpVDT(m@9DniB@PkgDQv{k%JPqY7ub&LY%cSCS72tv4c%KbKu z4c5^eec~aBZj2h*MTV%8P_RgsjqQ&Gd}4n9BOWfNw~T#=H8Wo6lXQOP0%cL4?aH{v z>XymWg(F0nDYOXkGSQ`(>kcL0=oOY746A`i9+_|2r`a5 zqrRH@+*N~#@TXgL#a-euNz4>n6kzUP2GIpBBAZT}O$BF~_jVU$I*ZbL6Q=?cK&SW| zP4)DL@5THhhEFf{&x*Y+n*ILbZHH8IO0VqM8ji_6o(Eq1_Sp~ItZW;bZUyui)&#BpV0Q#4^qqpnv8Il^VR_qyFMzz6=U$5)wH}^KnC~C5VDco2Pyrmapt-W z57dY#=iX6RE>S1B|9!f|&Udskuua5CTp?0g-GmYmdlL&fXhk$EZ5PDt9Kn-SH#|7G8L;mpy=8Lv)n4Pu#%vp9yen*~SlxPVWneerO($Z9LIa zLg^~Eif%o_k;*HQ`TP%V)2NTRksZ-WXk7g8oUt{1dyiNWx)0^CEye7>Jc=7MHO0ZG zK1iGSdhj$B#+B7|N$2--8vKNz5(%aiT{C=pgF1qMt$a(ssZhP{0k8qwi7y1Es$BbIfQNPrG7b5VA<4-it4TX5}gUAJd}jAZE%@0J%OMzCA%88oh0#@cPzt!!ZC zTKh{+Dvg@&w9oDu9eAmIm{aWE_g`avJe??dQ`&Ez8yWMzy=t3g*>%h)_sj)SG(i6H z-j}q|zrgP^JI%jiyQb;^U>uTC$Pcq6uDR_mH8`}`-e(Q3RDSAxY>}3 z5^E`%x!YTOZ0VKxWk1?u+NZ^J)5O7Jh!y4-=DuDhDPK;?wpuVNVWJHL}*4xDE-6QCK)ob#y6Jw95!7t@9Ny9aKq*&QkRqx zevYJ2mNjFKx~oE)3P3=z(1xixC;DMS9@5_z&wnG@?zvy|?+v&TOn?d2>5S3>E_3ZZs6zX$UCeEA?>-<+tm0 zZ^)uzrn>LdyGS`roR8Q-I=#tA!3Ef#ZPC!$q*GDuuo}k~xs;*IBzylPnLuN4x&?4r zIE1ST6(FL)fA+AN5i7F>@uE~cBu~VQ^Q7{e!1*`)N@_0AC#ypmhWWC)3Re|hrYN1~ ztcF@A*tF~Lzz}33Rz_fj&EV10tvSIqF@Llc1e~thNt=#&VpcfjJf8>h~2lwhnV>8dr-wY4HQS31mZ=!m!e zNs#@#hoM9hX7JK)C?FT0Z&dFNKq_r7wC=q~GQSk8w-27z`QZ9(_5bZvhxl%Fo=agM zR;YC|<8qu}JG>Av^wpsIvh|;d&X}oiF2-V!(Y4VhjXeS0UcFt*^A5<3!QpuEO;jW^ zr*{if#P{YIbV-RMJfL2)z!hSGg%(wrL5z!x!f;*DFU;A)C5<2sso1ilI3AKl;!YhMpAEH^J^83tzp*Jk(O@b*HYnk?PvhKyW4CUiUEo-C=~ zA5^NYzU1#D9Ph?0@-cats8arq2g;0Z|KXyGs==da{T4T~$l6Y`pvBXQt9VW>&2lqm z;oQ+G_7t^5<0_iwJo04f$hVA3ZtueeVbhLAnHvkC!B}!9JFz&(WA&+f>Ju-SUi7zgVvZ1(9eXXyD*CLBnvucB+kcjG z+||2PKPeGOlnGQ`c!p<4YDyUwWq7aY>D$}5C+T5{j^;p&uVTUaJ}%CQf))|AqNl>)!v>{ z4%N|`I|3X>p{+vj3w4iA9Qc}{szM8k1`}=kGe9e;&!>_n%iFd=+V+D>h+^K9)opEG zkO;d^?W+R9{3KqK$I4{rJjz>p3{@nVc@dD$R!$!4+1!ebJ}U@$fxg>+y56 zt6aKaB4fWhc>uq0kVD88Cd*!X2vZUH-AUfFL5NIKLvxxkQ7Ei7FcM8Nf5PFuAn1L?_a9yljj<5A4-KvNu1DD7zO2(uy7lL7@(WVjV}qq~9R%NAy8yB` z^P?@;cbFH(L1X?6Ay0-fb(viB46C7KAeyHPB@ZCf3#_GEI+GgJUJiQA54bw|&( zTp7;_!C#8r#sH<9ENI2lm+?@`EXTNlz z&vGn^x~{j40TpzCmIB;R#{GG|dahu@UYAwOn0wzd+Pq#nF#?gbIuCmw(m?9Ix=0r_ z^YHxn^xvUh92IX2T~PYn;lh!12yW+^SJdN>FRpL)JXKL?%xx1zJ{WfUtVMk<>xBOeMZA9VO9|3W zppy&bm>HYl^HKo!StJUuMj+tSQhd6$ld11b*k!HP%06(x3admE*?;-GLQvf zU#0N`cEDqCpgAJen16R2)L>K_OGxzqGO|9P3?VINpOZ#%+&mT?R1+plB4i_8*4~jG zSTVw9{(cxeLU~ou&UGcuq)5b7B*=uS}~LbOHJO53i6qhfL!=W4=Uzs zpkzeY1;2{jy$=zk(2KvvNz%EcyaVk!*pw>yG0Klw`)Xu8JlZB&QtEDl;6)+R2n)sDY5&+$r&+c!;$EQ@vK# z;_ZU4GVo{YojVt0#)5v>0egvc<#`a9tx&4Q+{q}Rt%1u_LUqRvf0*7{i1Sroj7(uC zaeb@+lB9~x+TPX|4YZMrYX zF`o=ddaPm$cU)xdP9_KA2K~i`7vsf2iwapM{q>-Q?<`JAwYHkE{;}NmBN_RlC>c}L zQr0o~cLh}K;(9jAd-s~j4YYacmj?CwZU5G>o?vNX4K+x9R*ktGlyT-bek4>>Il{NN z`K2~^Fgkx4_(In_9n3oL;pO32bJ%rr(}h~C*eKDUM{lDhvds>94Zrhlv|DsK12M4p z0+&Y@7NnYiod(^x_t~z`{M#nMcJ;TK`zG`W&Uxi~Wl-t)f3{N`#cdkDe4l%s(ELP# zjATG`A}6X%noDFw>f^IF8DV^Ye)t&dPQG__>m~Vm_2?wNw^b>=FS8`c5t9;Yg>f0S zQNGWfMS5{VUbLBNT&lgJUz}k74(kaAPRfp@4-ikH-eD&(sARIcp6rgQHkZL;2C#~3 zpc3PrMEl;oN361kvI2o}^8`i*oR^7*3e$xykob$XPF?#3FoB~FKzyQ}t&@@yp1H+) z&qwVtW7$-08aM4Wc${CfbiHoczPCQwwncsiyu*C|NjTt1t|LnuPh1QlE?WyWSKt}8 z05I$rJDh|v_Z>7jX%q2hpxoTU3esGG4z8Xwm56yyy4FJp<5|pV=okEF+Bj9Ueb#s- zKo=#-x_-oFV-^4L>vM4)9=HAmmAza(VEaU`fY@;^g_^Fq{%8D3L?s&ahYR z9CObwC#DTsy6W8Wh{UFD>5xf#<}Wc$z1ER}!ig`+=nx7sK|7qtNun zlQ!C8E|GWf!~96dGZ~R{0srmRd3IA_rn(vPLVVvmum1gve`)juYli^*mCb8NUz}$A zB23LJ_|hmhMBJTDD2+}f_BNB=&M6OKla|F6eXsgPN1nSHqTSvamJu;h`}R-w!1wsu zQHa`#E3~ZNt@Crj(!@yq@@K`fl`TFU0>n0OZ`+_X!JhoL9YIx}Z;WA?#}l2mv6(x*T|LYMj(`AmJRTaU7b zODu?6N$_52(w~i$4^pAiVzkreuU#8aW^_V?b$eV+Hp-ST?ZGU z&o*zU`j!3DK#SLV3i_x{oxS!4ukQ8ccTKaB%%pD`Ujvn^+f+Ob&D z9|yBN|8Q?5sJ*}P{EJS#Zu;FX|D1eY^X1#j;X?}})&1_5)c5u^U%EbgBLkCyMId>! z2J0dKlE~&4v_EkF-=|UBVDLAGNbksB31QNrZ}mehg!jcc%--NJ&3S^ZN>oW-Qx|Tx zJon{Z`MVdmrs4g!Y{*+_H%GVeQK!h#q;-hw;h)j!zymJeD)$@eGEj_c+B?#LXPV>T z2GqLb2Zj;3xt;_Yv8nW{^koI9DK39q<~HVTQ!#-A;1jgTk7)f%@6J6o5PFC$Mz3RY zw_cHvxb{C)$m3cJytGF+-UpX@{X4IRpBvgefF==rj3Bk!eVNT5Vv1_*w{DKal65KE`1|7mzC+k6;l90uv~rFy z(-YEqO_$M4*eH;BIX8EcII<-`Z%?cI-5wFIcHL(_**VRl}*ZILeVfN2u1KKQt`4 z-2Qo-b{ZgnUP)Zj^Aa|Pdne<%+-2RHUMw{I^6>0?zc_3E?K&IK8UL8?8Ae&5yD=O$ zZ98k&un`zBG`B>NpZU2J^q5O}XUa5o9wBXvIhZQUJ+1?3E~GEipo2N*ca#d1y4ScL zz1;ESM&h64vAd)9cU;r(wgr^-&H>woD@IweR^~2nvg#Q1nw8xTQ#1MYi-!>cw=ey2 zCN{9q*vS>4a%HU#@t+-+fsdR({OvHFkuY6hk=UX4*W?glFfcRZ8KTxnAIi)Z$LH*sJizH+5>tG?>uf)UM=4?{=i0qfTivg? zUugSUuC>;6Fg@5^#xGi-10maEzPSrp@UgynRV1%I)vR4GV(4NlHSNnt9lf(djWZV( zR+2DI{bq~{-2e3zH`1DS*Pc{^o*8!2PnQ-ER-DU;MRl8M3}jfMM=&F3E+{CL?d7BD zUvx2yNKodnp>)Q%(b>-HXX~2)M|JXCig8LX{?Zq9McgR}NYpFLJtlJ%6NthQN{-tY ztWb=-(@CC*SKI#ZdDcB&NrC6)?9h(6@KQ&~yPNa~6TGnBE1zA&P2!N;6*1)~FC z!1IkvfN4}<*MBasDSr5LB9PAlUF;v&MB5!tqkYJg;@N5qPEH3)?Q7hxtqTsk*S)aa zRePyu$^BfrOMXNGI_&i=FRHNX`j^(Fi6uxqs?Jb44u7?qmNSW0CY0XBjdo5;N9NJ%F1d++Jz7P;DcPfBO<>(Bp(N-hIxo}z@)c0 z`urBF=d3*vC}MdzpIp@avI2l&^j5xNFd=3k`~)M7 zeRoD6NpuyyWAMF4Tq_S4ncfbp# zc^_aV!m-5;-=A-K`KYYTBJvoBA>?_hCM-vT`EsWxTaerpMA~1 z$@)rfVvuEfblZKCzA;38Amy=pjEMoh<>)WYQZwIw)N<3y&VD>Oo1=kcd{n@DA2V%r_UIuo4c01Xsikxc7SIgf3e$2lL}TJi5i)+a#5tz--I`X$ctE_5 zeG;$M5>GukcqC!mvm0v!Pe^17-i}nx$7$^Jrcs28@~=Rp?-TJ>l(~`YtM{#yy7Y$V zC5W0d!v8a^=VTQ*yNz!y&46~6zQ!w4aH`>!P3Mbrsc-rZs9~5D*do^4n6gs~tRTi# zV5rj1&QH+3>UD-k-6BsJUzqq>G#flCx_-T!!fmmn#=OY0@nu|KkZ|Gprc&ZYxa#1S z(~$ly&bdten*8RiWt;MZZ8x^eAR^ak>J;9$AFD$xZi(wsEeusy3igPpc0w@~8QU0G zS`5T+tP6r1b919v`%$t;hVq%Smc8~va*z@S`R&p>)}@gal0{t~JyyjrSW?+7Z6pO6 zSZqg1h{6%=ar5TabTeKZ2rsjGlLXCv#@tmq7X@7F-#_RP_S2X0-tB#GES<1-`iAUW zg4dv?ldSyoN_M~Uj5u&AgZ`2FmgHb9-Fv&PTu{ z^&R%LUZ}gPqSRN;I)0LmmR@MhU(RF7XG1;uP@*Wv0aR*pr77fC2=4}yp7aUELqbrE zym+g0=;4jvy<`1@&vh;foJu~_*RlHJZs8*-tgFg2{g(CfL_G7Q^2SpBeb61v*%&e2PZ!Hpg$*+4WZrD-|AU znvc*oY|yLBFbn@l;5yi+fsg7znX~PUlw$@h1>&sq!I@shB7mhC9q&V%pHyJgxaWtKLIi~G+h?l&#y0R+%80+^N zt2So__G1+#MSfb0t{W~795@X2+xv(`i-R+oTjrc5JuIRd3GwucPTZ!VBE*~1;;lwi zhpMv8`?d;$-kk95=l|D;%_&U>>x}Z`t2s4fpce2>nk)oi>|S8H@3uAt+64_qisYsPI&6t>Oy+^-uZO@~^gElSh!N z*Pu>=F=nP6@0Bp2Ke-X}2mkvN<4EQKHKVBn>>(1Cge5QhaDPsG!P~PVH;VhrnH4#2 zP%=pN8ZS7xkrSyw$^{FvUd>@w%9~a#(l)BZlr=VjQSQT%+-7v$mK0q{BY-) ztHm0xyf+y?+>MC)a&zrY3RcFmHxyGf1J~gAa0bS!Je<44a7b;*KWTNOH{xO#J*a*G z-KZaBeorSlC?J0^7_;aJx4frS${|ESx=7CT{YG!~N{zf!nw-s}jR1`?yOC>7XV-la zmj(_cs8sgMPN_{hFigApZH?^J<5fh(5rb{l1ULY0*vCWi?eY#LG&#GPB_LSfw1=he z`2WXT3xy8*qMlBwCvL~46?_%=oX8}1C}+u5AG5JN&YfO#>z(`KmyCfk`33#?wr@Xi zJoxp&wk6=|`$$nuG(d?>e=FOZbx7^x|DVToS1W|hOIa3MRLT8a8**Rms1kE^2Wa1D z^T5&VZby{l=6{-vg~CNeMCi8!q1{akH3yi01^P}(0a7gNfhxy8)DtTb%m(;20V^sk zqWGkL;T&Tguz!?|e(Xz$4FKl;6hK}A&6gFGM`wEy`^cbrRt;NvGb_sWDGDndLBgYOX% z{=ol?jY}9>Z2cNsX)j?Znu=Ja_ZHo)jxWnNrU-={r!A%GW9x52mqzr#+SnDbrvM8C zo(*Gg*}dCCDEP)D%w8DDE#q{E;8J2z_uae?ArAY?beZiy!@l{9+DQm%FlH?{xT!?S zxp2-Nt6LqU<~R^0cp>~xY}@TnHf;V5t9Np&G$wLqdf&K~c z#5?C`8RRK&T&^M;timE99gWLa7t{t@Dz}}GjYM8&$W{#qwtc+NsW_{M*4Zs`=4P`o zE-G)K6$|tH~|a{AzxUh@J{JNi0$=$Q<$V>gR% zYW+LXIiF3kQ|b%qbtHTvgsS}{?TeR03*+5nomB1`G=mti6< zaFaSTCxx;Iv)4-Fk2RMzQ0t{5hIJkEY<89hdUmE9v2TvKMi0GPFv6JT`9NC<91kjv zA|7yK2{q`=u%smP*E^o>t$eJyslFO_U%wPf1vW@^Bnd4Tf1|xqdPr`U=0qItjMA?}&@&Kv^+X7DbygKKi+}KPC zXe=VgAdUZ%r2sO;3*-{?i}OB`iJ566=xt-6V!une>QAGFF=l|o9e(f3{7>n40xJ2)9^O1BkCggX#`rBit}@V!hSkBAbI#)&w)dxVW>Uja zxhGM7qV7?C`$rh|ld2M~EwNFx(^ab`>|#Q~NI*s4Yh7{+DIV@oTy@7{z&hsgp1Sm4 zmH?FvjfW~OWSmnanQIuWaQ2#q`bY?t2q}BGhLvXT}vxIU#Z}&!Q9LEMqT(t!Oye*zXfvh%wFS_ zU~N`T9;lqZ)nFvSi4bsV3$pgUWsgtFv8ljg7J{qJUu$m&>O z93k(ca%y506`78Ze1a8_qDY|Aq3-y60$*<{8-Q>jR!pIt-!`+*%)z-wlM7Wx~v+-+`AD?Am-d+;2R467!(q zrda5Gs>AjF$PoUZzes6n-(zCoC$dkLgh`JwbdUlSoENo>Ey1J!&@~I?2!<#jdtL%M z+akUj+s|nMVmfNzuEkVQ#^b56WvKc+o_m=7NDGhhwt%FvN9pIa4e-J?6t&@tc0UV# znBz{+Ug9Y1U#Dr=kUF{jhx42IOnNh|o0G#(Gf=0&d#fSqX)d|z`SAT}9N_{+?r2n} z$RK%&FgxJC=iA{7^u2rcC~D`WOrvq1WNJ>KLxn{iXZ@BR%REcK^l&5dB8VNQ?l1x|lPlPb|C0jh38hsfRjw*s+8gL8h>@r}& zcWt<}43~p6_>-_7*d81+WEDCY=vi^d)pJpiy2Q2y#f^`O=vRX9uEWXdgutioNOj(# z4vtkKi{K>8_c)-|F{=VqLbEG}&Hvvh)Yw7WKw$TIO~@pRLJPMl5U=06*jkx2`7@cBdNJuMHDAu*Q z8kpegb&`@$hF*#5_sVs@^kc(oT>I&dS5GLCa~10}Ia<%?wu>sqegCHg$Yc423Wh?^ z_}0WtpDcS4_z62^yJqm!;foamg?RkaJs%1poE5zPy}X;a-Cm;Z)1@^=V@iV%7n64I z?2l`oC>CrdcB|dl+Q@d5nea=cyyDmSR`y}}Lg)D`8wdM}gZTdh?{vXvS|Uw7Tfb8# zgXI+qbWMo!J?@}6lI=Hxkuy!n7jHF$_D!WrGLk^ z{}dbG%rgtJpmi07puUZd^n0DzD|li#IrISFdPy0Y7O3V&kR~T*PEgQ8&@3AAU~GdS zBir(}++)>At#^WyZ-n%8HrDH(&b&HVC%G=s_I`8{J7;VQrP>o!Dx$0oiG!Dvx4gGl z+s=4#PHg|f`d(Yk5APj)$X{qJGqOrrM zq^*BA6e#SU+Be&K!Ck0kS>)Nzc|Kz&dj`NeZoJj< zxLB&%xMSHO;l)~#TZNces}cF_T=WN#!Hi9-b**pJA_o&3{0?;oXIl9fS z`8G`+W=CzyyW_#uxAnsvn61Sv%o?nW7ys9kA3IxB96${ zjkE>w3jiW(Etj1(R?Ca|XazJOS_(~W>76?1Hto5;m5<|_3bRwaUalwBuiC9I9dl~& z?@rR0Cc37epzTIbBrD5bO7xflVFI7x>y@jBr~X@q2Cif;{+O&XmUQbkKXuNv9lbD8 zf+O=AuaqYwA9OnGw6FB~dauCDGvgFD9cJ|38$fzC|mH(ch_@X{c&2SMBa>AhBf2Oe_0gWA{H8#?cYyXtMuRaw=LJ}_SgRK>#-!6 zTiNc^m_+nf&YJ}6_b+_RiwY`%Dz(22dB)poD*NwU_o@@{d?{R_RN~^$%I;uSd()cZ z;fwqn)&DscS=AEEb(GjW?lVvSNQiuV#y@7NNl1aQOF<0Ck0_mFHwI`11xi_&KJ+Hi z#@1B{H6!Bo8$4qxQP00yLap^5>dk91Ow~*+u$#SMnEw=CvS(c=YL`TL`}P*OyU@Fb z{h;?74}yHGdi;Z?%u=sKY-?qOG^e+m7V7hrN>72ojvl zz+Og=cP$;tCr7T%=C9U=U0%0Y3qH2KblL7@6Yrkb&N%B@c~)f$-P+x z2~tDO&L;$Qk_e5s*;k%T^sWbkbG?C=l=7Y1UfT?RF3f!SSxNBk1LX188RGBg-9C4S ze&qYg_wY_|i|9oTSF>FN$jMRCp9-G|LH0XEQzvOY)1Hrri&joPiD zgytZm%qU`Z{VXw9YFA*foHOuH%G)QRq5eQcH(+*kaD3##@7NUV+D*Zn!fRKcvrpDp z3AKP;0h(PFzmwFCd1w@ISs@G+dd%juluXl|)pA>0y=DI3m%;8Eua>WJ2)94zLWH>n zIjkDH0(R^MX?)%YXPcufjJs&i;S7d(3u~w^fXjJk*6n6 zB0qkju3Stv5PvIxInnMYawDZo(GBh1rKccX^w1_@V@_RW@{kV>izMMzC9yVjqHcEU zgU_h5Cva@4Ce}kbj9E?t+$^~8+_R{wse0pbC1`c<_S$)WQ|Oyz z$Cq2%>)M*1Sn53g#L}E~i~Y?cpJ_J+7jtucs6&;$Y?0WY__=g#TYx{SmObvIXNN^| z*6-g3_i>4RpnkuldGU8Z^4H#^`irdXO2rqZv+xeq)s$hi`hdR=Lv>(EJ8^?B@0SqisxXiaonfb= z{mN<2R|nZM6*sENzRgyz8muo2|8DnLinVgbKB!!&(pA2k7B-`tS^KTT0=xD*_SWwV zP)xV#7QZsA2y*DSyAeXctAPO^y=4}(b)ZWk>0zjPT6v4L51Xs~WDP{2I(@5mF9yV; zbxXXoYS!MBFNGD$4Src8HB77qb&Ir%CWrNhY6S(X4{jH+lens7+sg~;WOH8!7AQ?2 zPJqbs_3oebY|v{NOzl8A#R$zwaWrYNJ5Vxu3f`RFJn7O(a}{y4(4_*2D6%?9in?-0 zb!kL(N~f6B63w*~_C)mZ2VKXlDnl7OENo>fFCy+w*K?k0txI}L+fVOp|J=QszW7Un zIqD$OW5+=1?VEll@6H;fY~Oel05_3d`7qB9vQB%)UiBWN_dfkRk@+Bgx}l0 zsZD12w~QC5<=rI}j_MYK>dLPEkEc%okwuTy(LVW9I$W1u^TJ_q_XY|YQLY%N9eViqc-<7g~fu6bh=Cjd{=m&t~UF{ zzA~$}uTCC6ILB7yyjS?3prN@zx3N%Hy|8~txCmS0PCsRL*0SX$b0Jd;hqlfGpHGOG zwSNDqZ)+RV?LP6y>cvfd{u0L0+wx9Tyn}R}#?P}a@HwVS?3)T_b{iq4E^Vk|?>L1A z%YUEyc0%Wk2(&syEhLUwi07(5?P)CKy<==o5eK2f`C-H^f-J8-NjHvL%+lZPZLg4) z3DIRuB#9L!QHgtI9pAq?b^%$&AD=NU@zZI>U=Q9>{h!R{Rj@|DNn}G4r{BOfWXcm& zM;#`7Ar|CCjv_={JWf2FeAMRFjUeAUU35%vxJr7RH6(n~LYNca0Xj!O=HRu+I_9@x z1ja=eKjKUa)lkgopJVYB8*@KNDQmh*0}5X12b#WIatJ075!D5|w-Zc~N!jG(zfI9_ zK>kf*nM6aUvUuKCHjM3%5+SG2?~JVfLsa*sSD3_|z`6e0u zdu>ewGakadQq#Q!#$Me`P|YTNf_#52fJE$Zyd!(B~4G_q2aP3|*%B?xvahD42qdey;5IW?xsH#Ob>7dl`KT z7_Qc$4jI;LP26=wa>N6DMZDoSmCUE?vs+kGv_kAhin(9=Xv5kg&8xgdGu3HYDVzT*LqS0*2^;On@%d4%B^F4M4A**`CkOJH3Dtk9{)mvyWJ`sfcsk1Ve% z=9LC>?-mw0<79}@MLA$jVc9^F)tt%Bz#~~&U1J8h8$daq3jap_$2H8^|D5Y(6|@Uj z$;2;JCs5kEYC|r**}v&`_iHDJ`5TY{e34z}^y){q_>Fo|{f5BayfxB``k!ynU}q}x z5Qqo9=;HOHzVF*?Q)$kOHN+*{-^DnKOw^)KcPw0Itp49>{__Ei6xAgO_CKok^VP61 zA~)A^gbx|I!>fwb1y(2WEH^TkW}K`41XwKbQmegPOYBVAar+C$eNB$sc)n}BY>QxT z)^Tc^5D`5iip~(^F~jNKu=b5(|MGF&Vp1zq!)Q{OTr#De$e~jvX*f$MH2%pNW3UO;51$nP~_Nqza5-;lKm{OC*XKTPy;V+%zF zKnVOHkCJxG?wD5JPasmIXje&3br97Xe1%MOBXSm4y-&>m0d<&%GC_JxId}=<8jZMH zb2mktAe<8MIew>8AS)57eFWfudLqdAi%DC(i(1r#P_`L+^8}aoC(PMSUVCxB)>0D? z2x(jsJ|V`=cV#M1)RgU>$U#!wzL+0ohxYx7zV6k2J7T6uVCHLF_VsU(Yn6hHfD7{E z7dT*Q4{}QwH>O_dQ44N6Y%mPcZBCjZt&n&OBUq|#`E{i~0%bXiFh9w=w?@AKVegMt+^(7U%ONt=N_8I8{PW&fh z5X}Ui_lWi{RCs3H8PAbLH|ZQf+J-it0ql0pk5P>LxKm~b;I~*nv~Sa6NmoxX0I~2V z7og}^`^jJxY@UQ zS-i#HWsPJ?&<=Cn&J$xEW()#rV<^m~g*g$+z4cnCFNu0Zb00qQUk_Hhty(zY+p{A% z#6!EQM4+4M*o2?xeV_@=c^%lU!2hei|M=T_YA`WFa|FxaHpl2fBv8v_)4)La5|&J8 zsNEfGoKX+dQ@Xd~0o35JJWRAia z7$szuV3F!K#r(V?78wWfcTG*s9%2$0IY`thI|J%%qSz8L5~Vp)e=`xF?nyr&KUZr_ zbA-BAKM`gJfi?LVdR{oR;WHzAN$Hx7sa4r9V_LAvMW$>h%bi*b=YNGeBD<+k@w2J* zE_>>EO9zK0#GUF~(Gcqm^*Pn+a&RQXn?H1soThnW;Jxs(Mi&2p^OA$%+|;ZON``;k z0AKlSD%X9Bq#0!KQ#IhPteI0%F2=Xwm#{o+^bYqP4~T%O6E#=8CICD5`I>vf0&gYYg7BYf+m? zggBX1cG{POAJSrUZ!U2ivcz~XkTJm$pF{>-NDb8@=R9eOj0PwR^H`u#3=90S9j%0T ziB0cd)6z;?K)f$K5Lw=z8zdw6PUeDt)TY0^*~eB3S`ogxEdYDB_~X{ z@PPlns3z_kBMt#`twr(?ke%PcqT;RxTP9}V-)F@gD>#lL&gcy5BnvR1#H?2^w5C=a1n1QAzFVPzcGFeCGm79jlDLC6 z$NSdZf~3xEU1&z(I5BAYums#9u{%SW_JO2GW{%o(Xy_BDasRe6%IL{?I zvteej+(My9-lpSU%Ce!GrRuYb&Vhw_UgyX14aWs-F_C}yZpmG7yNc5xDL6Ur>}1RM zgiQVJ{G^5}X6+Xfp=|ddfk;Tw@3^j0EJSomu2i;Qt}05|{)wEA;spB+o<%XnPlPIG zw=0v*TPVa7qh3J~)>H2g6Iz^*EtVRJpw(lc-c9v8fs5JOx^n+{zUbl>7{UPA8gveP zEkvFd5?m?oeab2@jQ8rJT_TyMY~KQXeHGLrfDB*@N)%XQP@;Q^9zT@K`v^Q{CdEe) z8D%Bu{BJQ*!jsD9TjXSFDC4J{z4PI*$x`GoUSw_E`Ow&3G#*n)mqGdqwvZvedESLcSi)E;!I6U_$RP%KV% zcB-E-8!-0NKhz?WV%`7oL-UgdI>nIgz>N~`!c0pkt$TU9h1vUX!rZmMlFcPZkdKq& zI`#_u4xu+3ps=@Jfgyxj4xnIM9>!b3dppznavU}K5zFeX9ywr!{I?^?QeLjTA%3#8 zJia*}45`T6P}05{c@X7e1w#6h)8Sm!d5?G+CW* zaP&RjHZO+$#0IJa?F%jBF`JKDPIooP6=X$GPg+@=X{&mS6;StO1csgVu@x%kYL$1t z{G|BtBj#WkZQ4SRSkq zHjLv+oZ8#{jHd{)K6puynF6N%t>UCFsf)N(II&)oMUgEuLWm#8jQ!psV{?z$8^sy5 z@HYlC77R=JV=v+^d*MwGmM3YC2^L7=1tH4?IiP5-EIug=N>Z^Jv+S!p3F^ZD(aW14 zuQ@Y{98dlYA=4n6y@xvqj_au)0FEI-F)qg`39Pdu_>_pm!ms2Rj0-Y&qtn2dhN6q! zAv!MYbB%rc9A4N=%IiS?IKton)&n+yuWA?L6IJ{D-`$XO9+;r*g&}lI#OWn;9jk+I z#Jb4kEr`rbm2}n%b2?5BbcLPE;} zP$CfdxiI4(kRyt+t&B^r7d^mZdta%2tKvDYyXK%%gYBCxc?{BQKu&befu z9}ZWj2_}EN?BSa8>gDb5VKqS;Th}X|*{?L$hHO4}9xdjyR4u2p9ML~6=lRlZeY>Eo zjjLx1`XgJRTj+>mvz_9*{0DwI8H0`++@$48BU}F_)pK!{3?ayk^gA!el3u_N_Y0)A z&w}}+e*8}hph&pQHC9$brWVEQ44E0lURZeZE`2q z0azh{-<}03Cg3nn#BiHd+8q&WU-#bx|82iK3|i3AC^M4%X4H=)cc5hn18m&bX4rn2 z9u|6fc{+2~j4h3ungL?BJon%Ju3Q66X;^%;=L z>-2 zU}La~C_}c&i(e{HxW=5iYu>M~9+Hon^=9reRA?kNyL z#&&FyHfieQ0m#2LGU`@ho);AItFNCpF3m+WzKq#wp|>qhJQIEFzWA}?fFsBLk(-nC z_RPHbFyDA9i#-1}(=qRyyGJp`UhHJOqsB8k>HFfxW>+r6)(c(0y#Js%QnR4b?ow2@ z@6p9)qE79P4$XujuAmtxX?sT8=r&ibHkMGfLWn?J4GZK}QVBvYnm560BEYmcJ@HW7 zeh2$E)U*!>%rXvgD>)3|GV<<4L#kvvJSe=imXi}E14_EuzZJXr_}U*fZ)v6%oKN0D zq0$2|ocdT9rO-A2-J^)?to#jD4M^nwMl|BXKEvmV<3EtJrn$!qlJHV7WZt`^467Pk z9PaX_Hrok|A!KL*2icJGY?AYL+_r4lg9`sEA%3#ua>Y32$ydxI9C#VC_COO`BCUAbf_*%KpKE0io{>3`n3pYQ+oczE2$ z<7Uiz=Cz#Vd7kHa9{tE+KQOTn!%jTys(Uv;h@F(WZPU%^cTv3kPp#1aP8e9)!m8@8N~ECH~wojuK>n-_Hg@YLhRsh-(ad`uQ?INHsqZ z2#NjjGFOs&>0SsD9DaULGqZ8TggAZY0>YQ<5$EKM^P!7`7PmF7T40f}I!%8CL2myP zxlr&k^4tnG{Wya6lZ?DLNKZ}CH=@TAg1}S~%gyc(atg@L`{;WRA4oZzhYE-BMX3}Q z#1=5ALSH#X)6Jvhg_hab`T9CLlLzbxSM2%x^54o+z6pILWNBV~YvjDN|LAGa(?qr; ziR6?u@S*pTslnlR$)?6<7we6)_P4f)yiYAz_z?B-sZ8s?@tXG@w%=F#vjd5$!iWf= z@iG2%!D`GG@&0Y-A3VyKfq#s?8kp&>!IQjiA@o)5-B_{i*1_W5*KT^DcBHHEfQIa3 ze2uW{&=^au4oGEDE}+5zV)k?6@rKkAJ~@k!(5;YPD@+AD@;OITTi_Nle}l4VGw7j! zH%AAcreU9&;w>IvJLE0khD#wHC-wd}Z4;A;?|W(M2W~Q1o&a0lE7sVwHYP)%2&xyW zjUmLPzB1$rrT8c^w$pDTf425z1V-(EVhO2c>U}Lbbkk4-(($DbXi`+y0B15yun3eS zoBpn1Gc?PXk4n!j~{6i`Q_0dt-OShCpa}pd8o@{pkm&m@HF=yRPWE zEkTL$?kiO6Z~kg*C~wZ+EypDE`SjpOus!2>p~H&EUEqK=74r6zFfa}z9!ha`s0R3+ zpPl(>d^6#Qt8f_w(QMcQAvg3Y)Uhevp6g)`?G6_YOd!Gu%%-^EUbC!0>>#))U_5u2 z;B)d;B$4JimJ`5pKU6YNmVh1-73%vK$(QbWudOdp;i4`5ZQ#H)5Y!v3+NJOfH4>Rx7e8A5U*~_lz*;eBmGF zba(aW2{^T4R*7akF4&$R?5%gGi-mOdTw~64)^aX8@7$dOVZG;%IwtfxZxHX)`aas9 zy&N`mW+4BxrQNea9FsV)-J(HmD*yvFB76Nq^RS~Bb)b|qLbEDjeHSE`DR)o^~Q> zdAHA=#w8<{K>=p>0dkC*RD}cxiyd;A?)&4+ZCga>SttnyMK91cfH%sI?&a)azMP0c z(hn+O66xB8ld1O6pZSEF4;J2KU>&opp#>>Y@^ZEkhkFqzXL#jidNg35Wfej_LW_yW z_H;dn|Fn{_(PdAgL)_WFbcqV>^l{)|UNp^|T?XxVCTn9As{0`lYr#*@2`it1p$!~w zBri>k4S9fNZLCcC)SnA|)69+k1l7Z9C})Id;zuC|&hE+L=)&n|pIGxg z-H!`0Ox>rAl_cwTXXAJ z`vp)MFf0x{@v{u8N&vuGEa~@upF6PZZfHR>@3JQ}L&1ou8eZ5^uCH6*hzdxS zKJuOTX7eWb?B?ejc}@W^QF9=p0w0!I^O?5$_>;`7K@S160O=t$-U@zg@Fun`1sqp0 z;U}~BW0=#%nT@Ku5XV>@!UMbhrGA-N?dG!4H76;UOKDzDv@D9Pma(9J%Z%-rt?q! zlmAX}G-nu@&j0LNOZ%(u@Kr;1S@ypNx!XV|1OB#ME{9XLA3ksUMy@j;ZR-^33la!P z+y0A^vy1bd+|JDmiq;xaxdxLi`gT+V$Q~(3RK9NZ=%U7#w<5JVLWjNhjH-+!$T4r8 z*=gJzIJ0hSs$51QzVBLuiRY2QDUG+M;O&{L?+G1VRPMrDZxAd1U+K87HWvmEs;j$e*El{S&P!8?nzL z{rYQ|#wK63iOHe^DH(ah2MBKj;> zTI>$HRgS3sn2M6Ju*kAdE_k}1{W3efC%ow|nDBDJJ9?+Y^W$=cvG0jFanuu>8#Ua2 zw>D}#u=&1^JmPxv?v!-d@D6*Qe_PYPv-5lWj}DHnJ)4R0N44TJd3T^(F)K!~DFK3v z63C_0R>1*|}Y( z>WFb9$glQfV&+BikL@YN3=;QIZJ6y`ursOo6Kj*V;EAQ;g5*pqQ} ziM%jS3kh+u|4Bxw>=d)pMryBir)K;I6?XLXz5n@A>RsjVoO|ihr4lFCni9-ZhDdB$ zmN~0ks$H1Szs^z;wQuiDpR+)U&MmtQ@Xtdsi4 zg5gUh%%<_!ANoFE-gIX(v^QID4>(QtNQ=Fd)Vp_-Qq|AfoCqTLRg6zF4#>AQ%XLKg zf(7Wjj9+U7ojAocy@$}DS<+QvIf%=IDwZxC!LMC1@r= zPRgF%RzGgd|9*~fHtNM&Bc@|F^^grYMzz{eCE@o!;`W!{G;Gp1v+F~yj6XEhz&0LK zVPef6q6~O~8%B3Mvc%qiHpovvA`>2`1*iDQHps(P0Mms6k;p9qFrxthRfdc zh@akGzo|obUJB(Xb0bu`L#fJ?auu!r>|3^s)<~U5-)+Pcmy@Cg*X|MfjYNgyExu65 z(0-~5YJ*Ica{_7=w=CMDSYbTzPHs`Qap+VX&r^Z?#>7|#8;(lMD6C9&D}gv@br-@K zEkO9fpc})FN-iE6T_H~MU*Qo-6rwby6FMM_Zr|L`18GlAUVoI7E|O0)Mg88o?+`eq z47wGBhNow?(4^n2nOaLdboE-3K#FrfQr?~$tX5><9t81ei;wk~F{!(T8Rc;HEd#~0 zrE`!xlpn_X8QQjV5|@A$?pk7G!}1EvJoBSQX%| zG;iB?$F{w%1s4ul$z*i!9}Gx=BC-r!GG^D$bD^-K@L!g*(d)oztj&J?>kHzYFg&QQ zBbC;sck7)b9O22eBb*h@8NXj-?l?YWHAv~5cvq8_<==lbR;2IRO1bor6UN4u|2!Xc z2a}jA?ey`ClvsNU2b41ej)Ne)9zJe+?-7#`FLTKAgy5LEd0G}6+4A$EqC(v&jOwCF zftRe1$l;a6*7ACa_Ym_NyEu_V{5$=_K899)Zr9Bw{QLPVDZwqgz~emH>xM5tf@5$c zejS)lU=i6yBtz7tTqUB7bAucdruM!%grs#Y_JW8qA?t_~xVs0z>$Er1Oq|KgDgbg^ zdeu2MX_*m3g#js;pm4z!2*=Q6!=xV6c#F1GDa1Jbp&?Pvd2#qTi(CR?nBZsPo50LI zrf)jjk?uJ8+v*AvFF&BFp=?c-A6+vN6L&w*&$_o8fKh|Qc-wy^1NWodJuf=1ghh|J zYCkGhoU|Rf&pXpjaq~zlxc*T%hoS%LxlQzQG}bv#B;CeERqwesQ?{c){1vdDu{#DK}G1@Af=Z8@KF@+PDXp9IpnQ;*-bya-C zN)eRV;n1;jGV6H_Y_&cxA{d_;6VNEDNZ(F>&NxkSjG(L2$H)UBt2{lVQd7|Zyd{`d zRwB*oGQH{5hCl2raDB3}TY{|Rvm5sqo`4d&@ozzC;l0SAOL2yA^i}|jQo8jp(8sw| zq2w0?^W(BO@L@>C-BeW0Sc2oM94xdb&?vi;Ni@F2zZ(k3U0fGtSU8bwe>s-$COAa= z?wl#}dw#~by4MKp^Uq>uw|<6Zy-37nxb&<~->HzFtI53+IGV@$PJzLq@S^NyA!bP} z@j2`nrSy;EQ&6()>#LmWWb4gz?e5DutClAd7`92T2z5h8-xzbR3Uhcq>Lu>^x0jIq z6x~0*iB+t|vJ$cU*)WkO@wVL0+_U27B1ND$?&i93 zx}eSYK|HiFHxi>?%f-yDj~yA~2qH$qdtak-MEkG4P1=d)p7(@uPjkhY!sq=t(`}@L z`y2%r3oFglacXx~WRbJGq&R4zE20HE7ofD3o+s5Mr|(fBkG!Zv z5|{S6DlqwG(5rM4cG2xmt%cI3r|GU-7y3;<&Gy5iN=0$a=lZiwFTRhjS2!JP^E^R{ zv&U#?eCy0N;d{{r8Rjl$m!7XI4xx2bgU4FN9llD`A&*w*SLM2MIWSt_)mhHmTT!!A z1{72R$Ww`D>9QaMa8;HV9iw-|XI<9_=LRT} z8-7=Jx?8c=rQe?uz!-!4Qw5xV=k;S|k#fV%t<2)#s&UlFKnQWV4!J}M9+_BK2&1fL z>%sLIuJTA@I-tiDN?aQlAm0cd&F7OBbKfYlv=(e;&biftYMEcXUoeUJcN;kw5NDx3 zVA2sXAIW?eC}1K_q_z3oz)rK)=UgKkeY7H~O>jfVpKQAF)?V!CKURaezuAuCYTDWk z)yB`>n`nL@J8-}NGXS2Bwf>U#6L{*6!c4RSSd70`w!wl&{CAk+9qyLgQp`;I_v5dJ zFcQhb{r~ZQ*=_f{ivq4JPO563+ulkiNr2Gr!zUqKvHiWsgv6iIOmXC1 zVSLCkN|74FUOZ&7%Z~)M;2EQmQ%KhAy|ilUPetQVS{PqbLal!3@GPl7oQZ8fZg6UI z#9MSz_9|zL5li1m&*mKB`g0A)19#?xbC0LZ;S+dXfkJmV6eRV(tyHhJ1+KBXY()Z+ z<1U)ktKn$#)$rw5dB0>EeJ>~|CHzfD5*T;q(wvNucx{udi?1ELVw4RjBHRU5J`~O<))fh$uaEpJ>uH36)~S`dd~oy5&(B&g%Q3dNT36| zCM8QN|#iefPz94J0(0NddE)-lw-ak+vNA$>wm-Pk(GB{-+0^n-5F>BZn~OHbd@xl zOz)=0!rfx?TzG&*)+gl`wrGpaL4iM*cl*6RWe&3vdBr;w-`Usn46-zJytg;A+XL#_emJ{^`i^k^*zYI?tgyT=K}!i~Im z2J}+VrmHRVX^aY59kP=)?1$}HP4-uu_R<7ZB^Cu6yJxwahd%RaGs9q7wtB;GiBpex zp2BG1#Z9V&lS0*aJyRdU#c!OQblC&(q-qP(f!`~bz|3<-h2NlXuFrT}q~=nuMLz|n zu7h6-9S@JozQb^V|J|6Uq+?h)*0h&Qs=bR+1oGDjY3Q^P>rv;RIn~HEjhhQBG)sWPMN5&F^_?>qk?3W*0F3yi_M4%pupp1jMDZC75xBM7BS6OBvdiry2i zz-tRjZrXGLM}n&V7~Fyf3ra(A~uni4q^jRdOG;142 zMFwGe@VC|icC(tp#Rf4c0R}ry#eO*;15A=CplI5PsGx2F-bxWIpc=@*i zx#`9r#<9Le9u@)l@oSNGGT`>E?ZgH2n%T!H5nbusNWoTA15g%tnS$nxbBQ+W^nQWe zVE@}6{3!r6#Wd_}ahY!qwj2mk4DO^FkDcqox6povE}nDjsZ^mJ!jm_t-oyFlGCurz z@$vjEsz|c6VVK9|+pL4^yc{l9RH~%qaM;gI^W!I}gOYy+uaT@wzzWp?BCDJ(3@r>B zWgaw7U_;r%m|MJhl}#GzIf^1Aa_x?V>56$Qj%VKOYb(zHgxVX=>&)<|k^tF@B{>~x z@arJmy|_JSS~K#~Qcp@}cujRFFeVIC!DRJ_F7gzqtL{=Wt5hef!4!UP{fJUCe7*q9IRE>3fg+X0igslLNX-Ut7ZhOFg4I{# z$){%OU;wWum>RYFu9xWtEk=I7DhWW?S_)p_e-ATAa@|SIgO%cmHvsl@(4RR85Ll)% zhJXQi-0Q4ukP{p|up-jf*MEoHH3qK!d>zJed+&Q{`>)yFY=C1jrbQrxt+YF$RyS

vCAhP$~KpWJzRHYE4P`YSjkh-y|NRM3Ly2T@&F+?)^oA%5Y>|AU=uW96H zPX+7DzKnC8FXqlO_nQG5=7rLhQ;L@jXii8i&;1(_>HMUgIcxe#@gj_WAO4>fHBy7$j&D4 z=iZW*yFpaYE#O5^C$mqYh_6f8q1=ZMKf2!iRfQ`Eys&NObgh`K(6_wwg0%O10c{aA zzh3%>0X^E2jfz?@EeOLKdTnTSqcw)S$%$UqukEc(YFC(6?Y)o0cJqQ zFO&_c;ZXE0i2-aaMvsRI8J&UVL`A~XVwmC#p%H!LV!n?>R;VUSt~iX+^|P15`{Ohd z<0y}+1=E2Ms37(VeT8=(rrQg+nKqOy*sfNiZ}B#x&!c>G!|U!1Kjszp3-#RX?+H5m zM;praG+rsZ&pFQ#Cr(dmV$&-g?(*Y^5*?$NDfn&-(52yH;8vk=jI8ihTIOE=tZXu? zy^>#B5D8(nZF(65SLxCW#Z>5Idasak}9dhAroEYGGOE= znNsS{^gAXD6vc8u159nj3N32kb7MZ#huLLHI%IinAi@x4ZQQrH2OqdL{X%la2pay^jaZ8n z88wtW)^XZ4Bi@L%$aU~#YQ7}ii$I_x_qbEP)!-0b737o+cl$Ay0o ze+Eo^_*PjfQ#Vz2!vm$tC)xk)?aQuI>#u-i+yOI6HC#ZK&@JF@2XznqP$B<{{)Oa7 zF^HesZov_u>%zToy6AKS?&cw3&J&I>M+>Q}$NboUblp**7=~_=2}oj6kJN43baN3j zI$2cTjEL3yqS^_cDqGwU6K-532_&|yyuZX6(+BM6X+RXD5JFLUfJgZ~uOSY*K!dj_ z1+8I>&vBc=CT#n;IwJd&$ttaoUIfr4WkZ3>(LEKJ2T1wW_jDkb`1rYk8mxpo*YS1s z3wL}g9){QtrR^hAepU%D`odovH)muqBrb*DV1-`t{?@n8#;E?&ViA$t>m!V)_fXGm zpznkTp!CwmLfE1Xve(zI{RYnB>Nh)lKNiSkJ#4D1IjvEGp1O@+626)^7gun-*vnA( zi#7JyTx5NVYMA_yi}av)Fx6e%-Y@ZL6;>kNfzE2NpEK}l=nUz$0N2b(j@hEjDyWiP zOrTh#0zkjJ;Qn|Nu;chNY zbJx-6l=BAYDFnE|c%Z#ez$!oXP-#am@dPvFB(&%B=f)C}SZcmnG-Z#>kgcih`{DTW zmol5>()jHUsok`J$!*`Ym>@)>H*-&1%+~xeon`NVujO5qq2W!*9>;;?Lv`m}#on@1 z!*_r41Wy^1e9mzPyMHC&oJGqA!)b7sl&2qXd!fj4`m%D7L=mXS5XJt5<0RI(o#RXl zAy)>Z5fvy>G)+}##cdMpR?CsJ&BC(O-Zj@vT~YRnEF7jxRLW8#NA=~!1~fRK-1GNL z5C@U{bloB}Ql>i!|hYBUb3if%=&i?wRH-s4Ca)z?WY)RtM~W3)+G&ZpB>~}Te43N^#nZ91L+%0 zz3tDfqwmZzqRwk#ao44-w~d_AojLBbo+j8Bst%-r1L~m3=zV|L4N|$Oy~cRcrkX}h zBI7lbNV9Lw8kX+3c|BC*r&41Ya5kF`2m;3@nqhv;gmH{x#Ni-&=)bZ924&F?b6;~5 z4S&PGkgnDP#jR>5ah~;stVtvVO;~ zFteX6*S4`ijy?fn+PFwGWi}y==rxhBXX$N-+cTvwwEx@2@2e1c+ods{5XV|fgG$#` zK(B~d)_?Ga$U`NE=h?#pXPWSSZxt%Ph3ogLi=3|{9w6liKX!h$pZ4!z+DFyJ{wUQ4 z5|;z68k*HGYN{>`gunm(gi(`)w28r-tdTgdZA}7zIe3G(5x)ipYkrSs-7APN7%@ zh@mkl4`pbK)EM`*owv7G|(+WwMHoi%4Hv3MbcdZE<303n|p#@DJ=a)sI;1zWO-=UZ*b8Q_i+wLr@nH zWQ)4(cSPGbi}FiH74&FeTzAAcedFw@xR}Nza~MhgQ~nKDWU)qzz$@k+|7lu`)VD{S z4_|H8U3M>amECA|T2Zfh9VonQeh-n(@`N?N-P~VTt*aZNU%oZsCa*XhEgx4Sd%S~c z_}G^cC0zo*PKYQ1nIspDmIGpVK{K(k5NYeBm^GshW8>jFP`;jD>-eLbN)wV!kg_&! zs&hnrg#?;~rz6cnDF@9TU{gE>9GM%3pCKSn)4;y~*!|Rd_6SE*Th5BIe|G}GbWg+86G!0l6J6R4a-l_FG~rLdvaxOf7J z71?T7fSVwRV468$W-ed8sx|@DZL%4g7aXd_vwbz4)C!uPt;_RV0NFntxY*4T|45*4;JMBz&PB%yo|5AO&u)?m9EY;46 zA_OTaj}590aZk#&5M46U@LxpY1gDaSBZGUHWj<{na2V8ORyCqaz}jw*S>&f_^N=*&-o)+1&nE#gr=Rz;j!dgGBc}1^(>M?!wLf zeJFwykE2v(Xn(ef&?GQz`0rH}|8;TdiN-S_sdfLIXpyWeY>K%EY79yK@A<>%k~uhW zx1aDWV!Wqzkx5W3SO-mTe-l&R_`%wycQYP=*;mLGhwQ1lx{j#O8i{lwHT5A2oixH3 z(vAYy;BG20x?Ks7w-^p`nq{ly*(u8CSMUt8jIME%%Rw0$aV8eQ5d`uW=YD640GUz< z24eCd;+X)m-64+>OoL_hAr%Kuo){O3(uUQLLvEzDC;rR#?NAtba2hsc>|qrqYJm<_ zF0dDaYlW`Y4sSkO=T(5>A@8Hm3yS`mG`Hxi4gF2IVuyVXDap<7H6|PRKsaU@5zQN| z77mrs`kV>_etfv=^zK327lgV6la`KL3bkW)c^`nfKnqy3k1)0b#&n*Nf?LLWfwvVp z^EV^0E9t5=H;`KfVq2!-hw-}t_5&16wYFsxVi2MLB^ze6-F_<&Dy7ZaP$EzM`39Gc zf*!bSF8}&)Xulzo=DR!Evh{U#m`vIYS(T|yVck4q z3$8a=6_O--rGT2WQz&{n?qxxkGffM>nMKKcoWqp@B5H!sFXKmR2;ukX8Ssc*y&P!z zhNj^FtOSBS=7liS;e(JXJ9(rd1iuqgA6diK=5u*-h;|vt_vR48 zG-mb*3?Ab3#?0f-^9CR-Kd#sXII_m28{Oi-=m!?1uqbzg(|Nq_D@^>JUCHn220ABJ zeT_Qs;9~L@k*)44rKm^Pv823S06hKbcprj&G}hd(xNWVnm7xlyZ|6SxZyrnDG-MBd zs)}o`N7S8CuOo&_tXs`Ad+c+CK;sH3Te9{>?we7FdR)f$facR6Vv{BxNSV%y-bf)l z!VDveya8EcX$k8+f%}1U(L28d>24<_c|aMU^na^dkYAIrw6polm)Yb~y&q-EBlo)V z_U{vUSP-Xty~##iwN&@ZfJnk-%uX*%wD4c2f9<$inD@7G%J)LB6~`BP((-Zh*Z!F0 zk@xrEL+1N)?{J`n9Q?2fW+$_H71$N=ytT+&;ie30?(0SMD%SUT550fvK9BBT3PanK z$0#m|bYs(EoH6r(5>ELVl)NX=Kfu*dU>t&$Qz*1Uku&@@A#J17b6+_x#`#8K7Lr+f zBIs^7o@Vi{z+kk$27aRzQlsr48@zlu4Jipu8xhRDPRusjK9dXp%^?R&!uH-aBWCeG zelLE>&jc~c&LJ(}Fc*48Y5BG(scdbHr+2b{ELR=h8e~A;v)p%lYZ-PH4yBrgSdUZ% z2oEt?mQ`}vAW__I;EqXvk`|bsxlh#}6GtnGVxBLVL5%KEAGVk(@{W#2lCS@kxv5@i z_6DtYvHbQk2(iry@;oCOJcb&L*O(pb|5*#sh@q>_w3)+2!#LHV9aWZ1g=vq?EC)k~ zFPG+GV3F%R*4QJJbL4c_2Or%rwTFBR6Nn?dLd=L`VQ#zG>y)vzNCR6_{CRGmecVp6 z9Q4YV?3#mc6FJg8@4$oEz01N9$%e%TG71}G)S#uz>>4j;7Z>3Q0_u~mJ#8`a5^F~_ z6@-_i=KsbxfG0~4uz;P-i{u>Oq*iDFXk<5qYK4FI`M4E?67mpZaiRaJ@AVT7t9s^ZM@p@Cb^h&u8CJGx(H_zulcl`R`s7DM&nH7Eh+>9uW_8G5TTXnK*QKS@Fhu1d19e2 z66=mMWfP+Xs~9;%wqhFnEfh*Y#ch2z8Ui}AvJm>d|7W$43J_{9N&=)nJiTBvbT-Us zk>P4H_uT^2_d^S(adEfh$jg^ZSQVqBjoG4$TGAm&Db=#38Jz;@R>j`VL2F$AAImw{ zKxsy-eUPpXW)d#@&<)H@Fdt~|7a@)m|J`UHQ0>wf;GO4rg$LtQJRAlA{}7n}xXK#` zB0t&U*gwu2<9FGFI&P_VLnB-Yt@DTQrWmLq8us6G0El;(VZ=yL89Q;S?wdIC^!vI0AWqk)gGbLshvEYd`<%jG>i^MiI+q3Gqpf;h`# zu7?s!bgUIejE#_*WmB0I_^yQuj5c&Kd}lUiGb4-64~y+Hf#U{BOQjI~EU}Ftop{(2 z1>+q^uSv>W8~yNav+rm&0*4Q;z-MV8NU-z9p zee=X6zvq8_jYhK`=UhIRSZav5AHe5M+{&^Fw%T!gdO9&eQJ(+!RDWA}wPTyQNYJ+N zJC}UMT7PEF?mOmyu7yjYV1FD~lg^Se2ig}PXDpr+;O@3>yHWL0nycErbdXF53KO{n zW|Q})=u9^_p$pOQWt!6Kp?Bc~tN^Q7N>~~d2NurE+ZNu)B^XJ@;D%Fo)h3u>T`#%HHDH-)KEVHRF z7b)sdWIMpU!2Y^W(T8hw5(VYLB3(SvppVXVbaREfa&<+2r;@_wUmef=rC!uxxo5FM zc>&MK5nQf?b98voeMJdMv{xP3V1M~E;RnJTH9oaQV-fJW3+cxTX(v8{GLtIp#ktKf zf@2r+700FB+Ybfn6qQ_hB02ym8sUWF2}jTC&;5!fy02J+FWpWxEressT(tuA8`#tA z7TD9c&4NNWZ-ffvt`v?B6>dPWL)R8y6cZ0u(QI0UBM1I4^815JCte3ST>&x}V(rfC znh|TSl_Y;SD@GHb5#DSDVD)hgo^Akcg+b!$NQHlsb53I|zqA9!TL$i46%Ch7y9l8f@SY!!h31|Ns&8qQL=BZQwU#b zzMc>Dy!jOw%DSfuSvJ&y+=?IQFpn{A}uP@V0H|P5-0#A4M6aiDT0ciIF5%y zqL39eO!Ldn!1u_rSE`*HJ~+cTTjGSp)Ju)Sodbs<5ROv9?&l5VKPdwXx1RO-Ot@K} z`R95zDGN?2DQ**dfge>Dla*Ccsg{Ds!F)}=>3ju!QVd?+Zjde#D%f3;ZJUFVNNTN6 z7Vy4K>})k_AJ81U$o`VXI1cqqkjK~8lgpdiWR|w+G!lU}#UWsg1mhu~ERAq$#SRMs zf^d*@|L}69U8g!MBk9T7v)S=%0!h3=|G$7B(5y~8*3>qy8t3Mhq# zXWRGXX?^LJBMeHz;QAEbwDlev%S0 z_nJdC_DGD`keN<9CS)0k@X2Z#w;>~d1)RkZ^}W=Tv39DkFss{#yeCx2rmPxd&)hR$ zC>hTb@~)}2yr7Su&I($`{64Xn`&!%g6h$*=`sh+K{)UN34lT563z>4MKVtBGs(}R9 zt02l+#}<*lk0cglmEIzkd8_&Hj<#3Uc2YCn1uBA+n;kZs-p(@;ReNL%CZ3v}Ak7s_zcX;_}}cJ=GkVb0%+rwZ72Hn*ziaqhLfZAhGBBt--1-466w`gO4Sng3b+ zUyp8%DUG;hz53SoG4-uNWN9^TlnOms!YIq6n{{yFIR3pC-x7g-_UTpi@`!ikjI5_u z!*HBuOD_)VXDMd)eeQlH;X3`>A-HqyZ%gEAw<8r}s+%2Z1&p{(Ay7AwCPg;309Ol{ zHTy>t_8GbyaZ_q%rDM6<=NN4{4Pd-qqVDVRse+m7 zZuS+T7fH1BMMUVjp~{|Rh+q=oI&6A#)zz6QvjM`d38h!qyRXW0*q#7NnRQ^e%!8`peOTBK>=ry?I%OdB$SPijb0Dy`EDzSk-_I+_Hc+PoUD; zNAqQD2O$w7;)%|q+&TbBCX_-4-h_u2P%V;Yn#%yTgMI@&or4!14FXXj=(x?+ui~#~ zA@^ZYiLo|X-`<<-Q2V8^FdT66S}yltrD~Ah=jcoKTo@2^n`_10bN zG1R6V54>`vr&_CH?r-JPs_qcY&u!P9dC6tm_y60$HUE{#@lFFprjD~EmPn!L#41nC2{HT@1^8jUWP(e`G`>ur7hM;5sVz>A>_Q20sE^4!-|z?{FFJxB%T+< z->AFVkh|68PI5P_p$d9|TOfSEec=T?7(f=QrRWjg4pJxLUqL*wZU?-w-Bzf<+&sJZ z6wtsa6%nPG`iR$|25MpP%=v25ri56=cGHrFL~{L6tF(}JAUo;OL*c#*^frT%CJGuX zcLjK(_VXPMYe!oQ+-zp>MngTX)tTBEK2VYknDKkYPB zVzmCaVdg+VcK!=L70WTJHp}cM^&v&iUQSv|R`S7>$Of_y^MXr+PLH=y&)KuuXMDAT zJQkPzY3*CIJ$-w^&IWcz6&-6Er?9J{ssq-^NtTi?M-kbYqui))sLRzL6k?c{Y&7E| zP44wqr0EbiH&gVx(a$jDW&EmQ|KkE6HvS-t05Yixv}Shf*+3VCn{hQ3(lmwWG(-`d zx@*7U4VI^_95i*@r_ijTI`*m4Uow2@uaHtCIv-cdmyX*FvoMNBvmxMh5LAVJP-fSV z4HSq)nHSDGU#6R#6<6h1v~se5oP!;>nO=x`gnA7uh5Qd^IRn}f2beKVqO`pTXy3%B z=R`9QKgAyibhb(j@g5OmngWsiUeD6Dt_J$zD9^NKO*&zQF*xNtffDSh)&(p@uT3&H zHxnYI4(HeO;|#0h`3QjQExcI0bw;MHm*m9&Gr7psdBO@*oTTX@d>=L5Mxt=tq|bV8k)rJ20^sSI+K&s%96n$%0C_9Fu#rqdF5Xv;Vjcw7_I@i ztw1X(rI@4sYLayKz3ERsnIc_U0J4j!lZ-6Wg`_f`a?{80=5!UVx&iq~=8Nw82{70P z5_adsJS%wjG|V!PC2SK9L&={{A#oDPc7Z$WxDta4-|wTh{ZVVIr9$3Qf$D0Ecc!5F z<_8``7vsA+l0A6;aXfgBtfzEv-feY)mYaD@JP9GfroVrR*W3?C96#f}qWSrK^zntz zfIqk8j|cxOC3COc4kOJk1ep&@t3pg$3~A+I`7f#$3DS`LVcTh3S02_yz<&X*H*}ft zMg9r{LpRbh4#xVGIwuX1sdn9}^@#Xmos`(3k}YGR>^ZFE7T4Zx=|bN#t$2_N09-!KvNk% zYtDgu2|WDK0t;SoI0xU$>`#y0)>jxDP3p2~!`(pq)Az650IR^999G(rIt`aua&u!t zSiMY%5U>yKAI#zQ>VXuEryF!C$pXU}Koz~maNS^AezbzNcEv>YCi~SN8?Cj<507o^ ziV?4H-P-?5b;Xw1_0UEH_GMsDj?lxC(1+PysS&z*dZrS&GZPDh9^#5VcF9oA#wkT~ z>0%vkhm`ZBZ1uE_c}6@Wn8L(fB*9HfEi z5k$%flt;+@v=4#C0A}IvBonSS5j~-w`Z;Sd&xR2Mrt|T~M2;-zK(2cS zF=n>E7)_`5_iB%%%+dAXnW-k_m}ktWmdZCO5F{9+tWWgF;mOsCDVin3CFcBK*!eO5k&-ebMm9^7n@KZ49?H=APd&bV2FO+7T zyw6^aXKfpQzv1Zs3hvSTCRDUHp+Wenm6xz%phSA5QucO#hQI4--AINtOOw(1+(6RNkXot={;h9B}?3>g@PMeXm1vaLxUuyaDguJg)Pj_QNFd|5JGH_mqwhkQrAyN|PWpV->ewPtD zUxa}x%M)eYotQ@I*Z@7zpK~I)CmJbe;3gj}_<6)KW?@^AHgKLsZXZ_AU7pQp-3;7u z_9O`!@Et0=w?k4~37qs~Mi6Xp@yMQbX^uMpMoq&C?WemZC9jOyXe@dj03W#|H6?s} z9)>h;894>wn{cC82_E^>3a>A_mBW10a?@-d_zHMkQt#g*cUUQ^Gaur%n7oTtVD(@# zzSQoKE>)1OkqUT~tEWf|civq4vU<=Ic#qSwKWIFFXYormps{J0VNqU`j9zX&dC z6@W(Cs=EeFxoGFiNkk%kb~8kJBB3e}+Fa75?J5v7|1ffpdtBSZlCqGC+79Q$ZI-ct zu6`*2)aZ^$%}8-kPM+|Gleck4px&t5g57c0#Por1AhL1PL|`=C2z+%i<1G#$ys_UO zK_`G3##)wDoW)zg*TY)@bM`3y7CeMC?PRLJf-(M6$3{@6y6ThvSDQE_A`zVs4~e!* zRpXG^4F9)f6fkB7SsnDK>pyedxBFA$Xmck8JR8lFR>7&DZ)+)$N(QQstU}!jlS~hRX#Dt1h*&5G^n`Xpw|9XsQn~SKYBM!3H}Cu; zKSo*bz=adOFeHwdF}dg4BQMu+YH}zx&96$7C)1rVU%N$AHA zdWDeCWcHaCkM3Nw%&!-KMRWE~TyZs286VuBnG@jvH}z|$gRbWj(WP3C*|nQ`k{3bv zz!jE3^MP%m0_QpF`6(vVE0?i>@%n+0&htli%7(^H8LNpc?xLxw>O$~R>K%X%LAL=u zLAfRbm~=}V$^p_u)?aQfhLR?$1ymt(2Q_aGdLcLR@Z*A;P-0zw8zaq|&c7Q%Z(`>F zPx}I3nzv!l)iDT}4J&_uW&(5)@i_6RB0FzXOFw8{m+#Fx&GyCK_G$vgQRPUk@YAcLdBT^<*a2TB&7g%f4ky5hA(Pl!vhw z`od6O$=)`*J3{AWi4esfc;c-Ux;lpKcXsp2LJU9EkC@r>;z{R2WsK8)g8tOaV6Hmn zzP85+N<$t`!cP6!o2;oKKH&9kYY2Vys9@$4G|P|dk-00Z2ZTi{K{jn%&X z`5ScsN_FK0gBQbA>K=WYOH9o#dLX}2rnHc2Usmqsx7}dVo*Sc2%a-NtrRj#vSpInY zSv2FVyqA9O&$Q=Qhig+wJRt7gxE58>Z0W*eg9qlPx}q=tJqL#SUF~g{1r@V4CFqcF_*0mQoOv-%ecke?(tM6)n1W4OBW=EY#^R!bLJR9 zFrOO9=?YA+qO7hcs$v3>X)=}}=pJarzwoGq;fTK5Fy~1oRi$!%a(-jeApCK8IlytG zL(d|#jTP|L^6mzJ!|cKau1>nbPWCSXuPoHX6}&|3k!i8ppf8j(!;6&n$R7#}+-t)J zfpC2|^2h!#knUssR>$3q%w0)i9N;kF6&JtDAsW;viqB9R;D&bEJ9498>e&DiRZn#a z+4C%KJPAik;dd)e8T|`RI4Q-0-ge03Q_m|_&E|pM`KEJs5@rC(BnWg8-QZ+x%md88YMbo$sX^W#XPZ+d5&6&ur++@2)gYUhz z_w;8eeJwVw6V~==IPi88eDH4&(1Fij1tVQ$7ovA?%?q#W1%accIvaWf{m}!XDj4F5 zL`u0i)|fHWGjsqFDYY-Xi(oh=DUT)*JFO%4#vQNMHG?L1~4J7_{PF6|3I_g=WLy``;hL!dUePp>=-H1i%Us5bny?-<1& z?(aSOAfA(e!cTZj0S&ew4v%h|fd7+H z?mWQycq8HA!OGHgYvCcWvtv6e><%SG(+TNO;rS0^dF)|)T>Ylr{@b~CL*gHHJsnSf z;gQdoeLeIlIMB}zEO)BSX_1}>T3#iKmYx<4Yp@(PxG9$&ae8S_E>-d z)ElxnR`HUcgRSjlBQm~Mp4?d?pt9$9!yk?kv&wVNOa_vYkbrP-mFgpc4q-ZU-+c8D z@Z511E+6-D|3#ADBkSrd4l=V4Tah0>sN@WBh9Qj98Dqo8`Ty8@^LVKH?|pp6WR#tf zon&xRwyb5V6b4a~rR?cOC`Phmok@ijGWKjOL(RyNJ?oTFcPdKuER%{b7)fO7cgFpG ze?Fh@ZYdf?U33n-`?y8$bbL&jf;vN_E2~O=>rgTi7*Yw^4rX(>n#p5}j`gi`lo& zYmZ_1*4euPH*+5P1N9&}^2O@P^ApZ5d}`z5YY7+h5=z_iW11rO{r&Gnn#$pA1Xdgr z96q6!2;JK{QCtWCfO^{ttO#HJdim`~@B@_P1L&JjQ@Dst#JLpyB`Z?ByU}FRWaj(} zMNl71rxmcZ_cKtj5LC%m>fvXfX>k_>S>R5%aDg8^K6@KE2zCAkUYD(nvUS3LTr6?< zs)*)oOq3xM6X|W$kcr$zZ>s1}D-@u=9SxuV0F!+zsZJJ|eoeAY-w7zeJxOXg*Y{MO_HbTfK&y^Pl6=jH-LR5JkXVX z@ZS@P9xpw|xDx*=m|ZWXf4_S%>*Lne@fY$|_nuEHV3f5Y775@3=bfKFsv!>RTDCm; z`j2#NQEY)5+tE!mfJ6dZnvP-m@(2JFad2wiig;Q*JKi_OI5!wUO-XeZJ5RguH{H zchbn;NS1zt^z4g?UeXO;5mpeR2*S!t%~rM+ z*VKb+G)KZZ;R8FIB%S8WwRP&;Fq%AHiK%v!#EX~nXt2|fACG19dGrOtVKZmf3Sb1L zcaK3qttlN2l}*IWNW!p1Fxgc!ja+(}p#6&Fqp#~INkS!#0cCiJsE z^z5aBhxW49;sG}sdYkOzPpB#|H4_Hp!w~or$)Z- z&4vE-7snTT!Jpf{y?07fgZ}@Aq9z989pmQ(k22)@2J=fN8k?IU_I+)NW4GD=>*G3j z;J8Gq5EF=3V>AQow1WUIazjuW=f%EGFFGsR&k3_V&!23QP@Rln8B#NMWWXJ%K`vd*WyW#FB@YKUGhYb! zRs?CtW*q3_7KPnt7l>dktmz}T6rGe_p`nNJ-&&iA`c;tP#ko@qWz{9c>pttS>K;QPO9Vgd8M4=kUDNL z$72Xt1NO4EEKKO{&`^Nzr0cRB0urQ52fGeDyZ-POYqcruf)?=u9Al#ia7o&q_>$Ml zO$nK2Pr~AXdxz`a%%f<4BuHQ=y`|`1Tx{l2d%4&e5XIkd@7FJ>i8 zlfU2dh@UGLk^Fh_*>Q;U#vWe4@Wra6O~5TxsT1hu5rg?T@?=l|XP$=e1*41|D2G?$fR8-j7~0C@j>@}4xk=V{{M;N+GP=8KLldfh z2VhNRvPj#Nr>_InGR!dRiuh%VRN}P4)z}eohysW|{N3;pDp?H{W$G=P^ibvE*)5@{ z4x|Ibtf}0a&_!metpSnA*CaK~OC1%DaF?4YVWaUpzG}TTG@VWKM8*Tu)b^Ed*1@=M z*MTN>xqjc;88HMgU{cx@^iQpex!7BE(k?$FL=(zVMo^Zd3a=f&QgTg71A3N7=LYNR-=#6L zIug!<4}vYvdhNO8#9P}LciAl%gUzlyBMsO~l&8N~zf!%T%^%|?eb%tEnGeKbe}&22 zFmFcoJB4n9Nl<&N5q<~Dg8=`hvyi$q4MXLf9g>P)c{y&I7gc ztqVh&p&#`}5qp`nYjWa{$gB5o$yJD_Y0_+q+{r#DEUxyq8jFo4`)rwAApP(VVV(6b z?}}W>=$FC@vk)3$kO;sQ0_Drsem*(vlvzxC3BN>50WDbKZ*Z=?RPj1^0#k~`#DY7$ zmFjJk_d$zZo`@oxB)o!+qHLZ^SA;7Ft6Hzh=VMcUc!+6$ z7F2e~w)Ze~s4VJ4FhR_5cF4~pr^5)3kbcG`(b-zR+)+B>G7%8JKp?~Aj&Lg-Q z7kE-{ssD-=+&1FoMY})2>?h$_m4)0l5BB%x^+90Asmt`b{TY>rPS*WQI~Ah4%GDrdLy zms0X_@N&=PSfGUc1l5L*P;+5RXuzZ+4Hj;zgGiTMCE@-?A!LY~@rss2E||`n`3qaU zBr%!vGMX3(mA!BD#^J$W9YEZ1MYj<}X68HIe>-Re*R}IE86@%m8Qv(-v^)(yXK*(~inS4JL!ysVj zXlGXyT|}@KdXw5c_u?gdjaJz=>05{roE{gA3jd}S+SAktOQa5aU2u?7W_`#=hVgE! zzhiP|T$=kASdjG!#dciEIMk%iS{_jn{{5}hg#q{2t7{K=ftOTKB2owSefA{-pamVq zHMnwX1I-|KW6NG3p{F@7fES$QY7m-WTH#dRBARU?Y{FD0OUM?)>u`QeVrH6I%fT!EjyoFvNcn! zwr6}3!MD0(m3QSukeqGfZQ>3HIrz08oJr+;IRVgX&6o?zFf;e=jl^s%5Hn`V6!_I;tUIDzFmVOQla_) zvSwr<`r-G6JFd!%Axg8Mg)HYD!#&kOWpY5uMu^lZtjOYw+On+ZJ6?oYZ}lQCapCg$ zxI(#_x%Tov@#jRniM+3vmh0FIcPt-dytJoviBKMOubWQ z15!5b6RYBnk?Q@pCv#^vXmQ{P?d(nxDzTA4>d3pWYmw4ANN`8R1?fH^>*eqR;C#ZP zh350asn(t0CIih<+g3J>Tcwem$?yJ82X#l_)PwX~y^DLHue~toaa*VA#digKu#vT^ z^yW=-Gh(pbeP$fHZ+^*|Z>F&aQ8Re1m5n%txL!y7f%2fwn(HOtsyeLXo2bSet}3Oc zq1uVXHS+}K(^?-47m9}~f(Qo@?eQ1OjUh?y`x~oL97!}HECh+=Vnm)Rum=O{zpBvk zhDdhKOQ_@{V5TaxWI%PWIwz6FT-Lm^$A3W(-$n78MgBr|(E4Jr)Qk@hbuwc|h^t2m z4S(~@KJ3mhvK@3(#CkbEeqe+=1v(Mw!|SwL(7}Hn4dv$~?JE1wZkQ9#5}(s%>he{U zBfU`tXnwgSZ!9jQ3JKg4ggfWd* z0r%U``hW>beCU%LCHN((mQAPgPU)z#Xpa{m@Bag`fmR1H6_wr95bP96Jd-0B>O_(D zN?JU2N!ys6ULMuVxHRJ7!$=PA%ObBuW>+mSeCkFmaX$=PDw#HC2@UXj*pHjyz*t<7 zc51#H^fBZZZ7}eW@SpU<%k-qbq+XMD+)dd6dY8`Z>&MlFsv4d`5Y(n5*)?)t)u)&E zpBe9q%`agu{6z4_bY^T;UQS!tgMXlq_AX9AC6yBvw%HY`XG16JN0^G z@k;(~zM=GPV}Fb3bOVx_;$oLD)@$EReYF10EOxVun(teel3UBM>3!`RH`nl`a#hq>k6=>1JlNk*=sO1;3V=sZe0UOfb1V{Abn}S z^O=^dq5fIpnwdZ&I4fF{95 zS}|OMv16E-{VOW$${F{hcvHDP{T){`nrVHY^8L;nd91zOHvN>JLv7i-r1~kJgHYpT z&->qFYsbg)w&}R7f4mHp$ANG5jC$~(FSr?3z=2%T;m{Y;m+<)w(>_M#ce5LmCXJ%} zp$xUR-_DZnx*Vqj&#ldA3#5q;wsgE|l89*Ff!S4%SWerYiGotx?i8E+dDQ;j7OS%J z%XiK%f6Kr6@q(%FLAg-7Y};(EK&vQix9lre+S>R?J?he~pUSWO28n+v6pSK&CUih$ z)E7uh-*mjShK&#R)r<6t)JX_9U@zBh(U;brS#-}FSO>@WZNh#k*NlN4WRrQHD39n>_0~$Kmtd-(J=idp`qDsWs8 z9$}1=w0=kx+plp?K6~i_4lf11vd4?Fle%mTbltxTQ~4)xtHG=*+LYdLj#fxOczsa; zFxhLP*c6)y2|8!pc74Ln=8ezryHjbAP)^%&d|^ZVS~6VxC}ZwMxT4C=gBDr^p_~Em zHg`M}Uj}ws_w6DMA4KGrU|VdP1`jm!kzJQLu+;0K783@A*);aUIBbzt zzMOf*=D%DskY#VWqOibT5T^LR7EE>DL)ufa7H4hBuVF0iL=&$RZxrej^cIz0w$Z2n zorj|#CAe>$eWX6_5Gdi7IH35zJ(3;g8}qs^{P(7Hw!hp7VshM$#_KAZ-BwHAUlMg* zYLM&iao&WZoC6WE(p#e~%OB`^#Emd3dI+2TQ5AEc5p-v!9xKg{?Mm+xX6$8%c{rWF z0n=7_+Pg;2^uA^voetoK?ave$$z-c-CP+hRwy{=U*S_;7nkGUpc#fgenWbDGQ;vnW zYk_+mT~K9=apks#HU;LIIC}F6RbKrmbprRa8mWhjB}RY=i0YASh90i?g~fkj{-WO#u*aW@jF^qH3*HaR#zrRM)_ zTe#aP+=e~D*`jmOn5U6_i)Ma^;iY_CLoTrkl^j#2i337ez>}xcY@pvlcI4;5i#KG+ zF+RZkp1O677KYX0E!)y~{1mQwU64IaSSO2w2TVHP^%q;#^1jpN9?EWokMl8;6*;`n zs1O9{JD>vllG-dsX#_v5lp$4R_LT(LT5f^uXhnWZC-pG@7 z1QZ(si*D0LE=e}7@BFY0u&GtPasW?24ZXt);12YV0293?EN}{YSyNU&{nAtWXuMU% zEp+b@^Br%68QI{IcMTGpp*E7EMYAuSf(YCopDz4$5%`KMAr9iW{E@^eH|TI`39$|Q z#XD378}zugiezh0@B3CuW6xvid_)g%Fpmu`EJlHg@p|FOaHK^NKN6q9%hbO(jkkUHipyx@Gd0)XEJ2k4jljb=-b zwy4z4l~<)SShInf~ zU&TFS9e4kBeTi^=cvpwt?anJO5z2z9iK5%Vxzo%%?VlZNhMm`oFXYuvP!5k!mDqCi zB3&05oxs^n>49V!(5v=HnJpOAvS+jt^aB-hI}2wQ3>fYMLbyX!C`aTcL<6dCETV=y zxUl7EO|=Y5fsx%Uft`v`1s;tCZ^oJwiMrv?u~RE#<7?;780JgM_wLOLSSLT$5Fj6sw9Ip7})M84!YZnY$$%+hlisJKe43{s ztC|U&Hcws6_!HF=o}R-_q6zA)B45X&OiG!X+iQPRN@*SyGQ;Li1r-vm`-3{=o zTIvf;&N5#nPnY>jbU!&d^S+xm(>nf@TfDCOCoJwR-|CB`xXL~Cj&s)|be)5bhXP$` zdR?Z@*Z_Qh$jd*7z`xBc?8mo_34dQFPNX_xRA-buWIYU7mKUvbR$W66zRoRxA@Lzo z+6Vfb!ZL*V__#$^0}SNF)#d)q{|bECBt63D-{$9@HSxNTz)Uzi@yu=TSc2v8XF{)H zD1MEHn6EGIGdMEYED(Orwd)v-i*SN|u>IOIb{K~$mC!_ZM{q|joV|S) z;lYnTps{0FMPm;bK}j}Gl$ya|3m+|PyP)Af&!cNibh55?UCE1Ci zZ5aDZMTUjPSw=j)z8aH0;7T(m-X_d_&Z~t5tUCo4l&+&hxBGXI+8^>Q^S-|;jf)#c zDEXNG)nl8`Fv#`@9ikOL<1eAvR?3qriaYt)%^^D}EjM?XKw#{Zn=(u zF;`I{h39z)fx@Dd?gc}*Eq$numGYKjSPvZBVtF^QY)Ii1t+nwkXp4f%K?g==+5*pL0YB4D+4K`+3 z!@IyIqh28-AtS212)^9voq}45Br-~{4%t_!KoO}E>zC3;)G6tiYTx>&p&IIzQ~ z%wo#8PcXJG>RSIOhO+FG(4g#8X-ZbPXv8x1aP<6Bim@L&?h({ct*H2$)Ahj1> z>yK;TE8mV6A5oUazL`GC*a}5WGnl;Vgj8d)(MK;@2-xbpI>Q-W7Yuy{!OQ{{f4xNb z0fWOT4J*ve?nGPH2!E}~!oEkNYk%1`8|oQ6Dry?xu6=j?O;KywZyT3PGVfWg);n$k zBEvr2gK^(aIA-R_VW%QG1-bUIZRDMA_`TCk8T)COgcLf#1)ClwqQSka%irqk7U+g~ z+_5U%T$HYIK@N{BSI|uG<=9wPSMIz(3y|)-!1lLO^HO-P(5o4~LYn_rlk8lLZC!Nl z4F++u&a*G4iVM<5|0y`Xt29NodrtNAq_;2o49O_=?k9QA(QbT2>DJF!XMOdwrw-YK8_UXc; zcW$Rg5ZmXZ8Yhn97X8s1$^BXv^;rkO)rYiOGn{GH=`t=$ofci~4XFN|V(}B!g%27; zmpwzT>Z8LI3=WI*a7Rf^*n%oR*!aYykW8&g6xr!G_&_#|G|Mf!_D;djyscUUOMIu= zuaSD|;GM1SDj7e-Szis6V5nYU@o}7yLtK8yfWq*@KvRM1CC0l9Orhl)g9Jwkl_|B? zOaE^(Cm~xT7oc~?UaR6t!U@ZNhLGX&EA9H3`r41vYY}}8z`Jn4I0^-@_G@PaD>WkS+FivPMLXG3s7Bj)QwDBUq;&f{fChbYbkLGS0%7WKZm_NX} z>|wxo{&gqipBN59IE(c6QJx)rcvz-e4Zs3LY$qZX@oV?U>%48+k01a94Ouq|3`r~- zm21E|eSQpSgBhphau}vM$x|GfIHHKlG2zJk_0${RcQYm+HY10QHFk>g<;IUrDugsC zgLa=8ZHYAuJjH$6moaYFTzO)UE_skFzo|xi2{N`))t3pEGdPT^-M_s~+2HUnH8Ffi z1$*>qmcC=Tjz&iK64O8%d-T2KTg5evawI6=-+H?Xl&wNNLw{O$HeHMeNFt|e@aXSo z8MTk|r9A>mIq}D8C~hwxMxob{T;Ar7(`2@6kMx({4S6KlhIG{eXqPf-;^r zUBO+J=2_bZCp{iUJHhtZ!x12^J2fCTb+JsS;MJqQfzhYLCkGTbtG8>=W`eC=gk}D9 zPq-UBw>IdxnJ0<%B*!V8#snkXipB}Ud1+^`zlL@DB4*2Q=H%{qg zcE6iSYHWCBWbmM}G#sz8;Jnidx>x|umGFxhT-q9czQ#B@;@3YNLgi-a{oW#{%4|au zF+JCd-L6b3G>$B^jqB;J7nxUT`Mo&viTBa{W;KI{CkG=nJYVSEPOUV+l-#LKxP4{X zD;`FK4yfu zKf+wtbV3)hPrLKM#05en@ew3x*tUz>^9DzHCS=A)e`9frRIXCguMYkbwWqc+w+t1q z$N18QVIOu3JTnJ#gs2b5wl|Kz^w*b!`%|Pefc1BeMSp080CB^|lwsVstNE7qc2T-X zI-93fyOX6lW_ZZ-;!V4mL68v7f#@pH&7;2s?x8!RP=mTR6{ZIS6lyJS*^B6x$O2T2 zCA_`7oq`2#I$lPjNjJ_IFtj~l$J+N{dnWdKh%&Oj@>9OA2>RsPaG1fbsU+!+Su?X~ zCC`*eRfi*xv9H80cxmUH>dYnqbNy4EfKaBz`Kse0`mw%Zin;3kecKbw;0ye!?1;0Bez^?~FbYPh@w76|T6E;j`8M+=h zOu>+xOrj=z1IG8rwUsLKr|*MZTKb?h((3f=aC-kuzW8ToGo6iZC-qETnz#pabO^C< zJT9yITo#2J>vlDmk=g^99*@;Ohkp!_ScPSUolG{1Jo}K8Vyv+NyDf7~{qr4hN^v7{ zZL1hMDqc)&mv5UYI)?K+N0ey1Q)uC9n!a45{0E($~E5>t+1Nz&f!B{|T8 zX%yND?KSNqq_AX;6_jS3dC$ZX$RoP;RReBd>mC2tS&9eF7j`@3<^Z+#H=M6-CTjD1 zuU(@Z1zy0r#G8DZeqEsi$cd)Jki>Pg0Jv#3px>I%;%FyRLO1DqQ~{(#^xIt{R!Qhm zY(NTin3EnUhE4A~%+wnZ7vo&x=xo7Ka{(@6Jfuhb!qQAB;BaDih@(j7?FGm(+7g2F zA{fN%_z1S2jZxUPAOZZYr;ZD|J>Bj>&au1PfMtC#-VV(}7!{5b9I<7J1lR(c|85*% zA~$|ccI(hxCwc6iQqEUZmhp+rd&W!EQ#}!u(Z$D@*Xl+~8F6YPZz)Lp2$s}8sag1{4Zm~fYO^jPaNERr0 zYZtHVMesjw;D6(%`PApjvrWg_lZ0CF0f*lqH$H4ZCDh9+>4VJMA?O=y8kmdc9z=)w*G>zus26A|&Z z#cfQZT|>12t{(IhM)W3mq!3b2GX8uE$x-1f=Z3(tyjy?@cB)Y;p_l*7_I%z?>ZVa@k$jPPl*?%LBUE9{(_gid2-NZZg7vqs#|bgC!gE!gi2mMok3= zgW%iW3=sOG@xaAz|At|_Wo`}0Ru_)^h=I$qhh^%)KC=rDo0QZR((R;Ip8jnV83wgh z_hOOY^nfU$a{|bDgb?Dx#LIT3!6z~Hr+Dx%pMXcb&w#qt)l&?w^0elgwSjWqr7`Kr z)YsCuY(7+yTytMp3KX{o47aVN>7P9Q;uDWT&pm0PWa8-#mF7;NmPeSLB))`!3)6qc zKmO=M5J2d`Q=`@$@XY)C4_w^AWa3G{2PlUlI93tnNLl*2JgI&}o8ifbYW(CtqPhlp z2KG>BX7HgHRS(wnqj{c8#Pji!g)SLU~YsOUulp}8TM7(hLPpDTZ$k43|Kd^W2K;5`= zK}@2ROoXn%!=L<;TDjL(>Y2A}d;`7cSD*gt@~hlvokZKsv%k@>LtjdL7fI!e2Hp_A zyW9cX>g_?o9)cDjKGoT;t&a#r8TVHVg9)fXUd;j438*t7Jrk(pK^=iQ-eo>bDR&aN zv_dUNti=cXSxcbcv<(c-^z+s6e@~TuV3V^|>c*-t%t6-j;dRQrLlf%hdp(>PlVI;U zXHjEUdXiB&0M)3}JW31}%S1xPNot=lr-@d|cl){ts$+jo5rSYX&HZP}IQvsan6K-! z432aeI2Aw^FQ#Gm>?rQ(`pr{sns)tePsPL-!?VBK%nJoGs5$_Y!LEL> zI)1Tyc&Mj&A<4*sQ0hIiH7fU1tE&Q>qNX8_i;as}*k%MjAz-~rt zRwZ$SHcxLmKMj_WZG-M;Pr6ez6zcIh;@3Thrfn=KNcF^7$?)&48>i!znM=wHBQT`; zmy&d!%3$Ir$Vn)q6^<{U`ji6^zIx2tGh^@xAU9?g4iC*=+@XQdJBriGL9bAj@^mp(hWry3Fb6ODeVue8gR6;v=3XUj)nKFA&T1!eJ7EralH}|+5K7BCn9RJR(#vt4 z&+slBp)((fpVQcSE!GY9TN~h{OkJZp>;@&DkC5ouJ)^&AN^IzK&jqfr-IdS59?ry~ zjSoMmv?6t++{gw`ou?;pLu(FfRv$o8&bejypc2M zMk#+kCs25{ie=@<`zj3axEyR$zncm`(&VpQzmj4FeJ)rIe>=~(S!aHfif&4r`(ciZ z9J}R6**ge=)9y&e&vP!=>TgFpGu)o5_dVHfqO>vaEbL&{EbWL#I=rVl9|?01JE;=I z$XH~}YyJ-x8}4KgXCz1%)e^2C+i4+M%)zrz;;CrmWT{6g`#pE@$};FiAQ})ke(+>)=+VBF&!Yt% zQ^jRIK52t>nytQ%)t7JOI+$6DebV1=V2aC>Fz20jl4qxgA(Tw;pC%BSo!S-Q&V9~5 z1jZZYkL*xFI~u3vJm4QiH>XISXtP1acnw9RovhNYR|YxH z#F_ld-cf!M>_AaecuwMOrD?Y>mtbVyidX!(Yik_( zLVXDe*sdSXko|kuhJbk5dE{S1*vt+#Euk$kp3P=_@O|K6ev{tmoM`hW$CB&onNn<9 z5Hk|Iz7#LB@g;?vzOPI~Eua6wHTTQTV-@iQU&bj~m0Fe;hOz(+5M9d*+#|1e>q2gB z9v~Yf9JxdDyihhl+zwL>(g-kvDmkpXppG@S#pBY&o83L$s0o-BR>#zy^IfDBBv_eO zSKUmoa>n$O%F*HzTI5HQGLy)iw1%KW{mA7q%xrP*Z9_o|b>G48Vn9=9#f4Z8|ig zgZ_Y)H9eFkx(j7KqC~8#vqW6vGs(Lt@*TXnXeKzIkW7DK+&o7p<(7k7Z>4}jNAt6M z)$~sf=}y0NXv2UKVXM7$JY`#j==m#G1S&EtKcDt2dM-v_5^~Q z9>U2wovW@Y(q`dVG$}+|fv7Sv%tH@bzey;9a6?sGMT(+O6{bIvoQUd3lh^zycImxb ziX3Oz-x^1mBQur4EU0eLM4p5uQ>yU{t&ce84FCah5mr*OjbhpsH?V$FA-Fk!b7xU6 z&{NwY6QkSAg~MK2It6utx?b#S{u7yl8U0$#08nI5q?ggGyv<@kENA*Rn+Zv4ep zT7N;Bg(;~MO>9Mp3UY13h_s49&2Wb9bCL2`5#@ty;Tl({{hp`8kxDoQjkkZ+q3F|t zKx2Qq87MAXFl>CEqC?skwqxMN#){10fn%#nh|5h?he8t&`d8~%X0TAciRg|f_=o?) zCnrP{T3;O;lU;DAyjqFd9)lxuQ>!#zfL*GD(I_rK(WkS>p8(lH02CB`?|RI|V(Gje z41Z>oBrFw3ddGh^*~?*r=m0DW@YMjCGW{}ANrSo1@rhsr~#9uI^}GiE6po$j2;)ByH(1zj-D071j1Y)?7PV^ z`Cl<308~?^dXS@AHX6QkQu!AM$)B12*eUL4Vl-qCvK;8Iz(C|Ep*KD5yJVRB4JhAK z7k1iul0M|<(`@mk*QW8$Z|?b#Q{O7lFdG5*(MXCv9khVGTo6%)Y@Z>F4~0&~&8e5R zerjv1?8N_5OLkd4E-O3Rce|HCIM(+tEN)emdZP1ym?pwZ;Od4^g+GR(pMM!u8mrLo zVA^nX(6Bk|&6ff3LfwG3c!?>Vn2nlrq3thqZ8|#cbkCrLOrIsA+4u>G>=ntkjC{J^ zKL+5K^ghvLjiCJAg*6J}#1G{6i!iHGK8t!Lj0YY|%s z&g=}x)mxq+HfB9>7b1X9E{{}v(i?DTu@?u}+Ldlc9BNnpnB} z_JEwH%6#XS=kPNs_ebc~uh$bQPXGrEQx3jqBne$)1LiH+AtpiYqez_ljpx70?lSKFJaWQTH z8+KP;7vef4fHH&3K5*jb*ivU8yU$y7Q5O|}xqi8iIzTI>wL;E!qfnh?Jht!AFj18# zLph4Oe9Y@L=esb$vhNDug_jVYyv?_Q{F5y7#6hREcYUPWL&SWvgw>NRc&T(Q+~7wTNc4#WCaEg z579dG=MGNYQXG3iw_g@NMpB{Zm8)wmY?#(59gdLJzZpKEWdNnm(&Ql~RzGXmro|>8 z$hBbK8SG5g~8HBwbY1j3!GnK&m`;kq^VU{qKdSw!(6b-=Cq~lOO7~!=Eaf4*#G?Qo zAye_F`Q{B-pfuFE5=N2WEw>W3(erOVgU^eh;G50gCZyF5{z1A!o5s(6Gq%WDYCZM+ zUkrMRBd1I!p|z&}&hzL~4uAK@I4tIhL9Ow{i&|BC>={E$>w64;-Pu#Klc7$# zOlKwa4oQU&mpJs0{yy=TEb}LULx2lYSZJYmW*8tzy7D!y7EVDW^to~W1NpB2SHea( zo+W;&l;1)RTU(sp?FGUE`E-rkGF1LWN~1SOF$Y4M4y@7{>4O49th1vj!5R7wKjn}rZIaH2={#i!=9%asf*NB;gx zMxi6&TNgu`ou7F7FJ1?8xgK2!;N=0MRrFf*kv?2O^hB!RMprKLR+ogHw?5w@=O56A2W|yzL522uhlRlG>#~qS(O-zV{~87Sw<;p_bG##a?4? zcATvmQWPFlO`pr8%|o_9z9G)JcK1~539li96pvx*kQ^v}QBeF`F$C0Stu=mqp{yK0 zfv_vcvqU3<{10?DTdeE!{*2~?*)}*KOV~HV6_r^hW-&a6WSnDsh9{4^5ktFAFC<4I z_@3?W!j&S8w>750+tOI6_!;qQCj^|3_=(dIMyuE01#o;*Tug4}$NBRlCwcqi%!J>T zjzX1uQZ#jJPqEN4A}A@=-NPdwsaw@4@po6t?s3&>I5Vnnj&KWV2|2>W&XK4ihMxQ{ zs_8N}vLP(mMXVF&1by0v199TpiJb^7F!=PB7M`J>k_O~-?&W2rta#TPSuP1EG>lZVj4`KAznl? zca+RhiQ|i4I^t_FxFAaV6Mkj~>E@I!RIl7Hu+Q_fma%h_-atJtjqpLh4#bSd)9;Wr zO4k?FZJ;`{kB4fD7(_d@CjG>)5~6(O^^Cu#^2nItr#~ZVD!Iw&3&Ky6*2nMuN&_1r zZM1556eRiR-HU}lqgjTk1=6Y>M>50d21evWpy&G3L0G?W8_wNnD~T5>Of8wpp2>`Kcs7<47@1nu4KhCF zI(LZi_n2!TbZbZGQU8<)PbW+{b)p_|WI{8Al;Mjg=Lu5uvmML7Qs2Fsl6CiV#po~Z zp};*@YD^9XSBRnWXP<*2jt28KpV8p@XO{O{&pi>v?`G+E;4fMYKqDVn+75X=_`0}d zm!~HHLHG3;Ga!>O>|91PM#$&?mO_(}+0313ga zAn}V<8YLL{!i{~GC1PXN+Wyz|jG7MqkLyt(+Y_sahYq@XA`Z3GuBBlp8I9k}sExJi2Z8FmF<%PBmoF9R@jKRB}`-YJ}ZUv(&m%ckdfH$)wo- zEiz_k{w*YVkmWx?Mak&zG?Bj)zmxHZK60fG`W_TPT}3sJ91-JrlEY3%Lm60x8$%ZU z_Z5p{2i8gEAyJki(knjAVh>)16~ZWlLKe5^B(S_yBT4^_vi)I6P^0x?qz~=DDpU(u z^;V9?PY-l`@>Nn>iky!4^^lzkt0axkeVU>nxxH*ZvhRMyDc&K}(3YXCLww<|5tiKr zedr6OX3%c1$M)NMv>{^X{ru3i0$aHCCuq%dx98IHZlXO&ll1fVB@e{Zmz^Y0O^skh zkI|USfsosM1g(VNHC1*mpv(ks`ATcov%Tr z^{HbtjqK@^+ArClq&eZA3Kr~VWSg6~!W9(T9}R>W&@Vi9!P9nrgx*ZG4-O`K-F;4q zZkxT<)h@=~#t+Fiu0A#D^Mee?ij7%EqYf@=9KS-PN zvniO^z(-0!hZY{|i+4yw|GI;3B8scl!qdGjxLYfUTi(uu|zA))aJ9N-B zz*4RWxyHSW)Q|@}qr1BI@7K4=@(8Ml4bOqet72749Y`h20Ex#^7Ny3{n_V~a zX3*36znvU=nqslQ*K!DIHfKkfJ5(PnuF}R)XN=6ZVW9$7Crn%JYYzJH=H1%Yq}Nbz z>-?wSF8a6NUNI2{69k*SpRZq6uN1VOWw4QTYRJZg0gZsud6vA_9GjJCTkol| z5f`27f}$t$5Av?`{kXhs`RJ>YBcxSUuE9ud;79cL!DjI1L|au+qb(MM_2*{;|9S>zNpcA ze)AG|(V)*i^5{M_m!bJj_QP@A`up`nmTN-gbzE3P-YL`i)a6_Iwq@lxVJHrZ$QpTX zo4MZki4n@tt{;&yb*N;OcTPQ3KYlPe19MF zd7XcIKO69%xPD}$_Uut^(TuNkqi>(_ERWw#{-5$oeOdkWW@_~Pz0SXe1cj+lkTPwS z-iBzx-L%^#fYQ5USc~F;7`AH~F^Wt=L@3rid3+h>nkC-l>t z(?6ouQpI*G34l7TkVlYr8U`!Uy*=xOsEhs0)Py-$P`TbPLJJ2H(xYwRg5kpI zMk!=oQ{r|QW-1;pvMb4CL%@Hm*`YA~q=4tK6%@plrS&TUdAMtDIb(QoIStvZo;j+h z#$CX3^ja6si?s4)Jn=H2lNOmZZ~ubM>v4I_96tk(Pf4$yp{Z%gG*{`)Ji4)&Ng0-u z;V50MT)wB=>Q$wg_Z{vsk#O!h*{mZ`W|zf$)UZ^WXrSyY{hl$1Da~}kT-)i{{Ct=` z*M+6=unn@bcf0=8EWX-kHdCD|DCW<_v89I?`_vKIeOe-Y_00OCRiYO04Qnn4+vAG@ zx|Jr40Z-?rHtQtA@ZNr~tF&USr?p>8CLYx68mR^_TsKH@>T^=TG1|krWcUMNl@jGz zR=;*8@1DJy?$Eo3{`+TxK|Yo~p{;Bh$Y`Yx!WwMVo>>{s&X=ED5|1^}iotxYP`G=!WW@R(3vq!rg$(ZpIj&U7qkr~hdOBZe2%44N* zC1Uo)(WuG#qW?{kT6oI;2MuI5tFf+Q?`|SP5uwcGNzVOkOIz^HuY!3rjP%SE%GE%m z@*|D1p=F6gs|MuONjsn0-Q}QP)#&wv~SE*b-uiBY59K4AwGK zAAchBDnYEPMMgu9?V=XVkOhb&#C1%qU7TZLr`Ci}>t}$2?4t=NkR6E>6m{YmOg!@~ z69LVYQP|Y6g2<`-=@lYBzm)l7FM4B}VfVat$Xb-LmL=(DO$2HcDU15`yi?#Np@m}t zW;5DMz!;r!mZKZ@Xzc9QuQux`x~q5RZrZl#g@VCo$>r?b#ye6|yn`nz+HnVo$C zrWS^dd$7%^z*%UanB0+t9^x7rogcaxDG1M`m_J-#{`a?cKHBf^h!;|IWW%qXvWi%^ zyd^7BTmhR`r8d7B7!SXST8C>GIz!tr7P%2k=WSgja>8qqZ%A-T9ecYv-*K}}erz{T zWgddNsA|-e_pXrQf08;xy08{zUmTQta)JtPZU!_^T$U6!?4`tB#?{0zr!JsRDHjm9b-iWMEe<+>R-nDu| zmc#9zTci=_PBck;A@ZdvZpBXj=!|HA-&E2MDg>O3NA8Am~^uw6D<=&~ne>>N}T$-r*4{?dj zc!l@(>HG_l6|x3{&I;E&xaefTyHv+jhpOXKe}8gmQb20kE!0a6NWe3GXlNjpbu^UV zGEv(+JU-SM%EM*NfhYfS-+aSQAffd(AvybW_sYGD36 z`boNYA?_~P@DNijd_lP0Jh7=5cvIqsU3SLqDmQpMhRowdb<8yesae__44xy0JQSG@x>`~MjG4!9<=?f)n~bytP;@m59J z?iyf`CS3?^7Z(U6xbzN6la7?oi|Z=uA|MGZKmbKVx^zOffuSTyi*!pM21t<54E28p z1l`};|N7=*m~k?hd+xdCe9!lsdovkzR0dOS`?7OoT0J6Hm7u><>ms2i8y|K|O2^RR^4;(oX zJdMmoHG1?9S+@hQ>4`~`F7a0<9e(gt%_>SU@ti&?dzS0Y3KKUaIe(+2Wpkg+wIB;5 z#>|f6GMQpc{v~eTbzy~qOqZjyI|3q0Whx+@*|NK~#+aApYm=2cmr-`rZ{;0iE~k2M z>rIxe;Pihn0i?2Vd|2>AR)+Cqsc$?Uo9|V8n{@;*10JxB+ZWkS6&dNBCN(s+RFid^3#9;_cvAGg+0%xjJuSE!N@*p%V& z&={HsYeSlklOH|x0?bQpn|W=-{vUArwwL3?Pc{q7ld@l*$mB829r{DhO#sHE2Mav8 zg6)w0{p`WeGtwXA-}qIFml zxIYfKztK-Khu2gQKWh{x9uZNa08^8hx+2|b_e3Ze@y&6wl&3#9{QG@vJWY-7(vS4b z@}T^m{IZuLUbT$&`oI2x$@|?w4D1)97-wR<)1VpLnuROIKbGtynRz)^qW}FgB{`zp z)Oz(|t->tY3yJ2lpLkLpB!=>Ic1%7sh>@5+=!HwtN&FA;*1q7^-)W4rI-D_|jm_tk zh5hu~K9@-xeIvXua&gm;>yMRqxp$EpN6pQXcc>i8+Z@aa$WuqO9F3|GF&YUCNB zZQ$Fk22Eu1E$Tc{jW^pBCeBr4+*aBmMqI@-Hq4)Fniz!J>(a9~f9|+&4YuyC>9Sf* zTk@DDPcG``t1L%}m)m6o$M_c*u>VyRyE(6D@_KCaU+Dor03^gbBlBOD@5)I@Sp z4*v_L_&}l3D>W#fbXkO_kmpZpg{&UQ5I>Y^e}RCK?Lh}SIm}4nAi33>QIK{ckLJ|j zzmY3SUP-vCvT9$<^L4bd<9E)H)EMKXwGh`(kC9hKm@;AZJUm-ZNqcV&MGg}lh7r`U z#4csQADeP_XXD^p`Ni6+HXWI>1%)?Y$J*|})iZm9Hytn%pwmShqO)(KQmBTGdR=*ck zHF>bfykWWh^yF{XYSf)?&T@#-&Xkt$x{k%=XTDgtmQ>yxW3L| zNRxy!Ki7kOa*-=n{5vL_ zPijdKCh%%?FRUy300};>Vvl&b_Q76lSqGJ1&=>y#8_BX=?RA%&Kh_n%WH&9BgcPC= zgWKiQGemh)M^Xpl^qB^J*4^h-srnszdwlMBDzJS!p4eneRsQG5J#m-Kc%^Eo`GzaY zV65(nKU;IBg$4+0M#BDl2(QiOd)2s{KFxSBU_CX2w9{pE$uD}+3MIaRDbDsHHyVb% z*TIAf6B7nF_h=YeCR2U#_*|BA657^*`D-)}z7OF38$r1*+xI}<(@?2#iPTuH9_IYiM!XbDs8?SlqWE?E2++_D6zk#=YNd#(c#qmHTZ?8F z44*E`0@H5X<|SZ=!@hcegoQqI(B+*h5nc6U&W1;rV{ZRR2(GRaW!!2v8v0>01VhfR zvV_$sc^EI@nQEQ0vm>+JES&4JjOB|RbzN7w;(Jw6s5;%}VGEgyqSGhp!`8#YQYgU> z^^hCF&is!#L_JQn6$$UP2p7YIn|FkT1lbO?0Ug%qiOx4}o}P$UlQJH-z_EbU>NxQ- zD8DggvjiSwZm`g1keB&-z$isS19lc3M zIHD(f!a6pjMwLu`=r$C};ipCVd6~m27Ny<^s>x2p7bPve-j4ylM!YN>5|qD`x4P)jEKMdY9c*%Vb z21^TE9ZF^sYiCLP)8_s0j|_&CGaCaR7~lII9&mBs$r?x#XHK`Qcxy zx5R(X4yJ5WrFXhD_ZG?D^C!x_ulEE|1 zJU*-WbNQZyWIx3;Cs)iU(tEbAhBRH9$Cp_xL%h`SLuaSxp#ri9PRH-;#CZE{(xMK= zS^fC2oCC!=6=KdDSUi3@p0r*=TCRb+^?&{HEh;ZuxH*k_spAT_wvn15SDvSUYtC`l zDX^&_h_hF zB~}!yj^gXZoc*Z{7|+5qG10dNM+qY1>Bcc0*Y`RQhZD^O&1yxQ1zHC}%j>@47WHRv zqNko`u3i;A(7UIVS32FVZg3qxZEZYobq0mwuzP$Cb6h4Yhw|3HlyhxQPIj>YD?aJ+ zOl=TzS*V=Hl|L2BXNaSWtM1#FBQ3Hm4jni%UPD`1A8fdz9!9|Fu!~iK@ybb5&!$T- zjeo@@N&gXnpCW;tRG4?`R6BJo>ULONCoJk?M6fo+X@_9$Zh6Iujf%vLB!wLFY|uO(o>lM!;;<;Q3>xil7GIVFkwi0Qwcl_dZ5(mTtcA>3%g1B8~p z1IqUP@gvb{1G;Y3BdIdltmY(i8$jk{nQG&u4b?Q3_z+(3g9R-RX2*!PX%c2+kvyhP zPY^O?%o+49=HQY=$12+?Pamc?xRSJ}JGwl#YSi|m#un50w-)7Tu$ETVlot49}XIW?dqaTl$N zm>G(oXX#jyIUA)tWD#vaJ}#@*gYuY!zINj!OJ-K}`%4`ymtE!d!1Rc|x*1OeE{Xpx zXu!e^eIIQZm!RV7J?JC1Mvuwve)+pn6%5wee-hd3zudO=VI5KF68r$o=iT$?73!EN zu}KX3t1KzwMrmkOwvHxs>kP@cir-v_+j(8t=vsZ&ZTbD)li1`i)Dhef{ymfmtj(8X= zxR2s0E|3S3j@T-S5G06Ga1TuJJDUbayY8fJTbh80Y>MC6K{}jf%z05QqvFQv@}$dm zxkVjyW8U22g;{V8-0QkFm?UFXAzkSRw>IK@X+6rWEHYL=d3T3+oz07Qj`F)VKSW5< zbZIjhIwWE=^p!SZxqUL}u?sBC=P?dHOK312C1eAG_9;p!ZRxsOwI@DcDlciNHCA-q zmaH2jFhioY2?OeI;*SUbe`7(;XK3e{>)9Z+ypL&>Do_+zeP&kTS&;( z!)XSIPvDk=J7~lHqS;YuG}x`gWW>3v1;qm&h{+@?y~YI;dCXU`QZZS*`T{YOL)6N$$S->AZqXsXl#DtVG_AV6R*wz>-_1Iv(H1$VGwF6Rj7b1enq+HQu*}0pGhzMl zaqt{=^lD54O)ijfROLA2qn(d8Z&7PKVsb=O@zkue&HZ}1Z%=V$dQdno7pbkkujI)% zGxik>W;6ch!zFF@NhNOb3~Y_f5jhM(3max8jL_HHlY(VWyX>$@vscMF zPEOmb_$Km=?P_X-b)GanEOIu&1Gq2;2u$xRD8oiWfgZ*~!MUPd55L8)dLw-0 z%zcCrLH_Tr1*qlWgC(8cUYSeMgtsX#yDC%k{F$^GZrT_JOmDb8>4=^A0O!vbsw;La zl<^G|Az?POcT9^Z7(mD|*9LpQW3R3YhYB;mScM(3DL@Pl3xj zhlk)nXm{=Wn!9||+G=Co!aQpgZqWlr1W+u$j>_e76hCgh=`GII_-hRxLXCQue<_nZ zc5HpHjCQfZs*YPUVyN9%O)~)=B3ijVBqehB{x`4(X#>tR61AuD@D#@U%S@**TD=lc zBkyK)oHJVOksdzu`@HofBNv*G_^Pbr9tYeBeYc_+UAOjtcAuIccSo0Hhx9U4`P4ge z@B>)Bbbox9kDy`nn0(rx>k@P2DudEkBR}Eq_e8sViaO}*T(VXuJ;aBmLjCr=^$ZxTt{wT7D5``eB=jTGI4tqC3go{j$ zWb4Z`e@FykzLRyusPq+3WHS98ti>m3Y6sor)2J7A#!uFhB9^BKWl}Xncs>nWQJ1~C z*eObW3tOmVPmv=_rVHV@NG+M+?52D3K@$J+U#%nuF{LK^*9S+{7~F*VDC;N%*aMTm z)ji&KaZVAs!==_9W$aL<2XF;+F3xMb|J@BcX{eD zn79NMX*bgsSB&Cm7(AP=K%J7#@ZL08o|YF~J=OOvaxP~$NnJ9U-YRGo~l6|$)8yaWUSoJb5NBRJg`=y z9=zk~yCWMT=S#DU8x))Z*a}}v(h1h4l zX4WCxywyK(!%+?mJ6wK?k1fWfG}(2S%brZnXYr^)oPcA2mf z{NOVSzjY$*-t}els|E-BXs(_uLhK+-+fIAG55gPD1a_|zlGLvG<1=Yxo7sC?8w*FX zBN(plsux^2V2vTNxNHTxTGiSg0FMgu&Y5qj4p~%K^lyn$rPREgWMTt_oy&edgx?EW zA+u4cVTYo$YgJiR6oQ?PIPD_lM$%k5;2N3pI7U(W9}!uww5U$gCsT0mn(glXHh}S$H5pq#Y9MYuz9!9yT%7gM&~LC;KvqaT+xic zjkQadMBX(#g_PZN%gK*Kp&7SPByDKqy|9I%8*pvqz)CHnA&*gl3B`^!)f}lxA)P|G zEXUAEN@~4qH5g2zY$cchTPPlnSJiSry7zN}b{@PGPzn`J;74REk2On2(Z=wWsxvPx z5z^g6VkDETq;Jhw<3<`}EE$_d4mWU9w?$ozdsP=6Z^;P#X$(=vIcYP#fs67!S-1L# z@ZzGQ988p%0R$uK44ZsdH07Q;FDxfW|6bKBYWyA7UYPjN(aU^{YZxrrdcFoO2xV__c&fPvfU`1F1h`D&NG}83yf$(}CE!DVT4R)lS}<*( zDm`wfyzXC)y~M{fj5bMVSQ`^&E*z%Cy;-|g?_od#`Zu=hDD$@9wzq)0yy{-P#DLk% z(c$ENPDslC|F)34sf5ghtX@z^$po>_H=<++`h@$*jLeRF!7x$#2xjuyXjl++IHyDzVsxZ}v@zo08ZOI?62IHj`Oa|^i#hat-5c&LKiYlc zzYijuwJ}i^h<&P^gQE|AuXaBGF0uGJ-u?vHCz3H3?$&!_ZM~M+Q95ka7ym@%Le|TB=;jX+K0Y> zXW3$cfW2S&2to1;I2XTKa=V=xmTR<0VmxYj;P4SSkM@gMRaia&sl`?twRCGZ?%f8% zVTZm@2y8;S*PF)b3t*hsZBIU2{08!=-s2na&D-ct;_bfw*+EX(+FQ5>#NhamEJ0wP zcJ@VR@P2gO6su2v^Bcn=dUh%l2?~KQI&)D`C~)m)UYb(+kHb1x1{-&=x*5A zs)A581-5Y_BMsH?smxA|;5yAN}gH=Kpf2F@8b^N|AD0*Qep!)ta!f zZD(mU3ff?qY`jW5&dPI5pv{ie%Fe!QmW&&^ofhOOj6FaW=E57)B*248beJa$u4hzZ z_jHa<;HW(TjQ`BP?Qu$2`);=#9}-!}(h{)`i6O#Ue*L1gY(!$3sx_ZuC570FAO+?1 z*hU*0zJ@bd_M6kL6|SQlYcC@Zl~KLvLx~nJ20W%+FM^ObBY{Sj7#|qbJ@` zof!M(XO?-(_`r}zG;p7Xtmy1hzRMHh2(rz zih&2~?ec6#X|6Rmh}3(Q*m7N6YkXPeuHtIUoxtEr>MNuD-J zHi&;8x>Sf)i7MzjFFPokKErQ-juY-4dL1pqcsT(%4}I>&(9zx2jtxuoakhTT1!wQz z@9taw=Rm`ry%hk|4Orb^P{@?6o&Tc8Ycv4n{jas#6R7ek%r{75wE`Z z-C)BH1b?|fnM^tN7><-A9Gb;C2aJTaV8OM1X15u|rHAlY0NVeHLY9QsiI5*>-{*$~ zzuorZh2IKrJLqnrR^?-@=1WW2cI2Zs4MY;~Qu00e=mg;~Z7f(hRrxD||NQWaPT~`N zNEGVJzl;stv6{p8X(AlL&0PWi?f^iWG_n#x_+F%%HDcfUw(6m3ZgAzot#TrLjn;18 zaXd>YWws&AL+wG3-u?p%j~8R-aMaePMvuFxOD~*eFM;pBxGA{jbGQdzXar^wIJ1^F zbTrhe!MGEbuxlBi&zj-tKp68icMNgp`@a)q_gr*yxf;=s0@=$1To!n=kIgo^*aZNO zR$G6m$9_}UKEJ+NGa0tzi7JB7<(GgEVI|-4ZKD~HPM_gJT19@Fz$mw=E5Q z0=w7=M`U`n<}ElOlUfTBhJV~vI<&FHz|^w>go(4Dc;2YA9R@=O>3ZDktJ-$YXxVBh zf+H=eD^ik3-O8@TS_X?s&Ir4BGQ3VUr(>@-E(N--2`>b1RAo5LvL|zwI1!e zOZ?M1Bc6@!lii$I!F1O6t zsBLzYuRT*8o-gI+?YY#4`sIi*gUZ&++kcWc@Y;#21IoM=?m580)%fcyfR>ce8%B(S zO=Y8^I}PU4M5Q}9LlP0U;{MM0|K3M8vl1|#CEJu`ybtIoWLrlo#7m6M=aVT$#8OfKT5hKOTE`o zeLHamO|~u`5n|_8Yx+Jtj#c?$%vL;!j0ABkbU;uTqAu?zx6$vNwl>+m17Z#{7XEPc z0P<3!fBOn>>^t?n*gOHX)SGhH(Oe?q6tVP$({rhV;$nt2I>N)%#$9DJL)&8h-BEFn zUNgaXLDGnLK$PSkwv`Sp5kf5_ixhKVt2+! zeLE;m`u}goIuY2$7Dzdpwg{N-{5BY#nSNeyh~2-r6o4M7`9ZKg%%j|sbS>{06Rh;Rso8YT0AjjXg+ClYLUQw=D+N%Dde0a$;>mF{_i7rJT++JD##UGnQrsV(GEYCr$_@6}!`}*cVrv zR(Bs|Sm~IDaSHfuP$6@M4^_h{GQEF#^6JFJaA9^2 zeMs~eNiiX_;4?J({)h;h0C^l8o?Y@q{gl8r??5g`!>E92gYWdV)A`PmY_qytvs5z4 z>i*)hO%aX|4H<|(4A_uNpqnz##A&425jaROy&e+>ZabCOOVpLNoHgB8U${f0<5U(q z2t(34qX0YBqrDXp#ME$3_r3&6K5JLu4G@cuxPyi{L{@F3d9McI3=L@H>Tz9m`owFN2n1F!y^M@9lj-WB%m4nUgoGsK@ckZRBrs`^K0in`}A#4b8+N2J)P zfO!myRIc$RiH3nvfCmE7(oo8k4DZ&exNv4rITL^-$T3cJ*YjMEZaSN#5>SKw^V;J3 z7rmduz+En(kT5994kJllhJj@fHh@-;113RQ1**cJ8PE!Xu1Xvl!POja8hQnJStN7K z4yV+5+gc-zsk+?5XQutQc!^dWcoho%)8{Lk&k{;Z<$n-QGZ)5?vu7At)qsA2ddNNW zO|{-bEx*QniukX(KY%>KMjE?STm3st?VeldX!kgf+XByRe{n9+kED2dAExIY3taf~ni!w|Jy)(afUe;1Q&GV$L0;llsb!>l0 z;nCMA0K+z>#UP#wKQvwL=<0X*Vj=owb5DFTYNuI?bF0|!ItWPhF|MH9f818uHsI=j z5vWd6zyvD)-Zb@XreeV-mBrsVs}c+`mBjE@r4k`zW*pDSO9S!0B!FBX-d3`|+V8tT zl_bxT=}nw$QzzV6_2lr$b219H2;!r#_wNfn2Z){){ht3Z8&J!-3jkf!?KH8a6v$)$ zQ63v9P~+HKeLv5Z8duJo9jg?b9dA(b(2j9$zms5-iQ=?9-Ctdk>?wd!I3kp>AG@eY zbjY>Ss>`Z0Cc4Ip90xTI%iy=50zUadc5BJXwpzrK43KJoKAEdzQ9<7PkGg`bK+AA! zA!Vrxg_J|)R*lgY;knOGHIwTph49B=kZ@KynG7C)sUEgsF;*U73#w8nGoud*(c&w8 zxB{aCk||}p$bl-%5lv#-L%-28X~i}Jswc!BkH6#BuX%)suSSIz;nUGorP+_+m_tgf;j zl*yp9F{OkKyaXhCF`U^Q%xbauhYdBjBidmPP*9- zl$#!>buw;9)Z5lS^wJ&lE`hAOl8vkYm95ebaq#0`wuz{0Cq0OXY=XSZ;X)NgzYk2^ z0R@9uJ6LF^m}BZZz1^-ODuQ)stk%_dV+o&8dEu!q!@}3LmqC4Uu7Chi5I~Z=&j4>P zo*pUV*AEc#J@rN`oh;|8xAVIajg}ZYF^zaRwBYWP}o-|xr(!ISu``?%G=x0scf&<~WdJW>5nBD~9&PgtIL{IAlx1hC(<1Q5Gjk?nV&M ze^|>u2WSLt_q^9-h!Otw86A3g+R0;PhbpDVn_P{}4%fJAUsd!f#5c

RzgaF)g9i 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 M~Sv=&?RbiHdtRs(Ct=+PGG=9P# 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 - - - - - - - - - - - - - - - - - EasyReflectometry - diff --git a/docs/src/api/api.rst b/docs/src/api/api.rst deleted file mode 100644 index 55bd611a..00000000 --- a/docs/src/api/api.rst +++ /dev/null @@ -1,85 +0,0 @@ -API -### - -Model -===== -Model is a sample, a background and a resolution. - -.. toctree:: - :maxdepth: 1 - - model - -Sample -====== -Sample is build from assemblies. - -.. toctree:: - :maxdepth: 1 - - sample - -Project -======= -Project provides a higher-level interface for managing models, experiments, and ORSO import. - -.. toctree:: - :maxdepth: 1 - - project - -Fitting -======= -Fitting helpers and objective functions. - -.. toctree:: - :maxdepth: 1 - - fitting - -Assemblies -========== -Assemblies are collections of layers that are used to represent a specific physical setup. - -.. toctree:: - :maxdepth: 1 - - assemblies/multilayer - assemblies/repeating_multilayer - assemblies/surfactant_layer - assemblies/gradient_layer - -Elements -======== -Elements are the building blocks that are required to construct a sample. - -Layers ------- -Layers are basic elements and used to represent a single layer of material with a thickness and a roughness. - -.. toctree:: - :maxdepth: 1 - - elements/layer - elements/layer_area_per_molecule - -Materials ---------- -Materials are the most basic elements and are used to represent a material with given physical properties. - -.. toctree:: - :maxdepth: 1 - - elements/material - elements/material_density - elements/material_mixture - elements/material_solvated - -Data -==== -Collection of helper functions. - -.. toctree:: - :maxdepth: 1 - - data diff --git a/docs/src/api/assemblies/gradient_layer.rst b/docs/src/api/assemblies/gradient_layer.rst deleted file mode 100644 index dcba2dee..00000000 --- a/docs/src/api/assemblies/gradient_layer.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.assemblies.gradient_layer - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/docs/src/api/assemblies/multilayer.rst b/docs/src/api/assemblies/multilayer.rst deleted file mode 100644 index 36cb6172..00000000 --- a/docs/src/api/assemblies/multilayer.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.assemblies.multilayer - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/assemblies/repeating_multilayer.rst b/docs/src/api/assemblies/repeating_multilayer.rst deleted file mode 100644 index 3698b4e5..00000000 --- a/docs/src/api/assemblies/repeating_multilayer.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.assemblies.repeating_multilayer - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/assemblies/surfactant_layer.rst b/docs/src/api/assemblies/surfactant_layer.rst deleted file mode 100644 index 1928733c..00000000 --- a/docs/src/api/assemblies/surfactant_layer.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.assemblies.surfactant_layer - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/data.rst b/docs/src/api/data.rst deleted file mode 100644 index 60abd70a..00000000 --- a/docs/src/api/data.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.data.measurement - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/docs/src/api/elements/layer.rst b/docs/src/api/elements/layer.rst deleted file mode 100644 index ed18bbe4..00000000 --- a/docs/src/api/elements/layer.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.elements.layers.layer - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/elements/layer_area_per_molecule.rst b/docs/src/api/elements/layer_area_per_molecule.rst deleted file mode 100644 index 1a238df3..00000000 --- a/docs/src/api/elements/layer_area_per_molecule.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.elements.layers.layer_area_per_molecule - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/docs/src/api/elements/material.rst b/docs/src/api/elements/material.rst deleted file mode 100644 index 8527a10f..00000000 --- a/docs/src/api/elements/material.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.elements.materials.material - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/elements/material_density.rst b/docs/src/api/elements/material_density.rst deleted file mode 100644 index 289f25bd..00000000 --- a/docs/src/api/elements/material_density.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. automodule:: easyreflectometry.sample.elements.materials.material_density - :members: - :undoc-members: - :show-inheritance: - \ No newline at end of file diff --git a/docs/src/api/elements/material_mixture.rst b/docs/src/api/elements/material_mixture.rst deleted file mode 100644 index 26afb58d..00000000 --- a/docs/src/api/elements/material_mixture.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.elements.materials.material_mixture - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/elements/material_solvated.rst b/docs/src/api/elements/material_solvated.rst deleted file mode 100644 index d990201c..00000000 --- a/docs/src/api/elements/material_solvated.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.elements.materials.material_solvated - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/fitting.rst b/docs/src/api/fitting.rst deleted file mode 100644 index 65e1ba15..00000000 --- a/docs/src/api/fitting.rst +++ /dev/null @@ -1,112 +0,0 @@ -Fitting -======= - -.. currentmodule:: easyreflectometry.fitting - -Objective functions and non-positive variance handling ------------------------------------------------------ - -:class:`MultiFitter` supports several objective modes for handling reflectometry -data during fitting, especially when measured variances are non-positive. - -The default objective is ``hybrid``. This uses ordinary weighted least squares -for points with positive variance and applies a Mighell-style substitution only -to points whose variance is non-positive. The older ``legacy_mask`` mode -drops non-positive-variance points before fitting. The ``mighell`` mode applies the -Mighell transform to every point. - -Mighell objective -~~~~~~~~~~~~~~~~~ - -The full ``mighell`` objective follows the algebraic form of the -``chi^2_gamma`` statistic described by Mighell for Poisson-distributed count -data: - -.. math:: - - \chi^2_\gamma = - \sum_i \frac{[n_i + \min(n_i, 1) - m_i]^2}{n_i + 1} - -where ``n_i`` are observed counts and ``m_i`` are model values. - -In EasyReflectometry this is implemented as a weighted least-squares problem. -For each observed value ``y_i`` the fitted target is shifted to - -.. math:: - - y_{\mathrm{eff},i} = y_i + \min(y_i, 1) - -and the effective uncertainty is - -.. math:: - - \sigma_i = \sqrt{y_i + 1} - -so the minimized objective is - -.. math:: - - \sum_i \left(\frac{y_{\mathrm{eff},i} - f_i}{\sigma_i}\right)^2 = - \sum_i \frac{[y_i + \min(y_i, 1) - f_i]^2}{y_i + 1} - -This is the same algebraic form as Mighell's statistic, with the model value -``f_i`` replacing ``m_i``. - -Scope and interpretation -~~~~~~~~~~~~~~~~~~~~~~~~ - -Mighell's statistic was derived for Poisson-distributed count data. In -reflectometry workflows, the fitted values are usually normalized -reflectivities or intensities rather than raw counts. They may already have -been processed, scaled, background-corrected, or otherwise transformed before -they reach the fitter. - -This distinction matters when interpreting the result. The full ``mighell`` -objective is not only a reweighting of residuals; it also changes the fitted -target from ``y`` to ``y + min(y, 1)``. For values between zero and one, this -can substantially increase the target value. A fit can therefore have a good -Mighell objective value while looking poorer against the originally plotted -reflectivity curve, or while having a worse classical chi-square. - -For reflectometry data, ``hybrid`` is generally the recommended compromise: -it preserves ordinary weighted least-squares behavior where positive variances are -available, while still allowing non-positive-variance points to contribute through the -Mighell-style substitution. - -Objective modes -~~~~~~~~~~~~~~~ - -``hybrid`` - Default. Use standard weighted least squares for points with positive - variance and apply the Mighell substitution only where variance is - non-positive. - -``mighell`` - Apply the Mighell transform to all points. The reported objective chi-square - is evaluated in transformed objective space and should not be interpreted as - a classical chi-square against the original reflectivity values. - -``legacy_mask`` - Remove non-positive-variance points before fitting and use standard weighted least - squares for the remaining points. - -``auto`` - Alias for ``hybrid``. - -Fit metrics -~~~~~~~~~~~ - -The fitter exposes both objective-space and classical fit metrics after fitting. -``objective_chi2`` and ``objective_reduced_chi`` describe the minimized -objective, which may include transformed targets under ``hybrid`` or -``mighell``. ``classical_chi2`` and ``classical_reduced_chi`` are computed -against the original observed reflectivity values using only points with -positive variance. - -API reference -------------- - -.. automodule:: easyreflectometry.fitting - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/docs/src/api/model.rst b/docs/src/api/model.rst deleted file mode 100644 index 4157e161..00000000 --- a/docs/src/api/model.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.model.model - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/project.rst b/docs/src/api/project.rst deleted file mode 100644 index 2f8f2932..00000000 --- a/docs/src/api/project.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.project - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/api/sample.rst b/docs/src/api/sample.rst deleted file mode 100644 index d7ed6167..00000000 --- a/docs/src/api/sample.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. automodule:: easyreflectometry.sample.collections.sample - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file diff --git a/docs/src/authors.rst b/docs/src/authors.rst deleted file mode 100644 index 7739272f..00000000 --- a/docs/src/authors.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../AUTHORS.rst diff --git a/docs/src/conf.py b/docs/src/conf.py deleted file mode 100644 index 127779c9..00000000 --- a/docs/src/conf.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python -# -# EasyReflectometry documentation build configuration file, created by -# sphinx-quickstart on Fri Jun 9 13:47:02 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another -# directory, add these directories to sys.path here. If the directory is -# relative to the documentation root, use os.path.abspath to make it -# absolute, like shown here. -# -import datetime -import os -import sys -from pathlib import Path - -import toml - -import easyreflectometry - -sys.path.insert(0, os.path.abspath('../src')) - -main_root = Path(__file__).parents[2] -sys.path.append(str(main_root)) -project_info = toml.load(os.path.join(main_root, 'pyproject.toml')) - -# -- General configuration --------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.githubpages', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'sphinx.ext.intersphinx', - 'sphinx.ext.githubpages', - 'sphinx.ext.imgmath', - 'sphinx.ext.mathjax', - 'sphinx_autodoc_typehints', - 'sphinx_copybutton', - 'nbsphinx', - 'myst_parser', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'EasyReflectometry' -copyright = f'{datetime.date.today().year}, EasyReflectometry' -author = 'EasyReflectometry' - -# The version info for the project you're documenting, acts as replacement -# for |version| and |release|, also used in various other places throughout -# the built documents. -# -# TODO: This should be updated to use the version from the package when that is working. -# The short X.Y version. -# The full version, including alpha/beta/rc tags. -version = easyreflectometry.__version__ - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = 'en' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - -# Ensuring the order in the file is kept. -autoclass_content = 'class' -autodoc_member_order = 'bysource' -autodoc_typehints = 'signature' -autodoc_class_signature = 'separated' - -# -- Options for HTML output ------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_book_theme' -html_logo = os.path.join('_static', 'logo.png') -html_favicon = os.path.join('_static', 'favicon.ico') -html_theme_options = { - # 'logo_only': True, - 'navigation_with_keys': True -} -html_baseurl = 'https://docs.easyreflectometry.org' -# Theme options are theme-specific and customize the look and feel of a -# theme further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -nbsphinx_execute_arguments = [ - '--Session.metadata=scipp_docs_build=True', -] - - -# -- Options for HTMLHelp output --------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'EasyReflectometrydoc' - - -# -- Options for LaTeX output ------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'EasyReflectometry.tex', 'EasyReflectometry Documentation', 'Andrew R. McCluskey', 'manual'), -] - - -# -- Options for manual page output ------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [(master_doc, 'EasyReflectometry', 'EasyReflectometry Documentation', [author], 1)] - - -# -- Options for Texinfo output ---------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - 'EasyReflectometry', - 'EasyReflectometry Documentation', - author, - 'EasyReflectometry', - 'One line description of project.', - 'Miscellaneous', - ), -] diff --git a/docs/src/contributing.rst b/docs/src/contributing.rst deleted file mode 100644 index ac7b6bcf..00000000 --- a/docs/src/contributing.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../CONTRIBUTING.rst diff --git a/docs/src/index.rst b/docs/src/index.rst deleted file mode 100644 index 5cb980fc..00000000 --- a/docs/src/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. include:: ../../README.md - :parser: myst_parser.sphinx_ - -.. toctree:: - :hidden: - :caption: Contents: - - installation.rst - usage.rst - tutorials/tutorials.rst - contributing.rst - authors.rst - api/api.rst - - -Indices and tables -================== -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/src/installation.rst b/docs/src/installation.rst deleted file mode 100644 index 7418d6f2..00000000 --- a/docs/src/installation.rst +++ /dev/null @@ -1,39 +0,0 @@ -.. highlight:: shell - -============ -Installation -============ - - -Stable release --------------- - -To install :py:mod:`easyreflectometry`, run this command in your terminal: - -.. code-block:: console - - $ pip install easyreflectometry - -If you don't have `pip`_ installed, this `Python installation guide`_ can guide -you through the process. - -.. _pip: https://pip.pypa.io -.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ - - -Developer Instrutions ---------------------- - -Clone the public repository: - -.. code-block:: console - - $ git clone git://github.com/easyScience/EasyReflectometryLib - -And install the latest developer version with: - -.. code-block:: console - - $ cd EasyReflectometryLib - $ git checkout develop - $ pip install -e .[dev] diff --git a/docs/src/usage.rst b/docs/src/usage.rst deleted file mode 100644 index fb6c24bf..00000000 --- a/docs/src/usage.rst +++ /dev/null @@ -1,42 +0,0 @@ -=============== -Getting started -=============== - -To use :py:mod:`easyreflectometry` in a project:: - - import easyreflectometry - from easyreflectometry.sample import Material, Layer - from easyreflectometry.model import Model - from easyreflectometry.fitting import MultiFitter - from easyreflectometry.plot import plot - - # Define your Material - material = Material(...) - - # Create a Layer - layer = Layer(material=material, ...) - - # Make a Sample out of the Layer - sample = Sample(layer, ...) - - # Define a Model of the experiment - model = Model( - sample=sample, - scale=1, - background=1e-6, - ... - ) - - # Set parameter bounds for fit - ... - - # Perform the fit and plot - fitter = MultiFitter(model) - analysed = fitter.fit(data) - - plot(analysed) - - -Details of specific usage of :py:mod:`easyreflectometry` can be found in the `tutorials`_. - -.. _`tutorials`: ./tutorials/tutorials.rst \ No newline at end of file diff --git a/notebooks/polarized_fitting.ipynb b/notebooks/polarized_fitting.ipynb index 83c4cedc..a75ffd76 100644 --- a/notebooks/polarized_fitting.ipynb +++ b/notebooks/polarized_fitting.ipynb @@ -358,15 +358,18 @@ "film_layer = fit_model.sample[1].layers[0]\n", "\n", "film_layer.thickness.fixed = False\n", - "film_layer.thickness.bounds = (150, 250)\n", + "film_layer.thickness.min = 150\n", + "film_layer.thickness.max = 250\n", "film_layer.magnetism.rho_m.fixed = False\n", - "film_layer.magnetism.rho_m.bounds = (0, 8)\n", + "film_layer.magnetism.rho_m.min = 0\n", + "film_layer.magnetism.rho_m.max = 8\n", "film_layer.magnetism.theta_m.fixed = False\n", - "film_layer.magnetism.theta_m.bounds = (0, 90)\n", + "film_layer.magnetism.theta_m.min = 0\n", + "film_layer.magnetism.theta_m.max = 90\n", "\n", "print('Free parameters (start values):')\n", "for parameter in fit_model.get_fit_parameters():\n", - " print(f' {parameter.name:12s} = {float(parameter.value):8.3f} bounds={parameter.bounds}')\n", + " print(f' {parameter.name:12s} = {float(parameter.value):8.3f} bounds=[{parameter.min:g}, {parameter.max:g}]')\n", "\n", "fitter = MultiFitter(fit_model)\n", "results = fitter.fit_polarized(data)\n", diff --git a/notebooks/zero_variance_fitting.ipynb b/notebooks/zero_variance_fitting.ipynb index ea3ede96..085ab4be 100644 --- a/notebooks/zero_variance_fitting.ipynb +++ b/notebooks/zero_variance_fitting.ipynb @@ -283,23 +283,28 @@ "outputs": [], "source": [ "sio2_layer.thickness.fixed = False\n", - "sio2_layer.thickness.bounds = (15, 50)\n", + "sio2_layer.thickness.min = 15\n", + "sio2_layer.thickness.max = 50\n", "\n", "film_layer.thickness.fixed = False\n", - "film_layer.thickness.bounds = (200, 300)\n", + "film_layer.thickness.min = 200\n", + "film_layer.thickness.max = 300\n", "\n", "film.sld.fixed = False\n", - "film.sld.bounds = (0.1, 3)\n", + "film.sld.min = 0.1\n", + "film.sld.max = 3\n", "\n", "model.background.fixed = False\n", - "model.background.bounds = (1e-7, 1e-5)\n", + "model.background.min = 1e-7\n", + "model.background.max = 1e-5\n", "\n", "model.scale.fixed = False\n", - "model.scale.bounds = (0.5, 1.5)\n", + "model.scale.min = 0.5\n", + "model.scale.max = 1.5\n", "\n", "print('Free parameters:')\n", "for p in model.get_fit_parameters():\n", - " print(f' {p.name:20s} = {float(p.value):.4g} bounds={p.bounds}')" + " print(f' {p.name:20s} = {float(p.value):.4g} bounds=[{p.min:g}, {p.max:g}]')" ] }, { @@ -359,15 +364,20 @@ " _model = Model(_sample, 1, 1e-6, _resolution, 'Film Model')\n", "\n", " _sio2_layer.thickness.fixed = False\n", - " _sio2_layer.thickness.bounds = (15, 50)\n", + " _sio2_layer.thickness.min = 15\n", + " _sio2_layer.thickness.max = 50\n", " _film_layer.thickness.fixed = False\n", - " _film_layer.thickness.bounds = (200, 300)\n", + " _film_layer.thickness.min = 200\n", + " _film_layer.thickness.max = 300\n", " _film.sld.fixed = False\n", - " _film.sld.bounds = (0.1, 3)\n", + " _film.sld.min = 0.1\n", + " _film.sld.max = 3\n", " _model.background.fixed = False\n", - " _model.background.bounds = (1e-7, 1e-5)\n", + " _model.background.min = 1e-7\n", + " _model.background.max = 1e-5\n", " _model.scale.fixed = False\n", - " _model.scale.bounds = (0.5, 1.5)\n", + " _model.scale.min = 0.5\n", + " _model.scale.max = 1.5\n", "\n", " _model.interface = CalculatorFactory()\n", " return _model" diff --git a/pixi.lock b/pixi.lock index 16dba96f..e457f388 100644 --- a/pixi.lock +++ b/pixi.lock @@ -203,6 +203,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -322,7 +323,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -514,6 +514,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -637,7 +638,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -815,6 +815,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -940,7 +941,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -1138,6 +1138,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1258,7 +1259,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl @@ -1443,6 +1443,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1565,7 +1566,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -1738,6 +1738,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -1861,7 +1862,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl @@ -2061,6 +2061,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2180,7 +2181,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -2372,6 +2372,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2495,7 +2496,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -2673,6 +2673,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2798,7 +2799,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -8313,8 +8313,9 @@ packages: - pypi: ./ name: easyreflectometry requires_dist: + - asteval - bumps - - easyscience + - easyscience @ git+https://github.com/easyscience/easyscience.git@sampler-engine-structure-280 - orsopy - plotly - pooch @@ -8358,6 +8359,55 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.11' +- pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + name: easyscience + version: 2.5.1+dev7 + requires_dist: + - asteval + - bumps>=1.0.4 + - dfo-ls + - lmfit + - numpy + - scipp + - build ; extra == 'dev' + - copier ; extra == 'dev' + - docstripy ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - ipykernel ; extra == 'dev' + - ipympl ; extra == 'dev' + - ipython ; extra == 'dev' + - ipywidgets ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterlab ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - matplotlib ; 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' + - pooch ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' + - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl name: backrefs version: '7.0' @@ -13137,56 +13187,6 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - name: easyscience - version: 2.5.1 - sha256: e44c3efb10f8e040ba88523c6ba58e131e3174721f24801d036fdc5a24e622f2 - requires_dist: - - asteval - - bumps>=1.0.4 - - dfo-ls - - lmfit - - numpy - - scipp - - build ; extra == 'dev' - - copier ; extra == 'dev' - - docstripy ; extra == 'dev' - - format-docstring ; extra == 'dev' - - gitpython ; extra == 'dev' - - interrogate ; extra == 'dev' - - ipykernel ; extra == 'dev' - - ipympl ; extra == 'dev' - - ipython ; extra == 'dev' - - ipywidgets ; extra == 'dev' - - jinja2 ; extra == 'dev' - - jupyterlab ; extra == 'dev' - - jupyterquiz ; extra == 'dev' - - jupytext ; extra == 'dev' - - matplotlib ; 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' - - pooch ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pydoclint ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - pytest-xdist ; extra == 'dev' - - pyyaml ; extra == 'dev' - - radon ; extra == 'dev' - - ruff ; extra == 'dev' - - spdx-headers ; extra == 'dev' - - validate-pyproject[all] ; extra == 'dev' - - versioningit ; extra == 'dev' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl name: cycler version: 0.12.1 diff --git a/pyproject.toml b/pyproject.toml index 9f90457f..f646fd75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'easyscience', + 'easyscience @ git+https://github.com/easyscience/easyscience.git@sampler-engine-structure-280', 'scipp', 'refnx', 'refl1d>=1.0.0', @@ -31,6 +31,7 @@ dependencies = [ 'svglib<1.6 ; platform_system=="Linux" or sys_platform == "darwin"', 'xhtml2pdf', 'bumps', + 'asteval', 'pooch', 'plotly', ] diff --git a/src/easyreflectometry/__init__.py b/src/easyreflectometry/__init__.py index a18fee06..840218e3 100644 --- a/src/easyreflectometry/__init__.py +++ b/src/easyreflectometry/__init__.py @@ -6,6 +6,16 @@ from importlib import metadata from .analysis.bayesian import PosteriorResults +from .constraints import clamp_sum_partners +from .constraints import constrain +from .constraints import constrain_equal +from .constraints import constrain_to_sum +from .constraints import derived_parameter +from .constraints import is_constrained_to_sum +from .constraints import restore_sum_partners +from .constraints import unconstrain +from .inequality_constraints import InequalitySpec +from .inequality_constraints import UnitError from .project import Project try: @@ -14,7 +24,17 @@ __version__ = '0.0.0' __all__ = [ + 'InequalitySpec', 'Project', 'PosteriorResults', + 'UnitError', '__version__', + 'clamp_sum_partners', + 'constrain', + 'constrain_equal', + 'constrain_to_sum', + 'derived_parameter', + 'is_constrained_to_sum', + 'restore_sum_partners', + 'unconstrain', ] diff --git a/src/easyreflectometry/_bumps_constraints.py b/src/easyreflectometry/_bumps_constraints.py new file mode 100644 index 00000000..c76256df --- /dev/null +++ b/src/easyreflectometry/_bumps_constraints.py @@ -0,0 +1,119 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Inequality-constraint enforcement for the BUMPS engine. + +EasyScience builds the ``FitProblem`` internally and offers no hook for +attaching inequality penalties to it, so the enforcement lives here +rather than in the core: :func:`install` wraps ``build_curve_problem`` +and :func:`applied` makes a factory current for the duration of a fit, +attaching the constraints to each freshly built problem. + +``FitProblem.constraints`` is a plain list read live by +``constraints_nllf()``, and ``build_curve_problem`` returns the ``Curve`` +whose ``.pars`` is the very ``{prefixed name: BumpsParameter}`` mapping +the factory expects — so attaching after construction is equivalent to +passing ``constraints=`` to the constructor. + +Only problems built by ``build_curve_problem`` are covered. Calling +``Bumps.fit`` with a caller-supplied ``model=`` bypasses it (that branch +constructs ``FitProblem(model)`` itself) and the constraints would be +dropped. The library never passes ``model=``, so this is reachable only +by driving the core minimizer directly. + +Nothing here is passed to the core as a keyword: ``Bumps.fit`` accepts +arbitrary ``**kwargs`` and would swallow an unrecognised one without +complaint, which is exactly the silent-unconstrained-fit failure this +module exists to prevent. +""" + +from __future__ import annotations + +import contextlib +import contextvars +import functools +from typing import Callable +from typing import Iterator +from typing import Optional + +from easyscience.fitting.minimizers import minimizer_bumps +from easyscience.fitting.samplers import sampler_dream + +#: Raised for engines that cannot enforce inequality constraints. The wording is +#: matched by the test suite and printed in the constraints tutorial, so keep it +#: stable; ``constraints_factory`` names this library's own keyword. +NON_BUMPS_ERROR = ( + "Inequality constraints (constraints_factory) require the BUMPS engine; the selected minimizer uses '{package}'." +) + +_active: contextvars.ContextVar[Optional[Callable]] = contextvars.ContextVar( + 'easyreflectometry_constraints_factory', default=None +) + + +def _patch(module) -> None: + """Wrap ``build_curve_problem`` in one consumer namespace.""" + original = module.build_curve_problem + if getattr(original, '_easyreflectometry_shim', False): + return + + @functools.wraps(original) + def build_curve_problem(*args, **kwargs): + problem, fit_function, curve = original(*args, **kwargs) + factory = _active.get() + if factory is not None: + # ``curve.pars`` is empty when no parameter is free; the factory + # then yields constant-only penalties, which is harmless. + problem.constraints = list(factory(dict(curve.pars))) + # Only to get the warning BUMPS emits for an infeasible start + # point; the penalties themselves are already live without this. + problem.model_reset() + return problem, fit_function, curve + + build_curve_problem._easyreflectometry_shim = True + module.build_curve_problem = build_curve_problem + + +def install() -> None: + """Patch the fitting and sampling entry points. + + Both consumers bind ``build_curve_problem`` at import time, so each + namespace has to be patched; patching the defining module alone has no + effect. Idempotent. + """ + _patch(minimizer_bumps) + _patch(sampler_dream) + + +def is_applied() -> bool: + """Whether an :func:`applied` block is active in the current context. + + Lets an inner wrapper (``MultiFitter`` routes the raw + ``easy_science_multi_fitter.fit`` through the constraints machinery) + detect that an outer block already attached a factory — possibly an + explicit one that must not be overridden by re-resolving the provider. + """ + return _active.get() is not None + + +@contextlib.contextmanager +def applied(factory: Optional[Callable]) -> Iterator[None]: + """Attach `factory`'s constraints to problems built inside the block. + + A no-op when `factory` is ``None``. + + Parameters + ---------- + factory : Optional[Callable] + Receives the ``{prefixed name: BumpsParameter}`` mapping of a freshly + built problem and returns the constraints to attach. + """ + if factory is None: + yield + return + install() + token = _active.set(factory) + try: + yield + finally: + _active.reset(token) diff --git a/src/easyreflectometry/analysis/bayesian.py b/src/easyreflectometry/analysis/bayesian.py index b5bdfb43..d6f23edf 100644 --- a/src/easyreflectometry/analysis/bayesian.py +++ b/src/easyreflectometry/analysis/bayesian.py @@ -1124,7 +1124,11 @@ def _save_parameter_state(model) -> dict: """ state = {} for param in model.get_parameters(): - state[param.unique_name] = (param.value, param.error) + # Dependent parameters (constraints, `Model.total_thickness`) are derived + # from the others: they cannot be written back, and restoring the + # parameters they follow already restores them. + if param.independent: + state[param.unique_name] = (param.value, param.error) return state diff --git a/src/easyreflectometry/constraints.py b/src/easyreflectometry/constraints.py new file mode 100644 index 00000000..6193bd2a --- /dev/null +++ b/src/easyreflectometry/constraints.py @@ -0,0 +1,329 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""User-facing helpers for constraining model parameters. + +These functions are thin wrappers around the EasyScience +parameter-dependency mechanism (``Parameter.make_dependent_on`` / +``Parameter.make_independent``). The dependency graph is owned entirely +by EasyScience; constrained parameters are excluded from the free fit +parameters. + +Constraints created through this module are preserved when a project is +saved and reloaded: ``Project.as_dict`` records the expression and the +structural paths of the parameters it refers to, and ``Project.from_dict`` +re-applies them once every parameter exists again. Dependencies created by +calling ``make_dependent_on`` directly are outside that contract and are +not persisted. + +Cross-parameter *inequalities* (``t_head < t_tail``) are not dependencies +but fit penalties; see :mod:`easyreflectometry.inequality_constraints`. +""" + +from __future__ import annotations + +import math +import numbers +import re +from typing import Iterable +from typing import Optional +from typing import Union + +from easyscience.variable import DescriptorNumber +from easyscience.variable import Parameter + +__all__ = [ + 'clamp_sum_partners', + 'constrain', + 'constrain_equal', + 'constrain_to_sum', + 'derived_parameter', + 'is_constrained_to_sum', + 'restore_sum_partners', + 'unconstrain', +] + +#: Marks a dependency created through this module, so ``Project`` can persist +#: user constraints without also re-applying the internal ones that assemblies +#: and materials rebuild themselves. Read together with ``independent``: see +#: :func:`easyreflectometry.project.Project._user_constraints`. +USER_CONSTRAINT_FLAG = '_easyreflectometry_user_constraint' + + +def constrain_equal(parameter: Parameter, to: DescriptorNumber) -> None: + """Tie `parameter` to always equal `to`. + + `parameter` becomes dependent: it is removed from the free fit + parameters, immediately takes the value of `to`, and follows it from + then on. Its value, unit, variance, min and max are replaced by + `to`'s, and its `fixed` flag is cleared. While constrained, the + parameter's value and bounds cannot be set directly. + + Parameters + ---------- + parameter : Parameter + Parameter to make dependent (the follower). + to : DescriptorNumber + Parameter (or descriptor) to follow. + """ + constrain(parameter, 'a', a=to) + + +def constrain(parameter: Parameter, expression: str, **parameters: DescriptorNumber) -> None: + """Tie `parameter` to an arbitrary expression of other parameters. + + Placeholders in `expression` are supplied as keyword arguments: + + .. code-block:: python + + constrain(layer_b.thickness, '2 * t', t=layer_a.thickness) + + `parameter` becomes dependent; its value, unit, variance, min and max + are replaced by the evaluated expression and its `fixed` flag is + cleared. Placeholder names must be valid Python identifiers and not + Python keywords. An unmapped name in the expression that matches a + mathematical builtin (`e`, `pi`, `sin`, ...) evaluates silently + instead of raising `NameError`, so prefer descriptive placeholder + names. + + Parameters + ---------- + parameter : Parameter + Parameter to make dependent (the follower). + expression : str + Mathematical expression to evaluate, e.g. ``'2 * t'``. + parameters : DescriptorNumber + Placeholder-name to parameter mapping for `expression`. + """ + parameter.make_dependent_on(dependency_expression=expression, dependency_map=parameters) + setattr(parameter, USER_CONSTRAINT_FLAG, True) + + +def unconstrain(parameter: Parameter) -> None: + """Remove any constraint from `parameter`. + + Idempotent: calling it on an already-independent parameter is a + no-op. The parameter keeps its current (last evaluated) value and + becomes fittable again. Its bounds, unit, variance and `fixed` state + are not restored to their pre-constraint values. Review and reset + the bounds before fitting. + + Parameters + ---------- + parameter : Parameter + Parameter to make independent again. + """ + if not parameter.independent: + parameter.make_independent() + if hasattr(parameter, USER_CONSTRAINT_FLAG): + delattr(parameter, USER_CONSTRAINT_FLAG) + + +def derived_parameter( + name: str, + expression: str, + unit: Optional[str] = None, + **parameters: DescriptorNumber, +) -> Parameter: + """Create a standalone read-only parameter computed from other parameters. + + The returned parameter is *dependent*: it never enters a fit, it + follows `expression` whenever any of `parameters` changes, and its + value cannot be set directly — a live calculation to display or reuse + inside another :func:`constrain` expression. + + .. code-block:: python + + total = derived_parameter('total', 't1 + t2', t1=layer_1.thickness, t2=layer_2.thickness) + constrain(layer_3.thickness, 'T - t', T=total, t=layer_4.thickness) + + .. warning:: + + A standalone derived parameter belongs to no model, so it has no + structural path (:meth:`~easyreflectometry.Project.parameter_path` + returns ``None``): it cannot be referenced from an inequality + constraint, and a project holding a :func:`constrain` that depends + on one cannot be saved (``Project.as_dict`` raises). It is a + session-only convenience. For a derived value that must survive + save/load or appear in inequalities, use a parameter owned by the + model, such as :attr:`~easyreflectometry.model.Model.total_thickness`. + + Parameters + ---------- + name : str + Display name of the new parameter. + expression : str + Mathematical expression over the placeholder names in `parameters`. + unit : Optional[str], optional + Unit the result is converted to. By default the unit produced by + the expression is kept. + parameters : DescriptorNumber + Placeholder-name to parameter mapping for `expression`. + + Returns + ------- + Parameter + A new dependent parameter. + """ + if not parameters: + raise ValueError('derived_parameter needs at least one parameter to depend on.') + return Parameter.from_dependency( + name=name, + dependency_expression=expression, + dependency_map=dict(parameters), + desired_unit=unit, + ) + + +def constrain_to_sum( + parameter: Parameter, + of_parameters: Iterable[DescriptorNumber], + *, + total: Union[DescriptorNumber, numbers.Number, None] = None, +) -> None: + """Constrain `parameter` so that the sum of `of_parameters` stays equal to `total`. + + `parameter` becomes dependent and takes the value + ``total - sum(other parameters)``, where "other" means every entry of + `of_parameters` except `parameter` itself (it may be listed or not). + This is the "keep the total film thickness fixed while fitting how it + is split" idiom: + + .. code-block:: python + + constrain_to_sum(layer_b.thickness, [layer_a.thickness, layer_b.thickness], total=120.0) + + Parameters + ---------- + parameter : Parameter + Parameter to make dependent (it absorbs the remainder). + of_parameters : Iterable[DescriptorNumber] + The parameters whose sum is constrained. + total : Union[DescriptorNumber, numbers.Number, None], optional + The target sum: a parameter/descriptor (e.g. a + :func:`derived_parameter`) or a plain number in `parameter`'s + unit. By default the current sum of `of_parameters` is frozen as + a constant. + """ + others = [p for p in of_parameters if p is not parameter] + if not others and total is None: + raise ValueError('constrain_to_sum needs at least one other parameter or an explicit total.') + if total is None: + total = float(parameter.value) + sum(float(p.value) for p in others) + if isinstance(total, numbers.Number): + total = DescriptorNumber(name=f'{parameter.name}_sum_total', value=float(total), unit=str(parameter.unit)) + elif not isinstance(total, DescriptorNumber): + raise TypeError('total must be a number, a DescriptorNumber/Parameter or None.') + + dependency_map = {'total': total} + terms = [] + for index, other in enumerate(others): + alias = f'p{index}' + dependency_map[alias] = other + terms.append(alias) + expression = 'total' if not terms else f'total - ({" + ".join(terms)})' + constrain(parameter, expression, **dependency_map) + + +#: Aliases :func:`constrain_to_sum` builds: the frozen sum plus one ``p`` +#: per partner. :func:`is_constrained_to_sum` recognizes exactly this shape. +_SUM_TOTAL_ALIAS = 'total' +_SUM_PARTNER_ALIAS = re.compile(r'p\d+') + + +def is_constrained_to_sum( + parameter: Parameter, + of_parameters: Optional[Iterable[DescriptorNumber]] = None, +) -> bool: + """Whether `parameter` currently carries a :func:`constrain_to_sum` dependency. + + Purely introspective — nothing is changed. Detection reads the public + dependency map and recognizes the alias shape :func:`constrain_to_sum` + builds (``'total'`` plus ``'p0'``, ``'p1'``, ...), so it also holds for a + constraint restored from a project file. + + Parameters + ---------- + parameter : Parameter + The parameter that would absorb the remainder. + of_parameters : Optional[Iterable[DescriptorNumber]], optional + When given, every entry (except `parameter` itself) must additionally + be among the partners of the sum. By default only the dependency shape + is checked. + """ + if getattr(parameter, 'independent', True): + return False + dependency_map = parameter.dependency_map or {} + if _SUM_TOTAL_ALIAS not in dependency_map: + return False + partner_aliases = set(dependency_map) - {_SUM_TOTAL_ALIAS} + if any(not _SUM_PARTNER_ALIAS.fullmatch(alias) for alias in partner_aliases): + return False + if of_parameters is not None: + partners = {id(dependency_map[alias]) for alias in partner_aliases} + required = {id(p) for p in of_parameters if p is not parameter} + if not required <= partners: + return False + return True + + +# The parameter tied by `constrain_to_sum` takes whatever the partners leave +# over, so on its own the constraint lets a fit push the partners past the +# total and drive the remainder negative (e.g. a layer of negative thickness). +# `clamp_sum_partners` caps each partner at the headroom it actually leaves, +# sharing the slack in proportion to the current values. The original maxima +# are stashed on the parameters under this attribute so +# `restore_sum_partners` can give them back; `Project.as_dict`/`from_dict` +# persist the stash by structural path so the round trip survives save/load. +SUM_PARTNER_MAX_BACKUP = '_sum_partner_previous_max' + + +def clamp_sum_partners(partners: Iterable[Parameter], remainder: float) -> None: + """Narrow the partners' maxima so a :func:`constrain_to_sum` remainder cannot go below zero. + + Each partner's ``max`` is capped at ``value * (1 + remainder / occupied)`` + — its share of the remaining budget, distributed in proportion to the + current values. A partner whose ``max`` is already tighter is left alone, + and so is one whose cap would collapse onto its ``min`` (a degenerate + ``min == max`` bound is rejected by EasyScience). The original maxima are + stashed on the parameters for :func:`restore_sum_partners`. + + Parameters + ---------- + partners : Iterable[Parameter] + The free parameters of the sum (everything except the tied remainder). + remainder : float + Current value of the tied remainder parameter. + """ + partners = list(partners) + occupied = sum(float(parameter.value) for parameter in partners) + if occupied <= 0.0 or remainder < 0.0: + # Nothing to share out, or the budget is already exhausted; leave the + # bounds alone rather than pin every partner at its current value. + return + for parameter in partners: + headroom = float(parameter.value) * (1.0 + remainder / occupied) + if headroom >= float(parameter.max): + continue + if math.isclose(headroom, float(parameter.min), rel_tol=1e-9, abs_tol=0.0): + continue + if not hasattr(parameter, SUM_PARTNER_MAX_BACKUP): + setattr(parameter, SUM_PARTNER_MAX_BACKUP, float(parameter.max)) + parameter.max = headroom + + +def restore_sum_partners(partners: Iterable[Parameter]) -> None: + """Give back the maxima :func:`clamp_sum_partners` narrowed. + + Idempotent: a partner without a stashed backup (never clamped, or already + restored) is left alone. A backup is only applied when it widens the + current bound — restoring never narrows a maximum that was widened in the + meantime. + """ + for parameter in partners: + previous = getattr(parameter, SUM_PARTNER_MAX_BACKUP, None) + if previous is None: + continue + delattr(parameter, SUM_PARTNER_MAX_BACKUP) + if float(previous) > float(parameter.max): + parameter.max = float(previous) diff --git a/src/easyreflectometry/fitting.py b/src/easyreflectometry/fitting.py index 37ba637e..2da76b73 100644 --- a/src/easyreflectometry/fitting.py +++ b/src/easyreflectometry/fitting.py @@ -2,7 +2,10 @@ # SPDX-License-Identifier: BSD-3-Clause +import contextlib +import functools import warnings +import weakref from typing import Any from typing import Callable @@ -13,6 +16,9 @@ from easyscience.fitting import Sampler from easyscience.fitting.multi_fitter import MultiFitter as EasyScienceMultiFitter +from easyreflectometry._bumps_constraints import NON_BUMPS_ERROR +from easyreflectometry._bumps_constraints import applied as _constraints_applied +from easyreflectometry._bumps_constraints import is_applied as _constraints_active from easyreflectometry.data import DataSet1D from easyreflectometry.data import PolarizedDataSet from easyreflectometry.model import Model @@ -21,6 +27,42 @@ _EPS = 1e-30 +class _ConstrainedEasyScienceMultiFitter(EasyScienceMultiFitter): + """EasyScience ``MultiFitter`` whose raw ``fit`` honours inequality constraints. + + ``fit`` is a read-only property on the base class (it builds a fresh + callable per access), so the interception lives in an override rather + than a monkey-patch. An explicit ``constraints_factory`` keyword is + consumed here and enforced by this library — the core takes no such + argument and would swallow it through ``**kwargs``. Without one, a call + running inside an active :meth:`MultiFitter._constraints` block passes + through untouched, leaving the outer factory in force. + """ + + #: Weak reference to the owning :class:`MultiFitter` (weak to avoid a + #: reference cycle); ``None`` disables the interception. + _constraints_owner = None + + @property + def fit(self) -> Callable: + original = EasyScienceMultiFitter.fit.fget(self) + owner = self._constraints_owner() if self._constraints_owner is not None else None + if owner is None: + return original + + @functools.wraps(original) + def fit_with_constraints(*args, **kwargs): + explicit = kwargs.pop('constraints_factory', None) + # An explicit factory wins over an outer block; without one, an + # outer block has already attached what it resolved. + if explicit is None and _constraints_active(): + return original(*args, **kwargs) + with owner._constraints(explicit, fitter=self): + return original(*args, **kwargs) + + return fit_with_constraints + + def _validate_objective(objective: str) -> str: """Validate and resolve the objective string. @@ -269,7 +311,7 @@ def __init__(self, *args: Model, objective: str = 'hybrid'): self._fit_func = [_bind_fit_func(m.interface.fit_func, m.unique_name) for m in args] self._models = args - self.easy_science_multi_fitter = EasyScienceMultiFitter(args, self._fit_func) + self.easy_science_multi_fitter = self._build_easy_science_fitter(args, self._fit_func) self._fit_results: list[FitResults] | None = None self._classical_fit_metrics: list[dict] | None = None self._objective = _validate_objective(objective) @@ -278,12 +320,88 @@ def __init__(self, *args: Model, objective: str = 'hybrid'): # to, and the spin channel each one is evaluated on (None = unpolarized). self.fit_datasets: list[DataSet1D] = [] self.fit_channels: list[Any] = [] + # Optional zero-argument callable returning the ``constraints_factory`` + # for the next fit (or ``None``). ``Project.fitter`` binds it to + # ``Project.build_constraints_factory`` so inequality constraints + # registered on the project are applied without passing them + # explicitly; an explicit ``constraints_factory=`` argument wins. + self.constraints_factory_provider: Callable[[], Callable | None] | None = None + + def _build_easy_science_fitter(self, models, fit_funcs) -> EasyScienceMultiFitter: + """Build the EasyScience fitter, with its raw ``fit`` honouring constraints. + + :meth:`for_experiments` documents that the caller drives + ``easy_science_multi_fitter.fit(...)`` directly (e.g. a GUI worker + thread), which would bypass :meth:`_constraints` and silently fit an + unconstrained problem. The returned fitter routes that path through + the constraints machinery: :attr:`constraints_factory_provider` is + resolved at call time (and in the calling thread — the shim's context + variable is thread-local), and non-BUMPS engines are rejected rather + than silently dropping the constraints. + """ + fitter = _ConstrainedEasyScienceMultiFitter(models, fit_funcs) + fitter._constraints_owner = weakref.ref(self) + return fitter + + def _resolve_constraints_factory(self, explicit: Callable | None) -> Callable | None: + if explicit is not None: + return explicit + if self.constraints_factory_provider is not None: + return self.constraints_factory_provider() + return None + + @contextlib.contextmanager + def _constraints(self, explicit: Callable | None, fitter: EasyScienceMultiFitter | None = None): + """Make any inequality constraints active for the duration of the block. + + The penalties are attached to the BUMPS problem as it is built (see + :mod:`easyreflectometry._bumps_constraints`); nothing is handed to the + core as a keyword. Engines that cannot enforce the constraints are + rejected rather than left to fit an unconstrained problem. + + Parameters + ---------- + explicit : Callable | None + Factory passed to the fit call, or None to use + :attr:`constraints_factory_provider`. + fitter : EasyScienceMultiFitter | None, optional + The fitter whose minimizer is about to run, when it is not this + one's — ``fit_polarized`` builds its own. By default, None. + """ + factory = self._resolve_constraints_factory(explicit) + if factory is not None: + minimizer = (fitter or self.easy_science_multi_fitter).minimizer + package = getattr(minimizer, 'package', None) + if package != 'bumps': + raise ValueError(NON_BUMPS_ERROR.format(package=package)) + with _constraints_applied(factory): + yield + + @staticmethod + def _keep_constraints_on_extend(sampler: Sampler, factory: Callable | None) -> None: + """Re-enter the constraints context around ``sampler.extend()``. + + The factory is current only for the duration of the sampling call that + built the problem; without this a continued chain would silently sample + an unpenalised posterior. + """ + if factory is None: + return + original = sampler.extend + + @functools.wraps(original) + def extend(*args, **kwargs): + with _constraints_applied(factory): + return original(*args, **kwargs) + + sampler.extend = extend @classmethod def for_experiments( cls, experiments: list[DataSet1D | PolarizedDataSet], objective: str = 'hybrid', + constraints_factory_provider: Callable[[], Callable | None] | None = None, ) -> 'MultiFitter': """Build a fitter for a mixed list of unpolarized and polarized experiments. @@ -299,6 +417,11 @@ def for_experiments( The resulting fitter is *not* run: the caller supplies the data arrays to ``easy_science_multi_fitter.fit(...)`` in the order given by :attr:`fit_datasets`, which lets a GUI drive it from a worker thread. + That call resolves :attr:`constraints_factory_provider` at call time, + so inequality constraints are applied on this path too — pass + ``constraints_factory_provider`` (e.g. + ``project.build_constraints_factory``) or set the attribute before + fitting; with none set, no inequality constraints are enforced. Note ---- @@ -317,6 +440,11 @@ def for_experiments( The loaded experiments, in the order they should be fitted. objective : str, optional Zero-variance handling strategy, see :meth:`__init__`. By default, 'hybrid'. + constraints_factory_provider : Callable[[], Callable | None] | None, optional + Zero-argument callable returning the ``constraints_factory`` for + the next fit, typically ``project.build_constraints_factory``; + stored as :attr:`constraints_factory_provider`. By default, None + (no inequality constraints are applied). Returns ------- @@ -357,12 +485,19 @@ def for_experiments( fit_funcs.append(_bind_fit_func(func, model.unique_name)) fitter._fit_func = fit_funcs - fitter.easy_science_multi_fitter = EasyScienceMultiFitter(models, fit_funcs) + fitter.easy_science_multi_fitter = fitter._build_easy_science_fitter(models, fit_funcs) fitter.fit_datasets = datasets fitter.fit_channels = channels + fitter.constraints_factory_provider = constraints_factory_provider return fitter - def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> sc.DataGroup: + def fit( + self, + data: sc.DataGroup, + id: int = 0, + objective: str | None = None, + constraints_factory: Callable | None = None, + ) -> sc.DataGroup: """Perform the fitting and populate the DataGroups with the result. Parameters @@ -374,6 +509,10 @@ def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> objective : str | None, optional Per-call override for the zero-variance objective. If ``None``, uses the instance default set at construction. By default, None. + constraints_factory : Callable | None, optional + Inequality constraints to enforce (BUMPS engines only); see + :mod:`easyreflectometry.inequality_constraints`. Defaults to what + :attr:`constraints_factory_provider` returns. By default, None. Returns ------- @@ -402,7 +541,8 @@ def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> dy.append(weights) original_arrays.append({'x': x_vals, 'y': y_vals, 'variances': variances}) - result = self.easy_science_multi_fitter.fit(x, y, weights=dy) + with self._constraints(constraints_factory): + result = self.easy_science_multi_fitter.fit(x, y, weights=dy) self._fit_results = result self._classical_fit_metrics = [] new_data = data.copy() @@ -430,7 +570,12 @@ def fit(self, data: sc.DataGroup, id: int = 0, objective: str | None = None) -> new_data['success'] = result[i].success return new_data - def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) -> FitResults: + def fit_single_data_set_1d( + self, + data: DataSet1D, + objective: str | None = None, + constraints_factory: Callable | None = None, + ) -> FitResults: """Perform fitting on a single 1D dataset. Parameters @@ -441,6 +586,9 @@ def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) objective : str | None, optional Per-call override for the zero-variance objective. If ``None``, uses the instance default set at construction. By default, None. + constraints_factory : Callable | None, optional + Inequality constraints to enforce (BUMPS engines only). Defaults + to what :attr:`constraints_factory_provider` returns. By default, None. Returns ------- @@ -459,7 +607,8 @@ def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) if obj == 'legacy_mask' and len(x_out) == 0: raise ValueError('Cannot fit single dataset: all points have zero variance.') - result = self.easy_science_multi_fitter.fit(x=[x_out], y=[y_eff], weights=[weights])[0] + with self._constraints(constraints_factory): + result = self.easy_science_multi_fitter.fit(x=[x_out], y=[y_eff], weights=[weights])[0] self._fit_results = [result] model_curve = self._fit_func[0](x_vals) self._classical_fit_metrics = [ @@ -467,7 +616,12 @@ def fit_single_data_set_1d(self, data: DataSet1D, objective: str | None = None) ] return result - def fit_polarized(self, data: PolarizedDataSet, objective: str | None = None) -> dict[str, FitResults]: + def fit_polarized( + self, + data: PolarizedDataSet, + objective: str | None = None, + constraints_factory: Callable | None = None, + ) -> dict[str, FitResults]: """Fit all measured spin channels of a polarized experiment simultaneously. Each channel dataset gets its own fit function evaluating the @@ -488,6 +642,10 @@ def fit_polarized(self, data: PolarizedDataSet, objective: str | None = None) -> objective : str | None, optional Per-call override for the zero-variance objective. If ``None``, uses the instance default set at construction. By default, None. + constraints_factory : Callable | None, optional + Inequality constraints to enforce (BUMPS engines only); see + :mod:`easyreflectometry.inequality_constraints`. Defaults to what + :attr:`constraints_factory_provider` returns. By default, None. Returns ------- @@ -545,7 +703,8 @@ def fit_polarized(self, data: PolarizedDataSet, objective: str | None = None) -> dy.append(weights) original_arrays.append({'x': x_vals, 'y': y_vals, 'variances': variances}) - results = polarized_fitter.fit(x, y, weights=dy) + with self._constraints(constraints_factory, fitter=polarized_fitter): + results = polarized_fitter.fit(x, y, weights=dy) # All channels are fitted against one parameter vector (the shared model), # so `result.n_pars` is identical across `results`; `reduced_chi` and # `classical_reduced_chi` below rely on that invariant. @@ -570,6 +729,7 @@ def mcmc_sample( initializer: str | None = None, progress_callback: Callable[..., Any] | None = None, abort_test: Callable[[], bool] | None = None, + constraints_factory: Callable | None = None, ) -> dict: """Run Bayesian MCMC sampling on reflectometry data using the DREAM sampler. @@ -587,6 +747,11 @@ def mcmc_sample( uses ``'eps'``). :param progress_callback: Optional callback for progress updates during sampling. Forwarded to the core MultiFitter. + :param abort_test: Optional callable returning ``True`` to abort sampling. + :param constraints_factory: Inequality constraints to enforce (see + :mod:`easyreflectometry.inequality_constraints`); the posterior is + penalised in the infeasible region. Defaults to what + :attr:`constraints_factory_provider` returns. :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, and ``'logp'``. :raises RuntimeError: If the current minimizer is not a BUMPS instance. @@ -650,23 +815,25 @@ def mcmc_sample( if initializer is not None: sampler_kwargs['init'] = initializer - sampler = Sampler( - self.easy_science_multi_fitter, - x=x, - y=y, - weights=dy, - ) - # Retained so the chain can be continued afterwards via ``self.sampler.extend()``. - self._sampler = sampler - results = sampler.sample( - samples=samples, - burn=burn, - thin=thin, - population=population, - sampler_kwargs=sampler_kwargs or None, - progress_callback=progress_callback, - abort_test=abort_test, - ) + # Resolved once and passed on as the explicit factory, so building it + # (which resolves every constraint's parameter paths) happens once. + factory = self._resolve_constraints_factory(constraints_factory) + with self._constraints(factory): + # The factory is current only while the problem is being built, so + # `_keep_constraints_on_extend` covers a later continuation. + sampler = Sampler(self.easy_science_multi_fitter, x=x, y=y, weights=dy) + self._keep_constraints_on_extend(sampler, factory) + # Retained so the chain can be continued afterwards via ``self.sampler.extend()``. + self._sampler = sampler + results = sampler.sample( + samples=samples, + burn=burn, + thin=thin, + population=population, + sampler_kwargs=sampler_kwargs or None, + progress_callback=progress_callback, + abort_test=abort_test, + ) return { 'draws': results.draws, 'param_names': results.param_names, diff --git a/src/easyreflectometry/inequality_constraints.py b/src/easyreflectometry/inequality_constraints.py new file mode 100644 index 00000000..75158222 --- /dev/null +++ b/src/easyreflectometry/inequality_constraints.py @@ -0,0 +1,466 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Cross-parameter inequality constraints enforced by the fit. + +An :class:`InequalitySpec` is a purely declarative statement such as +``t_head < t_tail`` or ``t1 + t2 <= total``: two expressions over parameter +aliases and a relation. Unlike the equality constraints in +:mod:`easyreflectometry.constraints` it is *not* a parameter dependency — +no parameter is removed from the fit. Instead the specs are translated, +at the start of every fit, into penalty terms attached to the BUMPS +``FitProblem`` (``FitProblem(constraints=[...])``): while a constraint is +violated BUMPS skips the model, adds a large penalty and a term growing +with the violation, which steers the optimizer back into the feasible +region. Only the BUMPS engine family (``Bumps*`` minimizers and the DREAM +sampler) supports this; ``Fitter.fit`` rejects inequalities for LMFit and +DFO-LS. + +Specs reference parameters by *structural path* (``models/0/sample/1/layers/0/thickness``, +see :meth:`easyreflectometry.Project.parameter_path`) rather than by +object or unique name, so they survive project save/load and can be +rebuilt against a reloaded object tree. + +Design notes — why the translation happens inside the fit, and reads the +BUMPS parameters: + +* BUMPS evaluates the constraints *before* the model and skips the model + while any fails. The EasyScience parameter values are only written inside + the model call, so at constraint-evaluation time they lag the optimizer's + trial vector — and freeze entirely once a constraint fails. The operands + built here therefore read ``bumps.Parameter.value`` of the live problem, + which BUMPS sets for every trial point. +* Those BUMPS parameters only exist for the *free* EasyScience parameters + and are rebuilt per fit, which is why a *factory* (``constraints_factory``) + is passed down the fitting chain and invoked by easyscience's + ``build_curve_problem``. Fixed parameters are frozen as constants; + dependent (constrained or derived) parameters are expanded recursively + into their independent leaves. +* Each penalty term returns the *linear* violation; BUMPS squares it once, + giving a quadratic penalty. +""" + +from __future__ import annotations + +import keyword +import numbers +import re +import warnings +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import List +from typing import Optional + +import numpy as np +from asteval import Interpreter +from easyscience.variable import DescriptorNumber +from easyscience.variable import Parameter + +__all__ = [ + 'RELATIONS', + 'InequalityEvaluation', + 'InequalitySpec', + 'UnitError', + 'build_constraints_factory', + 'check_units', + 'evaluate_spec', +] + + +class UnitError(ValueError): + """A unit problem in an inequality constraint. + + Raised by :func:`check_units` when a side cannot be evaluated with its + units or the two sides of a spec carry incompatible units. Subclasses + ``ValueError`` so existing ``except ValueError`` callers keep working; + new callers can catch this type instead of matching message substrings. + """ + + +RELATIONS = ('<', '<=', '>', '>=') +_RELATION_ALIASES = {'≤': '<=', '≥': '>=', '=<': '<=', '=>': '>='} + +#: BUMPS prefixes every EasyScience parameter name; must match +#: ``easyscience.fitting.engine_base.PARAMETER_PREFIX``. +_BUMPS_PREFIX = 'p' + +_SAFE_SYMBOLS = { + 'pi': np.pi, + 'e': np.e, + 'sqrt': np.sqrt, + 'exp': np.exp, + 'log': np.log, + 'log10': np.log10, + 'sin': np.sin, + 'cos': np.cos, + 'tan': np.tan, + 'abs': abs, + 'min': min, + 'max': max, +} + +PathResolver = Callable[[str], DescriptorNumber] + + +def _normalize_relation(op: str) -> str: + op = _RELATION_ALIASES.get(op.strip(), op.strip()) + if op not in RELATIONS: + raise ValueError(f"Unsupported relation '{op}'. Use one of {', '.join(RELATIONS)}.") + return op + + +def _new_interpreter() -> Interpreter: + interpreter = Interpreter(minimal=True, use_numpy=False) + for name, value in _SAFE_SYMBOLS.items(): + interpreter.symtable[name] = value + return interpreter + + +def _evaluate(interpreter: Interpreter, expression: str, symbols: Dict[str, Any]) -> Any: + interpreter.symtable.update(symbols) + result = interpreter.eval(expression, raise_errors=True) + return result + + +def _identifiers(expression: str) -> List[str]: + """Identifiers used in `expression` that are not builtin math symbols.""" + names = set(re.findall(r'\b[A-Za-z_][A-Za-z0-9_]*\b', expression)) + return sorted(n for n in names if n not in _SAFE_SYMBOLS and not keyword.iskeyword(n)) + + +@dataclass +class InequalitySpec: + """Declarative cross-parameter inequality, e.g. ``t_head < t_tail``. + + Attributes + ---------- + lhs_expression : str + Expression over the aliases in `lhs_paths`, e.g. ``'a + b'``. + op : str + One of ``'<'``, ``'<='``, ``'>'``, ``'>='``. + rhs_expression : str + Expression over the aliases in `rhs_paths`; may also be a plain + number (dimensionless or understood in the unit of the left side). + lhs_paths, rhs_paths : dict[str, str] + Alias to structural parameter path (``models/0/...``); no live objects + are held so the spec is trivially serializable. + name : str + Optional user label. + enabled : bool + Disabled specs are kept but not applied to fits. + """ + + lhs_expression: str + op: str + rhs_expression: str + lhs_paths: Dict[str, str] = field(default_factory=dict) + rhs_paths: Dict[str, str] = field(default_factory=dict) + name: str = '' + enabled: bool = True + + def __post_init__(self) -> None: + self.op = _normalize_relation(self.op) + self.lhs_expression = str(self.lhs_expression).strip() + self.rhs_expression = str(self.rhs_expression).strip() + self.lhs_paths = dict(self.lhs_paths) + self.rhs_paths = dict(self.rhs_paths) + self.validate_syntax() + + # ----- validation ----- + + def validate_syntax(self) -> None: + """Check that both sides parse and every identifier has an alias mapping.""" + for side, expression, paths in ( + ('left', self.lhs_expression, self.lhs_paths), + ('right', self.rhs_expression, self.rhs_paths), + ): + if not expression: + raise ValueError(f'The {side}-hand side of an inequality cannot be empty.') + for alias in paths: + if not alias.isidentifier() or keyword.iskeyword(alias): + raise ValueError(f"Alias '{alias}' is not a valid identifier.") + missing = [n for n in _identifiers(expression) if n not in paths] + if missing: + raise ValueError(f"The {side}-hand side '{expression}' references unmapped names: {', '.join(missing)}.") + interpreter = _new_interpreter() + try: + _evaluate(interpreter, expression, {alias: 1.0 for alias in paths}) + except Exception as error: # asteval raises its own hierarchy + raise SyntaxError(f"Cannot evaluate the {side}-hand side '{expression}': {error}") from None + # `paths` merges both sides, so one alias cannot mean two different + # parameters — the right side would silently win. + conflicting = sorted( + alias for alias, path in self.lhs_paths.items() if alias in self.rhs_paths and self.rhs_paths[alias] != path + ) + if conflicting: + raise ValueError( + f'Alias(es) {", ".join(repr(a) for a in conflicting)} map to different parameters on the ' + 'two sides of the inequality. Use distinct alias names per side.' + ) + + @property + def paths(self) -> Dict[str, str]: + """All alias → path pairs of both sides.""" + merged = dict(self.lhs_paths) + merged.update(self.rhs_paths) + return merged + + # ----- serialization ----- + + def to_dict(self) -> dict: + return { + 'lhs_expression': self.lhs_expression, + 'op': self.op, + 'rhs_expression': self.rhs_expression, + 'lhs_paths': dict(self.lhs_paths), + 'rhs_paths': dict(self.rhs_paths), + 'name': self.name, + 'enabled': bool(self.enabled), + } + + @classmethod + def from_dict(cls, d: dict) -> 'InequalitySpec': + return cls( + lhs_expression=d['lhs_expression'], + op=d['op'], + rhs_expression=d['rhs_expression'], + lhs_paths=d.get('lhs_paths', {}), + rhs_paths=d.get('rhs_paths', {}), + name=d.get('name', ''), + enabled=d.get('enabled', True), + ) + + def __str__(self) -> str: + return f'{self.lhs_expression} {self.op} {self.rhs_expression}' + + +@dataclass +class InequalityEvaluation: + """Result of evaluating a spec against the current parameter values.""" + + lhs: float + rhs: float + satisfied: bool + violation: float + + @property + def feasible(self) -> bool: + return self.satisfied + + +# ----- value sources reading the live BUMPS trial vector ----- + + +class _Constant: + __slots__ = ('value',) + + def __init__(self, value: float) -> None: + self.value = float(value) + + def __call__(self) -> float: + return self.value + + +class _BumpsValue: + """Reads a BUMPS parameter — i.e. the optimizer's current trial value.""" + + __slots__ = ('parameter',) + + def __init__(self, bumps_parameter: Any) -> None: + self.parameter = bumps_parameter + + def __call__(self) -> float: + return float(self.parameter.value) + + +class _Expression: + """Evaluates an expression whose symbols are value sources.""" + + __slots__ = ('expression', 'sources', '_interpreter') + + def __init__(self, expression: str, sources: Dict[str, Callable[[], float]]) -> None: + self.expression = expression + self.sources = sources + self._interpreter = _new_interpreter() + + def __call__(self) -> float: + return float(_evaluate(self._interpreter, self.expression, {k: src() for k, src in self.sources.items()})) + + def __float__(self) -> float: + return self() + + +class _Violation: + """The BUMPS constraint object: ``float()`` is ``0`` when satisfied, else the violation.""" + + __slots__ = ('lhs', 'op', 'rhs', 'label') + + def __init__(self, lhs: _Expression, op: str, rhs: _Expression, label: str) -> None: + self.lhs, self.op, self.rhs, self.label = lhs, op, rhs, label + + def __float__(self) -> float: + return _violation(self.lhs(), self.op, self.rhs()) + + def __str__(self) -> str: + return self.label + + def __bool__(self) -> bool: # mirror bumps.Constraint: never silently truthy + raise TypeError('Inequality constraints cannot be used as booleans') + + +def _violation(lhs: float, op: str, rhs: float) -> float: + """Linear violation of ``lhs op rhs`` (``0`` when satisfied). + + Strict and non-strict relations are treated alike: a penalty of exactly + zero at the boundary is the only continuous choice. + """ + if op in ('<', '<='): + diff = lhs - rhs + else: + diff = rhs - lhs + return diff if diff > 0.0 else 0.0 + + +def _source_for(parameter: DescriptorNumber, bumps_pars: Dict[str, Any], trail: tuple) -> Callable[[], float]: + """Translate an EasyScience parameter into a source reading the BUMPS trial vector. + + * free parameter → the BUMPS parameter of the problem; + * fixed parameter (or plain descriptor) → constant; + * dependent parameter → its dependency expression, expanded recursively. + """ + if id(parameter) in trail: + raise ValueError(f"Circular dependency while expanding parameter '{parameter.name}'.") + if isinstance(parameter, Parameter) and not parameter.independent: + expression = getattr(parameter, '_clean_dependency_string', None) + dependency_map = getattr(parameter, '_dependency_map', None) or {} + if expression is None: + # A dependent parameter always carries its dependency expression in + # EasyScience; this is a defensive fallback for a foreign Parameter + # subclass. Freezing silently would hide a bug, so say so. + warnings.warn( + f"Dependent parameter '{parameter.name}' has no dependency expression; " + 'its current value is frozen as a constant in the inequality constraint.', + stacklevel=2, + ) + return _Constant(parameter.value) + sources = {alias: _source_for(dep, bumps_pars, trail + (id(parameter),)) for alias, dep in dependency_map.items()} + return _Expression(expression, sources) + key = _BUMPS_PREFIX + parameter.unique_name + if key in bumps_pars: + return _BumpsValue(bumps_pars[key]) + # Fixed, or not part of this fit (e.g. belongs to a model not being fitted). + return _Constant(parameter.value) + + +def _resolve_all(spec: InequalitySpec, resolve: PathResolver) -> Dict[str, DescriptorNumber]: + resolved = {} + for alias, path in spec.paths.items(): + parameter = resolve(path) + if not isinstance(parameter, DescriptorNumber): + raise ValueError(f"Path '{path}' (alias '{alias}') does not point to a parameter.") + resolved[alias] = parameter + return resolved + + +def build_constraints_factory(specs: Iterable[InequalitySpec], resolve: PathResolver) -> Optional[Callable]: + """Build the ``constraints_factory`` hook for the given specs. + + The returned callable is what ``easyscience``'s BUMPS engine invokes + with the ``{prefixed unique name: bumps.Parameter}`` mapping of a + freshly built problem; it returns one penalty object per enabled spec. + Paths are resolved and dependent parameters expanded at that moment, so + the factory always reflects the model as it is when the fit starts. + + Parameters + ---------- + specs : Iterable[InequalitySpec] + Specs to apply; disabled ones are skipped. + resolve : Callable[[str], DescriptorNumber] + Structural-path resolver, typically ``project.resolve_parameter_path``. + + Returns + ------- + Callable | None + The factory, or ``None`` when no spec is enabled (so callers can pass + it straight through as ``constraints_factory=...``). + """ + active = [spec for spec in specs if spec.enabled] + if not active: + return None + + def factory(bumps_pars: Dict[str, Any]) -> list: + constraints = [] + for spec in active: + parameters = _resolve_all(spec, resolve) + lhs = _Expression( + spec.lhs_expression, + {alias: _source_for(parameters[alias], bumps_pars, ()) for alias in spec.lhs_paths}, + ) + rhs = _Expression( + spec.rhs_expression, + {alias: _source_for(parameters[alias], bumps_pars, ()) for alias in spec.rhs_paths}, + ) + constraints.append(_Violation(lhs, spec.op, rhs, spec.name or str(spec))) + return constraints + + return factory + + +def evaluate_spec(spec: InequalitySpec, resolve: PathResolver) -> InequalityEvaluation: + """Evaluate a spec against the *current* parameter values. + + Used for the start-point feasibility check before a fit is launched and + for displaying the constraint state; dependent parameters contribute + their current (already propagated) value. + """ + parameters = _resolve_all(spec, resolve) + interpreter = _new_interpreter() + lhs = float(_evaluate(interpreter, spec.lhs_expression, {a: float(parameters[a].value) for a in spec.lhs_paths})) + rhs = float(_evaluate(interpreter, spec.rhs_expression, {a: float(parameters[a].value) for a in spec.rhs_paths})) + violation = _violation(lhs, spec.op, rhs) + return InequalityEvaluation(lhs=lhs, rhs=rhs, satisfied=violation == 0.0, violation=violation) + + +def check_units(spec: InequalitySpec, resolve: PathResolver) -> None: + """Raise :class:`UnitError` when the two sides of `spec` have incompatible units. + + Each side is evaluated with the unit-carrying ``DescriptorNumber`` + objects themselves (the same arithmetic the equality constraints use), + so ``t1 + sld`` is rejected by EasyScience and ``t_head < t_tail`` + passes. A plain numeric side is accepted against any unit: it is read in + the unit of the other side. The same applies to a side mixing literals + with parameters (``90 - b``): it is checked numerically and its literals + are read in the unit of the other side. + """ + parameters = _resolve_all(spec, resolve) + units = [] + for expression, paths in ((spec.lhs_expression, spec.lhs_paths), (spec.rhs_expression, spec.rhs_paths)): + interpreter = _new_interpreter() + try: + result = _evaluate(interpreter, expression, {alias: parameters[alias] for alias in paths}) + except Exception as unit_error: + # Mixed literal/parameter arithmetic such as ``90 - b`` cannot be + # evaluated with unit-carrying objects (a bare number has no unit). + # Fall back to a numeric evaluation and read the literals in the + # unit of the other side; purely wrong mixes still fail here. + try: + numeric = _evaluate(_new_interpreter(), expression, {alias: float(parameters[alias].value) for alias in paths}) + except Exception: + raise UnitError(f"Cannot evaluate '{expression}' with units: {unit_error}") from None + if not isinstance(numeric, numbers.Number): + raise ValueError(f"'{expression}' does not evaluate to a number.") from None + units.append(None) + continue + if isinstance(result, DescriptorNumber): + units.append(str(result.unit)) + elif isinstance(result, numbers.Number): + units.append(None) + else: + raise ValueError(f"'{expression}' does not evaluate to a number.") + lhs_unit, rhs_unit = units + if lhs_unit is not None and rhs_unit is not None and lhs_unit != rhs_unit: + raise UnitError(f"Incompatible units in '{spec}': left side is in '{lhs_unit}', right side in '{rhs_unit}'.") diff --git a/src/easyreflectometry/model/model.py b/src/easyreflectometry/model/model.py index 993dc3e8..4cb47142 100644 --- a/src/easyreflectometry/model/model.py +++ b/src/easyreflectometry/model/model.py @@ -115,6 +115,20 @@ def __init__( self._scale = scale self._background = background + # Derived, read-only "calculation" parameter (see `total_thickness`). + # Rebuilt by every constructor call — including `from_dict` — and + # deliberately *not* serialized: it is a function of the layers. + self._total_thickness = Parameter( + name='total_thickness', + value=0.0, + unit='angstrom', + fixed=True, + description='Total thickness of the film: the sum of all layer thicknesses ' + 'between the superphase and the subphase (derived, read-only).', + ) + self._total_thickness_sources: list[Parameter] = [] + self.refresh_derived() + # Set interface last — propagates to children via BaseCore.generate_bindings # and then sets the resolution function on the calculator (see setter). if interface is not None: @@ -146,6 +160,56 @@ def background(self) -> Parameter: def background(self, value: float) -> None: self._background.value = value + # ----- derived parameters ----- + + def _film_layers(self) -> list: + """Layers between the superphase and the subphase (first and last layer of the sample).""" + layers = [layer for assembly in self.sample for layer in assembly.layers] + return layers[1:-1] if len(layers) >= 3 else [] + + def refresh_derived(self) -> None: + """Re-derive the model's computed parameters from the current layer structure. + + Cheap (a few string operations) and idempotent: the dependency is + only rebuilt when the set of contributing layers changed. Called on + every access to :attr:`total_thickness`, so layers added or removed + through *any* path (`Model.add_assemblies`, `assembly.layers.append`, + ...) are picked up without explicit notification. + """ + sources = [layer.thickness for layer in self._film_layers()] + current = self._total_thickness_sources + if len(sources) == len(current) and all(a is b for a, b in zip(sources, current)): + return + self._total_thickness_sources = sources + total = self._total_thickness + if not sources: + if not total.independent: + total.make_independent() + # `make_dependent_on` clears `fixed`, and `make_independent` does not + # put it back; without this the parameter would resurface as a free + # fit parameter once the film is emptied. + total.fixed = True + total.value = 0.0 + return + dependency_map = {f't{index}': source for index, source in enumerate(sources)} + total.make_dependent_on( + dependency_expression=' + '.join(dependency_map.keys()), + dependency_map=dependency_map, + ) + + @property + def total_thickness(self) -> Parameter: + """Read-only parameter: the summed thickness of the film layers. + + The superphase (first layer of the sample) and the subphase (last + layer) are semi-infinite and excluded. The parameter is *dependent*: + it tracks the layer thicknesses, never enters a fit and cannot be + set; it can be referenced from constraint expressions and + inequality constraints like any other parameter. + """ + self.refresh_derived() + return self._total_thickness + # ----- assembly management ----- def add_assemblies(self, *assemblies: list[BaseAssembly]) -> None: diff --git a/src/easyreflectometry/model/model_collection.py b/src/easyreflectometry/model/model_collection.py index 817fc5f0..cb1acce1 100644 --- a/src/easyreflectometry/model/model_collection.py +++ b/src/easyreflectometry/model/model_collection.py @@ -63,13 +63,26 @@ def next_color_index(self) -> Optional[int]: """Index of the next colour to assign — kept around so it round-trips.""" return self._next_color_index + def next_color(self) -> str: + """Colour the next appended model should get. + + Appending advances the cycle, so callers that build a ``Model`` + themselves can keep the per-model colours distinct:: + + model = Model(sample=sample, color=collection.next_color()) + collection.add_model(model) + """ + return self._current_color() + def add_model(self, model: Optional[Model] = None): """Add a model to the collection. Parameters ---------- model : Optional[Model], optional - Model to add. By default, None. + Model to add. By default, None (a new model is created with + the collection's next colour; a supplied model keeps its own + colour — use :meth:`next_color` when building one). """ if model is None: model = Model(name='Model', interface=self.interface, color=self._current_color()) @@ -86,6 +99,9 @@ def duplicate_model(self, index: int): to_be_duplicated = self[index] duplicate = Model.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) duplicate.name = duplicate.name + ' duplicate' + # A duplicate sharing its source's colour would be indistinguishable + # in the plots; give it the collection's next colour instead. + duplicate.color = self._current_color() self.append(duplicate) @classmethod diff --git a/src/easyreflectometry/orso_utils.py b/src/easyreflectometry/orso_utils.py index aa320933..15cb717e 100644 --- a/src/easyreflectometry/orso_utils.py +++ b/src/easyreflectometry/orso_utils.py @@ -87,9 +87,23 @@ def load_orso_model(orso_data) -> Sample: stacklevel=2, ) return None - stack_str = sample_model.stack - layers_dict = sample_model.layers if hasattr(sample_model, 'layers') else None - orso_sample = model_language.SampleModel(stack=stack_str, layers=layers_dict) + if isinstance(sample_model, model_language.SampleModel): + # Use the file's model as parsed: rebuilding it from `stack` and + # `layers` alone (as done previously) silently dropped the + # `materials` / `sub_stacks` / `composits` definitions, so named + # materials could not be resolved and their SLDs read as 0. + orso_sample = sample_model + else: + stack_str = sample_model.stack + layers_dict = sample_model.layers if hasattr(sample_model, 'layers') else None + orso_sample = model_language.SampleModel( + stack=stack_str, + layers=layers_dict, + materials=getattr(sample_model, 'materials', None), + sub_stacks=getattr(sample_model, 'sub_stacks', None), + composits=getattr(sample_model, 'composits', None), + globals=getattr(sample_model, 'globals', None), + ) # Try to resolve layers using different methods try: @@ -147,8 +161,11 @@ def load_orso_model(orso_data) -> Sample: def _convert_orso_layer_to_erl(layer): r"""Helper function to convert an ORSO layer to an EasyReflectometry laye.""" material = layer.material - # Prefer original_name for material name, fall back to formula if available + # Prefer original_name for the material name, fall back to the formula; a + # material defined only by its SLD has neither, so never leave it None. m_name = layer.original_name if layer.original_name is not None else material.formula + if m_name is None: + m_name = 'material' # Get SLD values (use formula for density calculation if available) formula_for_calc = material.formula if material.formula is not None else m_name @@ -187,9 +204,10 @@ def _get_sld_values(material, material_name): if isinstance(material.sld, ComplexValue): raw_sld = material.sld.real m_sld = raw_sld * 1e6 - m_isld = material.sld.imag * 1e6 + m_isld = (material.sld.imag or 0.0) * 1e6 else: - raw_sld = material.sld + # A plain number, or an orsopy ``Value`` (unwrap its magnitude). + raw_sld = getattr(material.sld, 'magnitude', material.sld) m_sld = raw_sld * 1e6 m_isld = 0.0 if raw_sld != 0.0 and abs(raw_sld) > 1e-2: diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 1c7fd29e..e5250a91 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -5,6 +5,8 @@ import json import logging import os +import warnings +import weakref from pathlib import Path from typing import Dict from typing import List @@ -14,6 +16,7 @@ import numpy as np from easyscience import global_object from easyscience.fitting import AvailableMinimizers +from easyscience.variable import DescriptorNumber as DescriptorNumberType from easyscience.variable import Parameter from easyscience.variable.parameter_dependency_resolver import resolve_all_parameter_dependencies from scipp import DataGroup @@ -21,6 +24,9 @@ from easyreflectometry.calculators import CalculatorFactory from easyreflectometry.calculators import PolarizationChannel from easyreflectometry.calculators.calculator_base import CalculatorBase +from easyreflectometry.constraints import SUM_PARTNER_MAX_BACKUP +from easyreflectometry.constraints import USER_CONSTRAINT_FLAG +from easyreflectometry.constraints import constrain from easyreflectometry.data import DataSet1D from easyreflectometry.data import PolarizedDataSet from easyreflectometry.data import detect_polarization_channel @@ -28,6 +34,11 @@ from easyreflectometry.data.measurement import extract_orso_title from easyreflectometry.data.measurement import load_data_from_orso_file from easyreflectometry.fitting import MultiFitter +from easyreflectometry.inequality_constraints import InequalityEvaluation +from easyreflectometry.inequality_constraints import InequalitySpec +from easyreflectometry.inequality_constraints import build_constraints_factory +from easyreflectometry.inequality_constraints import check_units +from easyreflectometry.inequality_constraints import evaluate_spec from easyreflectometry.limits import apply_default_limits from easyreflectometry.model import Model from easyreflectometry.model import ModelCollection @@ -75,6 +86,22 @@ DEFAULT_MINIMIZER = AvailableMinimizers.LMFit_leastsq +#: Properties not descended into when *generating* structural parameter +#: paths: non-structural objects and convenience aliases of ``layers[i]`` +#: (so a layer parameter is always addressed as ``.../layers//...``). +#: ``resolve_parameter_path`` still accepts them. +_PATH_SKIPPED_PROPERTIES = frozenset({'interface', 'parent', 'front_layer', 'back_layer', 'head_layer', 'tail_layer'}) + + +def _weak_constraints_provider(project: 'Project'): + project_ref = weakref.ref(project) + + def provider(): + target = project_ref() + return None if target is None else target.build_constraints_factory() + + return provider + class Project: def __init__(self): @@ -98,6 +125,7 @@ def __init__(self): self._current_layer_index = 0 self._fitter_model_index = None self._current_experiment_index = 0 + self._inequality_constraints: List[InequalitySpec] = [] # Project flags self._created = False @@ -329,8 +357,304 @@ def fitter(self) -> MultiFitter: self._fitter = MultiFitter(self._models[self._current_model_index]) self._fitter.easy_science_multi_fitter.switch_minimizer(self._minimizer_selection) self._fitter_model_index = self._current_model_index + # Fits run through this fitter pick up the project's inequality + # constraints automatically (resolved at fit time). A weak + # reference avoids a project -> fitter -> project cycle that + # would keep a discarded project (and its unique names) alive. + self._fitter.constraints_factory_provider = _weak_constraints_provider(self) return self._fitter + # ----- structural parameter paths ----- + + @staticmethod + def _child_candidates(obj) -> list: + """``(token, child)`` pairs to descend into from `obj`.""" + from collections.abc import Sequence + + from easyreflectometry.sample.base_core import BaseCore + from easyreflectometry.sample.collections.base_collection import BaseCollection + + if isinstance(obj, (BaseCollection, list, tuple)) or (isinstance(obj, Sequence) and not isinstance(obj, str)): + return [(str(index), item) for index, item in enumerate(obj)] + if isinstance(obj, BaseCore) or hasattr(obj, 'get_all_parameters'): + candidates = [] + for attr_name in dir(type(obj)): + if attr_name.startswith('_') or attr_name in _PATH_SKIPPED_PROPERTIES: + continue + class_attr = getattr(type(obj), attr_name, None) + if not isinstance(class_attr, property): + continue + try: + value = getattr(obj, attr_name) + except Exception as exception: + logger.debug("Skipping property '%s' on %r: %s", attr_name, obj, exception) + continue + if isinstance(value, (DescriptorNumberType, BaseCore, BaseCollection, list, tuple)): + candidates.append((attr_name, value)) + return candidates + return [] + + def _walk_parameters(self): + """Yield ``(structural path, parameter)`` for every parameter under the models. + + Each object is descended into once, so parent back-references cannot + recurse forever. + """ + visited: set[int] = set() + + def _walk(obj, tokens: List[str]): + if isinstance(obj, DescriptorNumberType): + yield '/'.join(tokens), obj + return + if id(obj) in visited: + return + visited.add(id(obj)) + for token, child in self._child_candidates(obj): + yield from _walk(child, tokens + [token]) + + yield from _walk(self._models, ['models']) + + def parameter_path(self, parameter) -> Optional[str]: + """Structural path of `parameter` within this project, e.g. + ``models/0/sample/1/layers/0/thickness``. + + Paths are stable across save/load (unlike unique names, which are + regenerated) and are the way inequality constraints reference + parameters. Returns ``None`` when the parameter is not reachable + from the project's models. + """ + return next((path for path, candidate in self._walk_parameters() if candidate is parameter), None) + + def resolve_parameter_path(self, path: str): + """Return the parameter at a structural `path` (see :meth:`parameter_path`).""" + tokens = [t for t in str(path).split('/') if t != ''] + if not tokens or tokens[0] != 'models': + raise KeyError(f"Parameter path must start with 'models': '{path}'.") + obj = self._models + for token in tokens[1:]: + if token.lstrip('-').isdigit(): + try: + obj = obj[int(token)] + except (IndexError, KeyError, TypeError): + raise KeyError(f"Parameter path '{path}': index {token} is out of range.") from None + else: + if token.startswith('_') or not hasattr(obj, token): + raise KeyError(f"Parameter path '{path}': unknown attribute '{token}'.") + obj = getattr(obj, token) + if not isinstance(obj, DescriptorNumberType): + raise KeyError(f"Parameter path '{path}' does not point to a parameter.") + return obj + + # ----- inequality constraints ----- + + @property + def inequality_constraints(self) -> List[InequalitySpec]: + """The project's inequality constraints (a copy of the list).""" + return list(self._inequality_constraints) + + def add_inequality_constraint(self, spec: InequalitySpec, validate: bool = True) -> InequalitySpec: + """Register an inequality constraint; returns it. + + With ``validate`` the paths are resolved and the units of both sides + compared, raising ``KeyError``/``ValueError`` on problems. + """ + if not isinstance(spec, InequalitySpec): + raise TypeError('spec must be an InequalitySpec') + if validate: + check_units(spec, self.resolve_parameter_path) + self._inequality_constraints.append(spec) + return spec + + def remove_inequality_constraint(self, which: Union[int, str, InequalitySpec]) -> None: + """Remove a constraint by index, by name or by identity. + + A name removes **every** spec carrying that name; use the index or + the spec object to remove a single one when names are shared. + """ + if isinstance(which, InequalitySpec): + self._inequality_constraints = [s for s in self._inequality_constraints if s is not which] + return + if isinstance(which, str): + matches = [s for s in self._inequality_constraints if s.name == which] + if not matches: + raise KeyError(f"No inequality constraint named '{which}'.") + for spec in matches: + self._inequality_constraints.remove(spec) + return + del self._inequality_constraints[int(which)] + + def clear_inequality_constraints(self) -> None: + self._inequality_constraints = [] + + def evaluate_inequality_constraints(self) -> List[InequalityEvaluation]: + """Evaluate every constraint (enabled or not) at the current values.""" + return [evaluate_spec(spec, self.resolve_parameter_path) for spec in self._inequality_constraints] + + def violated_inequality_constraints(self) -> List[InequalitySpec]: + """Enabled constraints that the *current* parameter values violate. + + A fit started from an infeasible point begins on the BUMPS penalty + plateau; callers should refuse or warn before launching. + """ + violated = [] + for spec in self._inequality_constraints: + if spec.enabled and not evaluate_spec(spec, self.resolve_parameter_path).satisfied: + violated.append(spec) + return violated + + def build_constraints_factory(self): + """``constraints_factory`` hook for the enabled inequality constraints, or ``None``.""" + return build_constraints_factory(self._inequality_constraints, self.resolve_parameter_path) + + # ----- equality constraints (parameter dependencies) ----- + + @staticmethod + def _is_user_constrained(parameter) -> bool: + """Whether `parameter` carries a constraint this project should persist. + + Both halves are needed: a constraint removed with the raw + ``make_independent()`` leaves the marker behind, and re-applying it on + load would resurrect what the user removed. + """ + return getattr(parameter, USER_CONSTRAINT_FLAG, False) and not parameter.independent + + def _constraint_candidates(self) -> List[Parameter]: + """Every parameter the project owns, model-reachable or not. + + Deliberately wider than :attr:`parameters`: a constraint on a material + that no model uses cannot be addressed by a structural path, and has to + be seen here so that :meth:`_constraint_record` can refuse it out loud + rather than let it disappear at save time. + """ + candidates = list(self.parameters) + seen = {id(parameter) for parameter in candidates} + if self._materials is not None: + for parameter in self._materials.get_all_parameters(): + if id(parameter) not in seen: + seen.add(id(parameter)) + candidates.append(parameter) + return candidates + + def _user_constraints(self) -> List[dict]: + """Records describing the constraints created via :mod:`easyreflectometry.constraints`. + + Parameters are addressed by structural path rather than by EasyScience + serializer id: an id is minted lazily when a parameter first gains an + observer and deleted again when it loses its last one, so it is not a + durable handle. + + A parameter is recorded only when it is both marked *and* still + dependent. A constraint removed with the raw ``make_independent()`` + leaves the marker behind, and re-applying that on load would resurrect + something the user removed. Internal constraints carry no marker at all + — their owning class rebuilds them in its own ``from_dict``. + + Raises + ------ + ValueError + If a constrained parameter, or a live parameter it depends on, is + not reachable from the models, so no path can address it. + """ + # Which parameters are constrained is decided from `_constraint_candidates`, + # which enumerates them without walking properties. The structural walk has + # to walk properties and leaves reference cycles behind (delaying collection + # of the project and its unique names), so it runs only when there is + # something to record, and only to supply the paths. Driving both from + # one list keeps a constraint from being dropped because the two + # enumerations disagree. + constrained = [parameter for parameter in self._constraint_candidates() if self._is_user_constrained(parameter)] + if not constrained: + return [] + paths = {id(parameter): path for path, parameter in self._walk_parameters()} + return [self._constraint_record(parameter, paths) for parameter in constrained] + + @staticmethod + def _constraint_record(parameter, paths: dict) -> dict: + """One save record for `parameter`, addressing everything through `paths`.""" + + def _addressable(target, described_as: str) -> str: + path = paths.get(id(target)) + if path is None: + raise ValueError( + f"Cannot save the constraint on '{parameter.name}': {described_as} is not " + "reachable from the project's models. Only parameters that belong to a " + 'model can take part in a saved constraint.' + ) + return path + + dependencies = {} + for alias, dependency in parameter._dependency_map.items(): + if isinstance(dependency, Parameter): + # Embedding a live parameter by value would silently turn a + # dependency into a frozen constant on load. + dependencies[alias] = {'path': _addressable(dependency, f"its dependency '{dependency.name}'")} + else: + # An object-less constant built for the expression (the explicit + # total of `constrain_to_sum`); nothing else serializes it, so it + # is embedded here. + dependencies[alias] = { + 'name': dependency.name, + 'value': float(dependency.value), + 'unit': str(dependency.unit), + } + return { + 'target': _addressable(parameter, 'the parameter itself'), + 'expression': parameter._clean_dependency_string, + 'dependencies': dependencies, + } + + def _sum_partner_max_backups(self) -> Dict[str, float]: + """``{structural path: original max}`` for every parameter whose maximum + :func:`easyreflectometry.constraints.clamp_sum_partners` narrowed. + + The backups live as an attribute on the parameters, which no other + serialization records; without this, removing a sum constraint after a + save/load could never widen the bounds back. + """ + flagged = [parameter for parameter in self.parameters if hasattr(parameter, SUM_PARTNER_MAX_BACKUP)] + if not flagged: + return {} + paths = {id(parameter): path for path, parameter in self._walk_parameters()} + backups: Dict[str, float] = {} + for parameter in flagged: + path = paths.get(id(parameter)) + if path is not None: + backups[path] = float(getattr(parameter, SUM_PARTNER_MAX_BACKUP)) + return backups + + def _restore_sum_partner_max_backups(self, backups: Dict[str, float]) -> None: + """Re-attach the backups written by :meth:`_sum_partner_max_backups`.""" + for path, previous in (backups or {}).items(): + try: + parameter = self.resolve_parameter_path(path) + except KeyError: + logger.warning('Cannot restore the bound backup for %r: the parameter no longer exists.', path) + continue + setattr(parameter, SUM_PARTNER_MAX_BACKUP, float(previous)) + + def _restore_user_constraints(self, records: List[dict]) -> None: + """Re-apply the constraint records written by :meth:`_user_constraints`. + + Records are applied in the order they were written. A chain + (``a`` follows ``b`` follows ``c``) resolves whichever order it is + restored in, because re-constraining a parameter propagates the new + value to anything already following it. + """ + for record in records: + dependencies = {} + for alias, reference in record['dependencies'].items(): + if 'path' not in reference: + dependencies[alias] = DescriptorNumberType(**reference) + continue + try: + dependencies[alias] = self.resolve_parameter_path(reference['path']) + except KeyError as error: + raise KeyError( + f'Cannot restore the constraint on {record["target"]!r}: its dependency ' + f'{reference["path"]!r} does not exist in this project.' + ) from error + constrain(self.resolve_parameter_path(record['target']), record['expression'], **dependencies) + @property def calculator(self) -> str: """Calculator function.""" @@ -508,7 +832,10 @@ def add_sample_from_orso(self, sample: Sample) -> None: """ if sample is None: raise ValueError('The ORSO file does not contain a valid sample model definition.') - model = Model(sample=sample) + # Take the collection's next colour: a supplied model keeps its own + # colour, and the Model default would give every loaded sample the + # same first palette colour. + model = Model(sample=sample, color=self._models.next_color()) self.models.add_model(model) # Set interface after adding to collection model.interface = self._calculator @@ -1372,6 +1699,14 @@ def as_dict(self, include_materials_not_in_model=False): project_dict['calculator'] = self._calculator.current_interface_name if self._colors is not None: project_dict['colors'] = self._colors + if self._inequality_constraints: + project_dict['inequality_constraints'] = [spec.to_dict() for spec in self._inequality_constraints] + parameter_constraints = self._user_constraints() + if parameter_constraints: + project_dict['parameter_constraints'] = parameter_constraints + sum_partner_max_backups = self._sum_partner_max_backups() + if sum_partner_max_backups: + project_dict['sum_partner_max_backups'] = sum_partner_max_backups return project_dict def _as_dict_add_materials_not_in_model_dict(self, project_dict: dict): @@ -1468,8 +1803,49 @@ def from_dict(self, project_dict: dict): else: self._experiments = {} - # Resolve any pending parameter dependencies (constraints) after all objects are loaded + # Resolve any pending parameter dependencies parked by the core + # deserializer. Only cores that serialize nested dependencies produce + # them; on the others this is a no-op safety net and `parameter_constraints` + # below carries the user constraints instead. resolve_all_parameter_dependencies(self) + self._restore_user_constraints(project_dict.get('parameter_constraints', [])) + self._restore_sum_partner_max_backups(project_dict.get('sum_partner_max_backups', {})) + self._warn_on_unreadable_dependencies(project_dict.get('models')) + # Inequality constraints are declarative (paths), nothing to resolve yet: + # they are bound to parameters when a fit starts. + self._inequality_constraints = [InequalitySpec.from_dict(raw) for raw in project_dict.get('inequality_constraints', [])] + + @staticmethod + def _warn_on_unreadable_dependencies(models_dict) -> None: + """Warn about embedded dependencies this build cannot restore. + + A core that serializes dependencies inside each nested parameter writes + ``_dependency_string`` there. Cores without that feature drop the field + silently on load, so such a file would lose its equality constraints + with no signal at all. Detect it and say so; the constraints have to be + re-applied by hand. + + The field is written for internal dependencies too (material mixtures, + conformal roughness, ``Model.total_thickness``), and those are rebuilt + by their owning class regardless — so its presence does not prove + anything was actually lost. The wording is hedged accordingly. + """ + + def _contains_dependency(node) -> bool: + if isinstance(node, dict): + return '_dependency_string' in node or any(_contains_dependency(v) for v in node.values()) + if isinstance(node, (list, tuple)): + return any(_contains_dependency(item) for item in node) + return False + + if _contains_dependency(models_dict): + warnings.warn( + 'This project was saved by a build that stores parameter dependencies inside ' + 'each parameter, which this build cannot restore. Internal constraints are ' + 'rebuilt automatically, but any custom equality constraints have been dropped ' + 'and must be re-applied.', + stacklevel=2, + ) def _from_dict_extract_experiments(self, project_dict: dict) -> Dict[int, Union[DataSet1D, PolarizedDataSet]]: """From dict extract experiments.""" diff --git a/src/easyreflectometry/sample/assemblies/base_assembly.py b/src/easyreflectometry/sample/assemblies/base_assembly.py index fcb2f06d..a4ff714e 100644 --- a/src/easyreflectometry/sample/assemblies/base_assembly.py +++ b/src/easyreflectometry/sample/assemblies/base_assembly.py @@ -8,6 +8,15 @@ from ..elements.layers.layer import Layer +def follows_equal(follower, leader) -> bool: + """Whether `follower` is constrained to be exactly equal to `leader`.""" + if getattr(follower, 'independent', True): + return False + dependency_map = getattr(follower, '_dependency_map', None) or {} + expression = (getattr(follower, '_clean_dependency_string', None) or '').strip() + return len(dependency_map) == 1 and expression in dependency_map and dependency_map[expression] is leader + + class BaseAssembly(BaseCore): """Assembly of layers. @@ -97,6 +106,51 @@ def back_layer(self, layer: Layer) -> None: else: self.layers[-1] = layer + # ----- public constraint toggles ----- + + def _layers_follow_front(self, attribute: str) -> bool: + """True when every layer's `attribute` is tied *equal* to the front layer's. + + Only the conformal idiom counts (expression ``'a'`` over ``{'a': leader}``, + as set up by ``_setup_*_constraints``); a parameter that merely *uses* + the front layer's value in some other expression is not conformal. + """ + if len(self.layers) < 2: + return False + leader = getattr(self.front_layer, attribute) + for layer in list(self.layers)[1:]: + follower = getattr(layer, attribute) + if not follows_equal(follower, leader): + return False + return True + + @property + def conformal_thickness(self) -> bool: + """Whether every layer shares the front layer's thickness.""" + return self._layers_follow_front('thickness') + + @conformal_thickness.setter + def conformal_thickness(self, status: bool) -> None: + """Tie (or release) every layer's thickness to the front layer's.""" + if status: + self._setup_thickness_constraints() + elif self._layers_follow_front('thickness'): + for layer in list(self.layers)[1:]: + layer.thickness.make_independent() + + @property + def conformal_roughness(self) -> bool: + """Whether every layer shares the front layer's roughness.""" + return self._layers_follow_front('roughness') + + @conformal_roughness.setter + def conformal_roughness(self, status: bool) -> None: + """Tie (or release) every layer's roughness to the front layer's.""" + if status: + self._setup_roughness_constraints() + elif self._layers_follow_front('roughness'): + self._disable_roughness_constraints() + def _setup_thickness_constraints(self) -> None: """Setup thickness constraint, front layer is the deciding layer.""" independent_param = self.front_layer.thickness diff --git a/src/easyreflectometry/sample/assemblies/multilayer.py b/src/easyreflectometry/sample/assemblies/multilayer.py index db02592d..973882be 100644 --- a/src/easyreflectometry/sample/assemblies/multilayer.py +++ b/src/easyreflectometry/sample/assemblies/multilayer.py @@ -31,6 +31,8 @@ def __init__( interface=None, type: str = 'Multi-layer', populate_if_none: Optional[bool] = True, + conformal_thickness: bool = False, + conformal_roughness: bool = False, ): """Constructor. @@ -48,6 +50,13 @@ def __init__( Calculator interface. By default, None. type : str, optional Type of the constructed instance. By default, 'Multi-layer'. + conformal_thickness : bool, optional + Tie every layer's thickness to the front layer's. Serialization + reads the current graph state through the matching property, so + the ties are rebuilt on ``from_dict``. By default, False. + conformal_roughness : bool, optional + Tie every layer's roughness to the front layer's, likewise + persistent. By default, False. """ if layers is None: if populate_if_none: @@ -69,6 +78,10 @@ def __init__( layers=layers, unique_name=unique_name, ) + if conformal_thickness: + self.conformal_thickness = True + if conformal_roughness: + self.conformal_roughness = True def add_layer(self, *layers: tuple[Layer]) -> None: """Add a layer to the multi layer. diff --git a/src/easyreflectometry/sample/assemblies/repeating_multilayer.py b/src/easyreflectometry/sample/assemblies/repeating_multilayer.py index 79eab4fa..83156cfa 100644 --- a/src/easyreflectometry/sample/assemblies/repeating_multilayer.py +++ b/src/easyreflectometry/sample/assemblies/repeating_multilayer.py @@ -45,6 +45,8 @@ def __init__( unique_name: Optional[str] = None, interface=None, populate_if_none: bool = True, + conformal_thickness: bool = False, + conformal_roughness: bool = False, ): """Constructor. @@ -62,6 +64,10 @@ def __init__( Name for the repeating multi layer. By default, 'EasyRepeatingMultilayer'. interface : Calculator interface. By default, None. + conformal_thickness : bool, optional + Tie every layer's thickness to the front layer's (persisted). By default, False. + conformal_roughness : bool, optional + Tie every layer's roughness to the front layer's (persisted). By default, False. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) @@ -90,6 +96,8 @@ def __init__( interface=None, type='Repeating Multi-layer', populate_if_none=False, + conformal_thickness=conformal_thickness, + conformal_roughness=conformal_roughness, ) self._repetitions = repetitions diff --git a/src/easyreflectometry/summary/summary.py b/src/easyreflectometry/summary/summary.py index 512b434a..320b4239 100644 --- a/src/easyreflectometry/summary/summary.py +++ b/src/easyreflectometry/summary/summary.py @@ -346,10 +346,12 @@ def _refinement_section(self) -> str: model = self._project._models[self._project.current_model_index] parameters = model.get_all_parameters() - num_free_params = sum(1 for parameter in parameters if parameter.free) - num_fixed_params = sum(1 for parameter in parameters if not parameter.free) - num_params = num_free_params + num_fixed_params + # Dependent parameters (user constraints, derived values such as the + # total thickness) are neither free nor fixed: they never enter a fit. + num_free_params = sum(1 for parameter in parameters if parameter.independent and parameter.free) + num_fixed_params = sum(1 for parameter in parameters if parameter.independent and not parameter.free) num_constraints = sum(1 for parameter in parameters if not parameter.independent) + num_params = num_free_params + num_fixed_params + num_constraints goodness_of_fit = self._compute_goodness_of_fit() diff --git a/src/easyreflectometry/utils.py b/src/easyreflectometry/utils.py index 43ac58d8..36bd7ce4 100644 --- a/src/easyreflectometry/utils.py +++ b/src/easyreflectometry/utils.py @@ -80,15 +80,29 @@ def _collect(item): def count_free_parameters(project) -> int: - """Count free parameters.""" - return sum(1 for parameter in project.parameters if parameter.free) + """Count free parameters. + + Dependent parameters (constrained or derived) are neither free nor fixed: + they never enter a fit, whatever their ``free`` flag says. + """ + return sum(1 for parameter in project.parameters if parameter.independent and parameter.free) def count_fixed_parameters(project) -> int: - """Count fixed parameters.""" - return sum(1 for parameter in project.parameters if not parameter.free) + """Count fixed parameters (independent parameters that are not free).""" + return sum(1 for parameter in project.parameters if parameter.independent and not parameter.free) def count_parameter_user_constraints(project) -> int: - """Count parameter user constraints.""" - return sum(1 for parameter in project.parameters if not parameter.independent) + """Count the constraints created via :mod:`easyreflectometry.constraints`. + + Counts only parameters that are both marked as user-constrained and still + dependent — the same test ``Project`` uses to decide what to persist. + Internal dependencies (``Model.total_thickness``, conformal assembly ties, + material mixtures) are not user constraints and are not counted. + """ + from easyreflectometry.constraints import USER_CONSTRAINT_FLAG + + return sum( + 1 for parameter in project.parameters if getattr(parameter, USER_CONSTRAINT_FLAG, False) and not parameter.independent + ) diff --git a/tests/model/test_model_collection.py b/tests/model/test_model_collection.py index dda554b1..59db8f33 100644 --- a/tests/model/test_model_collection.py +++ b/tests/model/test_model_collection.py @@ -94,6 +94,27 @@ def test_add_model_preserves_explicit_color(self): assert collection[-1].color == custom_color assert collection._next_color_index == (expected_index + 1) % len(COLORS) + def test_next_color_matches_what_appending_assigns(self): + collection = ModelCollection(populate_if_none=False) + collection.add_model() + announced = collection.next_color() + + collection.add_model(Model(name='Prebuilt', color=announced)) + + assert collection[-1].color == announced + assert collection[0].color != collection[1].color + # and the cycle moved on + assert collection.next_color() != announced or len(COLORS) <= 2 + + def test_duplicate_model_gets_a_distinct_color(self): + collection = ModelCollection(populate_if_none=False) + collection.add_model() + + collection.duplicate_model(0) + + assert collection[1].name.endswith('duplicate') + assert collection[0].color != collection[1].color + def test_delete_model(self): # When model_1 = Model(name='Model1') diff --git a/tests/sample/assemblies/test_multilayer.py b/tests/sample/assemblies/test_multilayer.py index f2d7bd61..3802b523 100644 --- a/tests/sample/assemblies/test_multilayer.py +++ b/tests/sample/assemblies/test_multilayer.py @@ -168,3 +168,40 @@ def test_dict_round_trip(self): q = Multilayer.from_dict(p_dict) assert sorted(p.as_dict()) == sorted(q.as_dict()) + + def _two_layers(self): + m = Material(6.908, -0.278, 'Boron') + k = Material(0.487, 0.000, 'Potassium') + return [Layer(m, 5.0, 2.0, 'thinBoron'), Layer(k, 50.0, 1.0, 'thickPotassium')] + + def test_conformal_kwargs_apply_the_ties(self): + o = Multilayer(self._two_layers(), conformal_roughness=True, conformal_thickness=True) + assert o.conformal_roughness is True + assert o.conformal_thickness is True + assert o.layers[1].roughness.independent is False + assert o.layers[1].thickness.independent is False + + def test_conformal_flags_survive_dict_round_trip(self): + # The ties are raw parameter dependencies, which nothing serializes; + # the assembly persists the flags and rebuilds the ties on load. + o = Multilayer(self._two_layers(), conformal_roughness=True) + o_dict = o.as_dict() + assert o_dict['conformal_roughness'] is True + assert o_dict['conformal_thickness'] is False + global_object.map._clear() + + q = Multilayer.from_dict(o_dict) + assert q.conformal_roughness is True + assert q.layers[1].roughness.independent is False + q.layers[0].roughness.value = 7.0 + assert q.layers[1].roughness.value == 7.0 + assert q.conformal_thickness is False + assert q.layers[1].thickness.independent is True + + def test_conformal_toggled_after_construction_is_serialized(self): + # Serialization reads the property (graph truth), not the constructor argument. + o = Multilayer(self._two_layers()) + o.conformal_thickness = True + assert o.as_dict()['conformal_thickness'] is True + o.conformal_thickness = False + assert o.as_dict()['conformal_thickness'] is False diff --git a/tests/sample/assemblies/test_repeating_multilayer.py b/tests/sample/assemblies/test_repeating_multilayer.py index 3ee2858b..32cf1903 100644 --- a/tests/sample/assemblies/test_repeating_multilayer.py +++ b/tests/sample/assemblies/test_repeating_multilayer.py @@ -195,3 +195,17 @@ def test_dict_round_trip(self): q = RepeatingMultilayer.from_dict(p_dict) assert sorted(p.as_dict()) == sorted(q.as_dict()) + + def test_conformal_flags_survive_dict_round_trip(self): + m = Material(6.908, -0.278, 'Boron') + k = Material(0.487, 0.000, 'Potassium') + layers = [Layer(m, 5.0, 2.0, 'thinBoron'), Layer(k, 50.0, 1.0, 'thickPotassium')] + p = RepeatingMultilayer(layers, 8, conformal_roughness=True) + p_dict = p.as_dict() + assert p_dict['conformal_roughness'] is True + global_object.map._clear() + + q = RepeatingMultilayer.from_dict(p_dict) + assert q.conformal_roughness is True + assert q.layers[1].roughness.independent is False + assert q.repetitions.value == 8.0 diff --git a/tests/sample/elements/layers/test_layer_magnetism.py b/tests/sample/elements/layers/test_layer_magnetism.py index a2932dec..1688ffc0 100644 --- a/tests/sample/elements/layers/test_layer_magnetism.py +++ b/tests/sample/elements/layers/test_layer_magnetism.py @@ -217,7 +217,8 @@ def test_fit_recovers_rho_m_from_synthetic_data(self): model.interface = self._interface('refl1d') rho_m = model.sample[1].layers[0].magnetism.rho_m rho_m.fixed = False - rho_m.bounds = (0.0, 5.0) + rho_m.min = 0.0 + rho_m.max = 5.0 fitter = MultiFitter(model) result = fitter.fit_single_data_set_1d(data) diff --git a/tests/test_bayesian.py b/tests/test_bayesian.py index 6d0c3148..38154d1b 100644 --- a/tests/test_bayesian.py +++ b/tests/test_bayesian.py @@ -191,10 +191,11 @@ def test_save_and_restore(self): # Use simple objects that support attribute assignment class MockParam: - def __init__(self, unique_name, raw_value, error): + def __init__(self, unique_name, raw_value, error, independent=True): self.unique_name = unique_name self.value = raw_value self.error = error + self.independent = independent param1 = MockParam('param_a', 1.5, 0.1) param2 = MockParam('param_b', 3.0, 0.2) @@ -221,6 +222,37 @@ def get_parameters(self): assert param2.value == 3.0 assert param2.error == 0.2 + def test_dependent_parameters_are_left_alone(self): + """Derived parameters cannot be written back, and do not need to be.""" + from easyreflectometry.analysis.bayesian import _restore_parameter_state + from easyreflectometry.analysis.bayesian import _save_parameter_state + + class MockParam: + def __init__(self, unique_name, independent): + self.unique_name = unique_name + self.value = 1.0 + self.error = 0.1 + self.independent = independent + + def __setattr__(self, name, value): + if name in ('value', 'error') and not self.__dict__.get('independent', True): + raise AttributeError(f'This is a dependent parameter, its {name} cannot be set directly.') + super().__setattr__(name, value) + + free = MockParam('free', True) + derived = MockParam('derived', False) + + class MockModel: + def get_parameters(self): + return [free, derived] + + model = MockModel() + state = _save_parameter_state(model) + + assert 'derived' not in state + _restore_parameter_state(model, state) # would raise if it wrote to `derived` + assert free.value == 1.0 + class TestApplyDraw: def test_apply_draw_updates_parameters(self): diff --git a/tests/test_fitting.py b/tests/test_fitting.py index 9fd02a4b..b1a1cf32 100644 --- a/tests/test_fitting.py +++ b/tests/test_fitting.py @@ -51,25 +51,33 @@ def test_fitting(minimizer): model = Model(sample, 1, 1e-6, resolution_function, 'Film Model') # Thicknesses sio2_layer.thickness.fixed = False - sio2_layer.thickness.bounds = (15, 50) + sio2_layer.thickness.min = 15 + sio2_layer.thickness.max = 50 film_layer.thickness.fixed = False - film_layer.thickness.bounds = (200, 300) + film_layer.thickness.min = 200 + film_layer.thickness.max = 300 # Roughnesses si_layer.roughness.fixed = True - sio2_layer.roughness.bounds = (1, 15) + sio2_layer.roughness.min = 1 + sio2_layer.roughness.max = 15 film_layer.roughness.fixed = False - film_layer.roughness.bounds = (1, 15) + film_layer.roughness.min = 1 + film_layer.roughness.max = 15 superphase.roughness.fixed = True - superphase.roughness.bounds = (1, 15) + superphase.roughness.min = 1 + superphase.roughness.max = 15 # Scattering length density film.sld.fixed = False - film.sld.bounds = (0.1, 3) + film.sld.min = 0.1 + film.sld.max = 3 # Background model.background.fixed = False - model.background.bounds = (1e-7, 1e-5) + model.background.min = 1e-7 + model.background.max = 1e-5 # Scale model.scale.fixed = False - model.scale.bounds = (0.5, 1.5) + model.scale.min = 0.5 + model.scale.max = 1.5 interface = CalculatorFactory() model.interface = interface fitter = MultiFitter(model) @@ -121,15 +129,20 @@ def test_fitting_with_zero_variance(): # Set some parameters as fittable sio2_layer.thickness.fixed = False - sio2_layer.thickness.bounds = (15, 50) + sio2_layer.thickness.min = 15 + sio2_layer.thickness.max = 50 film_layer.thickness.fixed = False - film_layer.thickness.bounds = (200, 300) + film_layer.thickness.min = 200 + film_layer.thickness.max = 300 film.sld.fixed = False - film.sld.bounds = (0.1, 3) + film.sld.min = 0.1 + film.sld.max = 3 model.background.fixed = False - model.background.bounds = (1e-7, 1e-5) + model.background.min = 1e-7 + model.background.max = 1e-5 model.scale.fixed = False - model.scale.bounds = (0.5, 1.5) + model.scale.min = 0.5 + model.scale.max = 1.5 interface = CalculatorFactory() model.interface = interface @@ -204,11 +217,14 @@ def test_fitting_with_manual_zero_variance(): # Set some parameters as fittable sio2_layer.thickness.fixed = False - sio2_layer.thickness.bounds = (15, 50) + sio2_layer.thickness.min = 15 + sio2_layer.thickness.max = 50 film_layer.thickness.fixed = False - film_layer.thickness.bounds = (200, 300) + film_layer.thickness.min = 200 + film_layer.thickness.max = 300 film.sld.fixed = False - film.sld.bounds = (0.1, 3) + film.sld.min = 0.1 + film.sld.max = 3 interface = CalculatorFactory() model.interface = interface @@ -1238,10 +1254,12 @@ def test_fit_weight_convention_matches_analytic_wls(minimizer): assert scale_margin > 10 * scale_tolerance, 'test data cannot discriminate weight conventions' model.scale.fixed = False - model.scale.bounds = (0.5, 3.0) + model.scale.min = 0.5 + model.scale.max = 3.0 model.scale.value = 1.0 model.background.fixed = False - model.background.bounds = (1e-9, 1e-4) + model.background.min = 1e-9 + model.background.max = 1e-4 model.background.value = 1e-6 data = DataSet1D( diff --git a/tests/test_orso_utils.py b/tests/test_orso_utils.py index 4ad2e9ab..b9e3368e 100644 --- a/tests/test_orso_utils.py +++ b/tests/test_orso_utils.py @@ -174,3 +174,78 @@ def test_load_orso_model_returns_none_and_warns_when_no_sample_model(): assert result is None assert len(w) == 1 assert 'does not contain a sample model definition' in str(w[0].message) + + +def test_load_orso_model_resolves_named_materials(tmp_path): + """Named materials from the file's `materials` section must be resolved. + + Regression: `load_orso_model` used to rebuild the ORSO ``SampleModel`` + from `stack` and `layers` alone, dropping the `materials` definitions — + every named material then read as SLD 0. Also covers SLDs stored as + orsopy ``Value`` objects and layers whose material has neither a name + nor a formula (must not produce ``None`` names). + """ + import datetime + + import numpy as np + from orsopy import fileio + from orsopy.fileio import model_language + + sample_model = model_language.SampleModel( + stack='ambient | film | substrate', + layers={ + 'ambient': model_language.Layer( + thickness=fileio.Value(0.0, 'angstrom'), roughness=fileio.Value(0.0, 'angstrom'), material='vac' + ), + 'film': model_language.Layer( + thickness=fileio.Value(35.0, 'angstrom'), roughness=fileio.Value(3.0, 'angstrom'), material='MatA' + ), + 'substrate': model_language.Layer( + thickness=fileio.Value(0.0, 'angstrom'), roughness=fileio.Value(2.0, 'angstrom'), material='Si' + ), + }, + materials={ + 'vac': model_language.Material(sld=fileio.Value(0.0, '1/angstrom^2')), + 'MatA': model_language.Material(sld=fileio.Value(3.0e-6, '1/angstrom^2')), + 'Si': model_language.Material(sld=fileio.Value(2.07e-6, '1/angstrom^2')), + }, + globals=model_language.ModelParameters(length_unit='angstrom'), + ) + header = fileio.Orso( + data_source=fileio.DataSource( + owner=fileio.Person(name='test', affiliation='test'), + experiment=fileio.Experiment( + title='t', instrument='sim', start_date=datetime.datetime(2026, 1, 1), probe='neutron' + ), + sample=fileio.Sample(name='named materials', model=sample_model), + measurement=fileio.Measurement( + instrument_settings=fileio.InstrumentSettings( + incident_angle=fileio.Value(1.0, 'deg'), wavelength=fileio.Value(6.0, 'angstrom') + ), + data_files=[], + ), + ), + reduction=fileio.Reduction(software=fileio.Software(name='test')), + columns=[fileio.Column('Qz', '1/angstrom'), fileio.Column('R')], + data_set=0, + ) + q = np.linspace(0.01, 0.1, 5) + path = tmp_path / 'named_materials.ort' + fileio.save_orso([fileio.OrsoDataset(header, np.array([q, np.ones_like(q)]).T)], str(path)) + + from orsopy.fileio import orso as orso_io + + sample = load_orso_model(orso_io.load_orso(str(path))) + + assert sample is not None + film = sample[1].layers[0] + assert film.name == 'film' + assert film.thickness.value == pytest.approx(35.0) + assert film.roughness.value == pytest.approx(3.0) + assert film.material.sld.value == pytest.approx(3.0) # 1e-6/A^2, resolved from `materials` + assert sample[2].layers[0].material.sld.value == pytest.approx(2.07) + # No layer or material name may come back as None + for assembly in sample: + for layer in assembly.layers: + assert layer.name is not None + assert layer.material.name is not None diff --git a/tests/test_polarized_fitting.py b/tests/test_polarized_fitting.py index d84f536f..fad69925 100644 --- a/tests/test_polarized_fitting.py +++ b/tests/test_polarized_fitting.py @@ -431,7 +431,8 @@ def test_two_channel_nsf_fit_recovers_rho_m(self): model.interface = _refl1d_interface() rho_m = model.sample[1].layers[0].magnetism.rho_m rho_m.fixed = False - rho_m.bounds = (0.0, 5.0) + rho_m.min = 0.0 + rho_m.max = 5.0 data = _polarized_data({'pp': reference['pp'], 'mm': reference['mm']}, model=model) fitter = MultiFitter(model) @@ -450,9 +451,11 @@ def test_four_channel_fit_recovers_rho_m_and_theta_m(self): model.interface = _refl1d_interface() magnetism = model.sample[1].layers[0].magnetism magnetism.rho_m.fixed = False - magnetism.rho_m.bounds = (0.0, 5.0) + magnetism.rho_m.min = 0.0 + magnetism.rho_m.max = 5.0 magnetism.theta_m.fixed = False - magnetism.theta_m.bounds = (0.0, 90.0) + magnetism.theta_m.min = 0.0 + magnetism.theta_m.max = 90.0 data = _polarized_data(dict(reference), model=model) fitter = MultiFitter(model) @@ -473,7 +476,8 @@ def test_shared_structural_parameter_fitted_across_channels(self): thickness = model.sample[1].layers[0].thickness thickness.value = 90.0 thickness.fixed = False - thickness.bounds = (50.0, 150.0) + thickness.min = 50.0 + thickness.max = 150.0 data = _polarized_data({'pp': reference['pp'], 'mm': reference['mm']}, model=model) fitter = MultiFitter(model) @@ -612,7 +616,8 @@ def test_prepared_fitter_recovers_rho_m_when_run(self): model.interface = _refl1d_interface() rho_m = model.sample[1].layers[0].magnetism.rho_m rho_m.fixed = False - rho_m.bounds = (0.0, 5.0) + rho_m.min = 0.0 + rho_m.max = 5.0 data = _polarized_data({'pp': reference['pp'], 'mm': reference['mm']}, model=model) fitter = MultiFitter.for_experiments([data]) diff --git a/tests/test_project.py b/tests/test_project.py index 62dd67d2..bdfbc0ce 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -808,9 +808,10 @@ def test_parameters(self): # Then parameters = project.parameters - # Expect - assert len(parameters) == 14 + # Expect: 14 layer/material/model parameters + the model's derived total thickness + assert len(parameters) == 15 assert isinstance(parameters[0], Parameter) + assert any(parameter is project.models[0].total_thickness for parameter in parameters) def test_parameters_enabled_flags(self): global_object.map._clear() @@ -973,6 +974,10 @@ def test_add_sample_from_orso_multiple_additions(self): assert material_1 in project._materials assert material_2 in project._materials assert project.current_model_index == 1 + # Each loaded sample gets its own colour from the collection's cycle; + # without this, every ORSO-loaded model rendered in the first palette + # colour and their curves were indistinguishable. + assert project._models[0].color != project._models[1].color def test_add_sample_from_orso_with_shared_materials(self): # When diff --git a/tests/test_utils.py b/tests/test_utils.py index 4d9c4828..782807b6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,8 +2,11 @@ # SPDX-License-Identifier: BSD-3-Clause from easyreflectometry import Project +from easyreflectometry.constraints import constrain +from easyreflectometry.constraints import unconstrain from easyreflectometry.utils import count_fixed_parameters from easyreflectometry.utils import count_free_parameters +from easyreflectometry.utils import count_parameter_user_constraints def test_count_free_parameters(): @@ -30,3 +33,20 @@ def test_count_fixed_parameters(): # Expect assert count == 13 + + +def test_count_parameter_user_constraints_counts_only_user_constraints(): + # When + project = Project() + project.default_model() + sample = project.models[0].sample + follower = sample[2].layers[0].thickness + + # Then / Expect: internal dependents (e.g. total_thickness) are not counted + assert count_parameter_user_constraints(project) == 0 + + constrain(follower, '2 * t', t=sample[1].layers[0].thickness) + assert count_parameter_user_constraints(project) == 1 + + unconstrain(follower) + assert count_parameter_user_constraints(project) == 0 diff --git a/tests/unit/test_bumps_constraints_shim.py b/tests/unit/test_bumps_constraints_shim.py new file mode 100644 index 00000000..2256266a --- /dev/null +++ b/tests/unit/test_bumps_constraints_shim.py @@ -0,0 +1,296 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +""" +Tests for the BUMPS inequality-constraints enforcement +""" + +import numpy as np +import pytest +from easyscience import global_object +from easyscience.fitting import AvailableMinimizers +from easyscience.fitting.minimizers import minimizer_bumps +from easyscience.fitting.samplers import sampler_dream + +from easyreflectometry import _bumps_constraints +from easyreflectometry.data import DataSet1D +from easyreflectometry.inequality_constraints import InequalitySpec +from easyreflectometry.model import Model +from easyreflectometry.project import Project +from easyreflectometry.sample import Layer +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer +from easyreflectometry.sample import Sample + + +@pytest.fixture(autouse=True) +def clear_global_map(): + global_object.map._clear() + yield + global_object.map._clear() + + +def _two_layer_project(): + air = Material(0.0, 0.0, 'Air') + film = Material(4.0, 0.0, 'Film') + substrate = Material(2.047, 0.0, 'Si') + sample = Sample( + Multilayer(Layer(air, 0.0, 0.0, 'Superphase')), + Multilayer(Layer(film, 40.0, 3.0, 'A')), + Multilayer(Layer(film, 40.0, 3.0, 'B')), + Multilayer(Layer(substrate, 0.0, 3.0, 'Subphase')), + ) + model = Model(sample=sample) + project = Project() + project.default_model() + project.models[0] = model + model.interface = project._calculator + return project, model + + +@pytest.fixture +def unpatched_modules(): + """`install()` mutates the core modules and has no undo; keep these tests independent. + + Unwraps a shim left behind by an earlier test so the assertions start from + the core's own builder, and puts that state back afterwards. + """ + originals = [] + for module in (minimizer_bumps, sampler_dream): + builder = module.build_curve_problem + while getattr(builder, '_easyreflectometry_shim', False): + builder = builder.__wrapped__ + originals.append((module, builder)) + module.build_curve_problem = builder + yield + for module, builder in originals: + module.build_curve_problem = builder + + +@pytest.mark.usefixtures('unpatched_modules') +class TestInstall: + def test_patches_both_consumer_namespaces_and_is_idempotent(self, monkeypatch): + """Both consumers bind the name at import, so each has to be patched.""" + monkeypatch.setattr(minimizer_bumps, 'build_curve_problem', minimizer_bumps.build_curve_problem) + monkeypatch.setattr(sampler_dream, 'build_curve_problem', sampler_dream.build_curve_problem) + + _bumps_constraints.install() + patched = (minimizer_bumps.build_curve_problem, sampler_dream.build_curve_problem) + assert all(getattr(function, '_easyreflectometry_shim', False) for function in patched) + + _bumps_constraints.install() + assert (minimizer_bumps.build_curve_problem, sampler_dream.build_curve_problem) == patched + + +class TestShimAgainstARealFit: + def test_the_patched_entry_point_is_the_one_a_fit_calls(self): + """A signature check would not catch patching the wrong namespace.""" + project, model = _two_layer_project() + project.minimizer = AvailableMinimizers.Bumps + layers = [layer for assembly in model.sample for layer in assembly.layers] + q = np.linspace(0.01, 0.3, 50) + reflectivity = model.interface.fit_func(q, model.unique_name) + for layer in layers: + for parameter in (layer.thickness, layer.roughness, layer.material.sld, layer.material.isld): + parameter.fixed = True + layers[1].thickness.fixed = False + + calls = [] + + def factory(bumps_parameters): + calls.append(dict(bumps_parameters)) + return [] + + dataset = DataSet1D(name='sim', x=q, y=reflectivity, ye=(0.05 * reflectivity) ** 2) + project.fitter.fit_single_data_set_1d(dataset, constraints_factory=factory) + + assert len(calls) == 1 + assert any(name.startswith('p') for name in calls[0]) + + def test_infeasible_start_point_warns(self): + """This is what `model_reset()` in the shim buys; without it there is no warning.""" + project, model = _two_layer_project() + project.minimizer = AvailableMinimizers.Bumps + layers = [layer for assembly in model.sample for layer in assembly.layers] + thickness_a, thickness_b = layers[1].thickness, layers[2].thickness + q = np.linspace(0.01, 0.3, 50) + reflectivity = model.interface.fit_func(q, model.unique_name) + for layer in layers: + for parameter in (layer.thickness, layer.roughness, layer.material.sld, layer.material.isld): + parameter.fixed = True + thickness_a.fixed = thickness_b.fixed = False + model.scale.fixed = model.background.fixed = True + thickness_a.value, thickness_b.value = 40.0, 60.0 # sum 100, outside the budget + + paths = (project.parameter_path(thickness_a), project.parameter_path(thickness_b)) + project.add_inequality_constraint(InequalitySpec('a + b', '<', '90', {'a': paths[0], 'b': paths[1]}, {}, name='budget')) + dataset = DataSet1D(name='sim', x=q, y=reflectivity, ye=(0.05 * reflectivity) ** 2) + + with pytest.warns(UserWarning, match=r'Unsatisfied constraints: \[budget fails\]'): + project.fitter.fit_single_data_set_1d(dataset) + + def test_tolerates_a_model_with_no_free_parameters(self): + """`curve.pars` is empty then; the shim must not be the thing that breaks.""" + project, model = _two_layer_project() + project.minimizer = AvailableMinimizers.Bumps + for parameter in project.parameters: + if parameter.independent: + parameter.fixed = True + + seen = [] + q = np.linspace(0.01, 0.3, 50) + reflectivity = model.interface.fit_func(q, model.unique_name) + dataset = DataSet1D(name='sim', x=q, y=reflectivity, ye=(0.05 * reflectivity) ** 2) + + with pytest.raises(Exception) as error: + project.fitter.fit_single_data_set_1d(dataset, constraints_factory=lambda pars: seen.append(dict(pars)) or []) + + assert seen == [{}] + # Whatever bumps does with an empty problem, it is not an AttributeError + # from the shim reaching into a Curve that has no parameters. + assert not isinstance(error.value, AttributeError) + + +class TestExtendKeepsThePenalty: + def test_extend_re_enters_the_constraints_context(self): + """Without the wrapper a continued chain samples an unpenalised posterior.""" + active = [] + + class FakeSampler: + def extend(self, **kwargs): + active.append(_bumps_constraints._active.get()) + return 'extended' + + sampler = FakeSampler() + from easyreflectometry.fitting import MultiFitter + + def factory(bumps_parameters): + return [] + + MultiFitter._keep_constraints_on_extend(sampler, factory) + assert sampler.extend(additional_samples=10) == 'extended' + assert active == [factory] + # The context is released again afterwards. + assert _bumps_constraints._active.get() is None + + def test_no_wrapper_without_constraints(self): + from easyreflectometry.fitting import MultiFitter + + class FakeSampler: + def extend(self): + return None + + sampler = FakeSampler() + MultiFitter._keep_constraints_on_extend(sampler, None) + # Nothing shadowed the class's own method. + assert 'extend' not in sampler.__dict__ + + +class TestEngineRejection: + def test_non_bumps_engine_is_rejected(self): + """The message is also printed in the constraints tutorial; keep it verbatim.""" + project, model = _two_layer_project() + project.minimizer = AvailableMinimizers.LMFit + q = np.linspace(0.01, 0.3, 50) + reflectivity = model.interface.fit_func(q, model.unique_name) + dataset = DataSet1D(name='sim', x=q, y=reflectivity, ye=(0.05 * reflectivity) ** 2) + path = project.parameter_path([lay for a in model.sample for lay in a.layers][1].thickness) + project.add_inequality_constraint(InequalitySpec('a', '<', '90', {'a': path}, {})) + + with pytest.raises(ValueError) as error: + project.fitter.fit_single_data_set_1d(dataset) + + assert str(error.value) == ( + "Inequality constraints (constraints_factory) require the BUMPS engine; the selected minimizer uses 'lmfit'." + ) + + +class TestTheCoreIsNeverHandedTheKeyword: + """``constraints_factory`` is this library's own; the core knows nothing of it. + + ``Bumps.fit`` takes ``**kwargs`` and would accept — and drop — an + unrecognised keyword without a word, so passing one through would fit an + unconstrained problem silently. These pin the enforcement to this library. + """ + + def test_the_core_does_not_accept_it(self): + import inspect + + assert 'constraints_factory' not in inspect.signature(minimizer_bumps.Bumps.fit).parameters + + def test_a_constrained_fit_does_not_pass_it_down(self, monkeypatch): + project, model = _two_layer_project() + project.minimizer = AvailableMinimizers.Bumps + layers = [layer for assembly in model.sample for layer in assembly.layers] + thickness = layers[1].thickness + q = np.linspace(0.01, 0.3, 50) + reflectivity = model.interface.fit_func(q, model.unique_name) + for layer in layers: + for parameter in (layer.thickness, layer.roughness, layer.material.sld, layer.material.isld): + parameter.fixed = True + thickness.fixed = False + model.scale.fixed = model.background.fixed = True + dataset = DataSet1D(name='sim', x=q, y=reflectivity, ye=(0.05 * reflectivity) ** 2) + project.add_inequality_constraint(InequalitySpec('a', '<', '90', {'a': project.parameter_path(thickness)}, {})) + + seen = [] + original = minimizer_bumps.Bumps.fit + + def recording_fit(self, *args, **kwargs): + seen.append(kwargs) + return original(self, *args, **kwargs) + + monkeypatch.setattr(minimizer_bumps.Bumps, 'fit', recording_fit) + project.fitter.fit_single_data_set_1d(dataset) + + assert seen, 'the minimizer was never reached' + assert all('constraints_factory' not in kwargs for kwargs in seen) + + +class TestExplicitFactoryOnTheRawFitter: + """The GUI drives ``easy_science_multi_fitter.fit`` directly (see `for_experiments`). + + An explicit ``constraints_factory`` on that call used to be handed to the + core, which drops unrecognised keywords through ``**kwargs`` — so the + penalties were silently lost on exactly the path that asked for them. + """ + + def _fitter_and_arrays(self): + from easyreflectometry.fitting import MultiFitter + + project, model = _two_layer_project() + layers = [layer for assembly in model.sample for layer in assembly.layers] + q = np.linspace(0.01, 0.3, 50) + reflectivity = model.interface.fit_func(q, model.unique_name) + for layer in layers: + for parameter in (layer.thickness, layer.roughness, layer.material.sld, layer.material.isld): + parameter.fixed = True + layers[1].thickness.fixed = False + model.scale.fixed = model.background.fixed = True + dataset = DataSet1D(name='sim', x=q, y=reflectivity, ye=(0.05 * reflectivity) ** 2, model=model, auto_background=False) + fitter = MultiFitter.for_experiments([dataset]) + fitter.easy_science_multi_fitter.switch_minimizer(AvailableMinimizers.Bumps) + weights = 1.0 / np.sqrt(np.asarray(dataset.ye)) + return fitter, ([np.asarray(dataset.x)], [np.asarray(dataset.y)], [weights]) + + def test_explicit_factory_is_enforced_not_swallowed(self): + fitter, (x, y, weights) = self._fitter_and_arrays() + calls = [] + + fitter.easy_science_multi_fitter.fit( + x, y, weights=weights, constraints_factory=lambda pars: calls.append(dict(pars)) or [] + ) + + assert len(calls) == 1 + assert any(name.startswith('p') for name in calls[0]) + + def test_explicit_factory_wins_over_the_provider(self): + fitter, (x, y, weights) = self._fitter_and_arrays() + provider_calls, explicit_calls = [], [] + fitter.constraints_factory_provider = lambda: lambda pars: provider_calls.append(1) or [] + + fitter.easy_science_multi_fitter.fit( + x, y, weights=weights, constraints_factory=lambda pars: explicit_calls.append(1) or [] + ) + + assert explicit_calls and not provider_calls diff --git a/tests/unit/test_constraints.py b/tests/unit/test_constraints.py new file mode 100644 index 00000000..a7ee07e5 --- /dev/null +++ b/tests/unit/test_constraints.py @@ -0,0 +1,442 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +""" +Tests for the user-facing constraint helpers +""" + +import json + +import pytest +from easyscience import global_object +from easyscience.variable import DescriptorNumber +from easyscience.variable import Parameter + +from easyreflectometry import clamp_sum_partners +from easyreflectometry import constrain +from easyreflectometry import constrain_equal +from easyreflectometry import constrain_to_sum +from easyreflectometry import derived_parameter +from easyreflectometry import is_constrained_to_sum +from easyreflectometry import restore_sum_partners +from easyreflectometry import unconstrain +from easyreflectometry.project import Project +from easyreflectometry.sample import Material + + +@pytest.fixture(autouse=True) +def clear_global_map(): + global_object.map._clear() + yield + global_object.map._clear() + + +class TestConstrainEqual: + def test_ties_value_and_follows(self): + leader = Parameter('leader', 5.0, unit='angstrom', min=0.0, max=10.0) + follower = Parameter('follower', 1.0, unit='angstrom', min=0.0, max=2.0) + + constrain_equal(follower, to=leader) + + assert follower.independent is False + assert follower.value == 5.0 + leader.value = 7.0 + assert follower.value == 7.0 + + def test_overwrites_bounds_and_clears_fixed(self): + leader = Parameter('leader', 5.0, unit='angstrom', min=0.0, max=10.0) + follower = Parameter('follower', 1.0, unit='angstrom', min=0.5, max=2.0, fixed=True) + + constrain_equal(follower, to=leader) + + assert follower.min == leader.min + assert follower.max == leader.max + assert follower.fixed is False + + def test_dependent_setters_are_locked(self): + leader = Parameter('leader', 5.0) + follower = Parameter('follower', 1.0) + constrain_equal(follower, to=leader) + + with pytest.raises(AttributeError): + follower.value = 3.0 + with pytest.raises(AttributeError): + follower.fixed = True + + def test_descriptor_dependency_gives_degenerate_bounds(self): + # A DescriptorNumber dependency evaluates to a non-Parameter, so + # EasyScience sets min == max == value on the dependent. Pinned + # here because it is why the docs say "reset bounds after + # unconstrain". + leader = DescriptorNumber('leader', 3.0) + follower = Parameter('follower', 1.0, min=0.0, max=2.0) + + constrain_equal(follower, to=leader) + assert follower.min == 3.0 + assert follower.max == 3.0 + + unconstrain(follower) + assert follower.min == 3.0 + assert follower.max == 3.0 + + +class TestConstrain: + def test_scale_expression_follows(self): + leader = Parameter('leader', 5.0, unit='angstrom', min=0.0, max=10.0) + follower = Parameter('follower', 1.0, unit='angstrom', min=0.0, max=2.0) + + constrain(follower, '2 * t', t=leader) + + assert follower.value == 10.0 + leader.value = 6.0 + assert follower.value == 12.0 + + def test_multi_parameter_expression(self): + fraction = Parameter('fraction', 0.25, min=0.0, max=1.0) + sld_a = Parameter('sld_a', 2.0) + sld_b = Parameter('sld_b', 6.0) + mixed = Parameter('mixed', 0.0) + + constrain(mixed, 'frac * a + (1 - frac) * b', frac=fraction, a=sld_a, b=sld_b) + + assert mixed.value == pytest.approx(0.25 * 2.0 + 0.75 * 6.0) + fraction.value = 0.5 + assert mixed.value == pytest.approx(0.5 * 2.0 + 0.5 * 6.0) + + def test_reconstrain_replaces_dependency(self): + first = Parameter('first', 1.0) + second = Parameter('second', 2.0) + follower = Parameter('follower', 0.0) + + constrain_equal(follower, to=first) + constrain_equal(follower, to=second) + assert follower.value == 2.0 + + # No stale updates from the previous target + first.value = 100.0 + assert follower.value == 2.0 + second.value = 3.0 + assert follower.value == 3.0 + + def test_unknown_name_raises_and_reverts(self): + leader = Parameter('leader', 5.0) + follower = Parameter('follower', 1.0) + + with pytest.raises(NameError): + constrain(follower, 'a + b', a=leader) + + assert follower.independent is True + assert follower.value == 1.0 + + +class TestUnconstrain: + def test_removes_constraint_and_keeps_last_value(self): + leader = Parameter('leader', 5.0, min=0.0, max=10.0) + follower = Parameter('follower', 1.0, min=0.0, max=2.0) + constrain_equal(follower, to=leader) + leader.value = 7.0 + + unconstrain(follower) + + assert follower.independent is True + assert follower.value == 7.0 + # Bounds and fixed state are not restored + assert follower.min == 0.0 + assert follower.max == 10.0 + assert follower.fixed is False + # Fittable again + follower.value = 1.5 + assert follower.value == 1.5 + assert leader.value == 7.0 + + def test_idempotent_on_independent_parameter(self): + parameter = Parameter('parameter', 1.0) + unconstrain(parameter) + unconstrain(parameter) + assert parameter.independent is True + + +class TestIsConstrainedToSum: + def _sum(self): + a = Parameter('a', 30.0, unit='angstrom', min=0.0, max=100.0) + b = Parameter('b', 30.0, unit='angstrom', min=0.0, max=100.0) + constrain_to_sum(b, [a, b]) + return a, b + + def test_detects_the_sum_dependency(self): + a, b = self._sum() + assert is_constrained_to_sum(b) is True + assert is_constrained_to_sum(b, [a, b]) is True + assert is_constrained_to_sum(b, [a]) is True # `parameter` itself may be omitted + + def test_independent_parameter_is_not(self): + parameter = Parameter('p', 1.0) + assert is_constrained_to_sum(parameter) is False + + def test_other_dependencies_are_not(self): + leader = Parameter('leader', 5.0) + follower = Parameter('follower', 1.0) + constrain_equal(follower, to=leader) + assert is_constrained_to_sum(follower) is False + + def test_foreign_partner_requirement_fails(self): + a, b = self._sum() + stranger = Parameter('stranger', 1.0) + assert is_constrained_to_sum(b, [a, stranger]) is False + + def test_survives_project_reload(self): + source = Project() + source.default_model() + sample = source.models[0].sample + first = sample[1].layers[0].thickness + second = sample[2].layers[0].thickness + constrain_to_sum(second, [first, second]) + assert is_constrained_to_sum(second, [first, second]) + + project_dict = json.loads(json.dumps(source.as_dict())) + global_object.map._clear() + project = Project() + project.from_dict(project_dict) + sample = project.models[0].sample + assert is_constrained_to_sum(sample[2].layers[0].thickness, [sample[1].layers[0].thickness]) + + +class TestSumPartnerClamp: + def test_clamp_narrows_and_restore_widens(self): + a = Parameter('a', 30.0, unit='angstrom', min=0.0, max=1000.0) + b = Parameter('b', 30.0, unit='angstrom', min=0.0, max=1000.0) + constrain_to_sum(b, [a, b]) + + clamp_sum_partners([a], float(b.value)) + # a's headroom is the whole budget: value * (1 + remainder/occupied) + assert float(a.max) == pytest.approx(60.0) + a.value = 1e6 + assert float(a.value) == pytest.approx(60.0) + assert float(b.value) == pytest.approx(0.0) + + unconstrain(b) + restore_sum_partners([a]) + assert float(a.max) == pytest.approx(1000.0) + + def test_restore_is_idempotent_and_never_narrows(self): + a = Parameter('a', 30.0, unit='angstrom', min=0.0, max=1000.0) + b = Parameter('b', 30.0, unit='angstrom', min=0.0, max=1000.0) + constrain_to_sum(b, [a, b]) + clamp_sum_partners([a], float(b.value)) + unconstrain(b) + a.max = 2000.0 # user widened beyond the original in the meantime + restore_sum_partners([a]) + restore_sum_partners([a]) + assert float(a.max) == pytest.approx(2000.0) + + def test_already_tight_bound_is_left_alone(self): + a = Parameter('a', 30.0, unit='angstrom', min=0.0, max=40.0) + b = Parameter('b', 30.0, unit='angstrom', min=0.0, max=1000.0) + constrain_to_sum(b, [a, b]) + clamp_sum_partners([a], float(b.value)) + assert float(a.max) == pytest.approx(40.0) + restore_sum_partners([a]) + assert float(a.max) == pytest.approx(40.0) + + def test_backups_survive_project_reload(self): + source = Project() + source.default_model() + sample = source.models[0].sample + first = sample[1].layers[0].thickness + second = sample[2].layers[0].thickness + original_max = float(first.max) + constrain_to_sum(second, [first, second]) + clamp_sum_partners([first], float(second.value)) + clamped_max = float(first.max) + assert clamped_max < original_max + + project_dict = json.loads(json.dumps(source.as_dict())) + assert 'sum_partner_max_backups' in project_dict + global_object.map._clear() + project = Project() + project.from_dict(project_dict) + sample = project.models[0].sample + first = sample[1].layers[0].thickness + second = sample[2].layers[0].thickness + + # The clamp survives the round trip, and removing the constraint + # afterwards still hands back the original bound. + assert float(first.max) == pytest.approx(clamped_max) + unconstrain(second) + restore_sum_partners([first]) + assert float(first.max) == pytest.approx(original_max) + + def test_no_backups_key_when_nothing_is_clamped(self): + project = Project() + project.default_model() + assert 'sum_partner_max_backups' not in project.as_dict() + + +class TestProjectRoundTrip: + def test_constraint_survives_as_dict_from_dict(self): + # Requires the easyscience serializer to route nested Parameters through + # ``Parameter.as_dict`` and park the dependency as pending on rebuild. + src_project = Project() + src_project._info['name'] = 'Test' + src_project.default_model() + src_project._with_experiments = False + sample = src_project.models[0].sample + leader = sample[1].layers[0].thickness + follower = sample[2].layers[0].thickness + constrain(follower, '2 * t', t=leader) + assert follower.value == 2 * leader.value + + project_dict = src_project.as_dict() + global_object.map._clear() + + project = Project() + project.from_dict(project_dict) + sample = project.models[0].sample + leader = sample[1].layers[0].thickness + follower = sample[2].layers[0].thickness + + assert follower.independent is False + leader.value = 60.0 # within the default [50, 200] thickness limits + assert follower.value == 120.0 + + def test_constrain_to_sum_with_numeric_total_survives(self): + """The object-less constant built for an explicit total is embedded by value.""" + source = Project() + source.default_model() + sample = source.models[0].sample + constrain_to_sum( + sample[2].layers[0].roughness, + [sample[1].layers[0].roughness, sample[2].layers[0].roughness], + total=10.0, + ) + project_dict = json.loads(json.dumps(source.as_dict())) + global_object.map._clear() + + project = Project() + project.from_dict(project_dict) + sample = project.models[0].sample + project.models[0].sample[1].layers[0].roughness.value = 4.0 + assert sample[2].layers[0].roughness.value == 6.0 + + def test_constraint_against_a_derived_parameter_survives(self): + """`Model.total_thickness` is reachable by path, so it can be a dependency.""" + source = Project() + source.default_model() + model = source.models[0] + constrain(model.sample[1].layers[0].roughness, 'total / 100', total=model.total_thickness) + project_dict = json.loads(json.dumps(source.as_dict())) + global_object.map._clear() + + project = Project() + project.from_dict(project_dict) + model = project.models[0] + roughness = model.sample[1].layers[0].roughness + assert roughness.independent is False + assert roughness.value == model.total_thickness.value / 100 + + def test_unconstrain_does_not_resurrect_on_reload(self): + source = Project() + source.default_model() + sample = source.models[0].sample + follower = sample[2].layers[0].thickness + constrain(follower, '2 * t', t=sample[1].layers[0].thickness) + unconstrain(follower) + + project_dict = json.loads(json.dumps(source.as_dict())) + assert 'parameter_constraints' not in project_dict + global_object.map._clear() + + project = Project() + project.from_dict(project_dict) + assert project.models[0].sample[2].layers[0].thickness.independent is True + + def test_raw_make_independent_does_not_resurrect_either(self): + """The marker alone is not enough: the parameter must still be dependent.""" + source = Project() + source.default_model() + sample = source.models[0].sample + follower = sample[2].layers[0].thickness + constrain(follower, '2 * t', t=sample[1].layers[0].thickness) + follower.make_independent() # bypasses `unconstrain`, so the marker survives + + assert 'parameter_constraints' not in source.as_dict() + + def test_unreachable_dependency_raises_rather_than_freezing(self): + """Embedding a live parameter by value would silently kill the dependency.""" + project = Project() + project.default_model() + detached = Parameter('detached', 5.0, unit='angstrom') + constrain(project.models[0].sample[1].layers[0].roughness, 'a', a=detached) + + with pytest.raises(ValueError, match='not reachable from'): + project.as_dict() + + def test_unreachable_target_raises_rather_than_being_dropped(self): + """A constraint *on* an off-model parameter is refused, not silently lost. + + The material is saved through `materials_not_in_model`, so without this + the file would come back looking complete while the constraint had + quietly disappeared. + """ + project = Project() + project.default_model() + off_model = Material(sld=1.0, isld=0.0, name='OffModel') + project.add_material(off_model) + constrain(off_model.sld, '2 * s', s=project.models[0].sample[0].layers[0].material.sld) + + assert project.parameter_path(off_model.sld) is None + with pytest.raises(ValueError, match='not reachable from'): + project.as_dict(include_materials_not_in_model=True) + + def test_standalone_derived_parameter_is_session_only(self): + """A `derived_parameter` belongs to no model: it has no path, and a + constraint depending on it cannot be saved (documented limitation).""" + project = Project() + project.default_model() + sample = project.models[0].sample + total = derived_parameter('total', 'a + b', a=sample[1].layers[0].thickness, b=sample[2].layers[0].thickness) + + assert project.parameter_path(total) is None + constrain(sample[2].layers[0].roughness, 'T / 10', T=total) + with pytest.raises(ValueError, match='not reachable from'): + project.as_dict() + + def test_warns_when_dependencies_are_embedded_in_the_parameters(self): + """A file from a core that serializes dependencies in-place cannot be restored.""" + project = Project() + project.default_model() + project_dict = project.as_dict() + # Mimic the shape such a core writes for a dependent nested parameter. + project_dict['models']['data'][0]['scale']['_dependency_string'] = 'a' + global_object.map._clear() + + with pytest.warns(UserWarning, match='must be re-applied'): + Project().from_dict(project_dict) + + def test_chained_constraints_survive_and_still_follow(self): + """Records are restored in tree order, which need not be dependency order.""" + source = Project() + source.default_model() + sample = source.models[0].sample + root = sample[1].layers[0].roughness + middle = sample[2].layers[0].roughness + leaf = sample[2].layers[0].thickness + # leaf <- middle <- root, i.e. the chain runs against the tree order. + constrain(middle, '2 * r', r=root) + constrain(leaf, '10 * m', m=middle) + + project_dict = json.loads(json.dumps(source.as_dict())) + global_object.map._clear() + + project = Project() + project.from_dict(project_dict) + sample = project.models[0].sample + root = sample[1].layers[0].roughness + middle = sample[2].layers[0].roughness + leaf = sample[2].layers[0].thickness + + assert middle.independent is False + assert leaf.independent is False + root.value = 3.0 + assert middle.value == 6.0 + assert leaf.value == 60.0 diff --git a/tests/unit/test_derived_parameters.py b/tests/unit/test_derived_parameters.py new file mode 100644 index 00000000..45341af3 --- /dev/null +++ b/tests/unit/test_derived_parameters.py @@ -0,0 +1,197 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Tests for derived read-only parameters: helpers, ``Model.total_thickness`` and assembly toggles.""" + +import json + +import pytest +from easyscience import global_object +from easyscience.variable import Parameter + +from easyreflectometry import Project +from easyreflectometry.constraints import constrain_to_sum +from easyreflectometry.constraints import derived_parameter +from easyreflectometry.model import Model +from easyreflectometry.sample import Layer +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer +from easyreflectometry.sample import Sample + + +@pytest.fixture(autouse=True) +def clear_global_map(): + global_object.map._clear() + yield + global_object.map._clear() + + +def _thickness(value, name): + return Parameter(name, value, unit='angstrom', min=0.0, max=1000.0) + + +class TestDerivedParameter: + def test_follows_expression_and_is_read_only(self): + a, b = _thickness(10.0, 'a'), _thickness(20.0, 'b') + total = derived_parameter('total', 'a + b', a=a, b=b) + + assert total.value == 30.0 + assert str(total.unit) == 'Å' + assert total.independent is False + a.value = 15.0 + assert total.value == 35.0 + with pytest.raises(AttributeError): + total.value = 1.0 + + def test_requires_at_least_one_dependency(self): + with pytest.raises(ValueError): + derived_parameter('total', '1 + 1') + + def test_can_drive_other_dependencies(self): + a, b, c = _thickness(10.0, 'a'), _thickness(20.0, 'b'), _thickness(0.0, 'c') + total = derived_parameter('total', 'a + b', a=a, b=b) + c.make_dependent_on('t * 2', {'t': total}) + b.value = 30.0 + assert c.value == 80.0 + + +class TestConstrainToSum: + def test_absorbs_remainder_against_numeric_total(self): + a, b = _thickness(40.0, 'a'), _thickness(60.0, 'b') + constrain_to_sum(b, [a, b], total=120.0) + assert b.value == 80.0 + a.value = 50.0 + assert b.value == 70.0 + assert b.independent is False + + def test_default_total_is_current_sum(self): + a, b, c = _thickness(10.0, 'a'), _thickness(20.0, 'b'), _thickness(30.0, 'c') + constrain_to_sum(c, [a, b, c]) + assert c.value == 30.0 + a.value = 25.0 + assert c.value == 15.0 + + def test_total_can_be_a_parameter(self): + a, b, total = _thickness(10.0, 'a'), _thickness(20.0, 'b'), _thickness(100.0, 'T') + constrain_to_sum(b, [a], total=total) + assert b.value == 90.0 + total.value = 50.0 + assert b.value == 40.0 + + def test_rejects_nothing_to_constrain_against(self): + a = _thickness(10.0, 'a') + with pytest.raises(ValueError): + constrain_to_sum(a, [a]) + with pytest.raises(TypeError): + constrain_to_sum(a, [_thickness(1.0, 'b')], total='12') + + +def _model_with_film(*thicknesses): + sample = Sample(populate_if_none=False) + sample.add_assembly(Multilayer(Layer(Material(0.0, 0.0, 'air'), thickness=0.0, roughness=0.0, name='air'))) + for index, thickness in enumerate(thicknesses): + sample.add_assembly( + Multilayer(Layer(Material(2.0, 0.0, f'm{index}'), thickness=thickness, roughness=2.0, name=f'L{index}')) + ) + sample.add_assembly(Multilayer(Layer(Material(2.07, 0.0, 'Si'), thickness=0.0, roughness=2.0, name='Si'))) + return Model(sample=sample) + + +class TestModelTotalThickness: + def test_sums_film_layers_only(self): + model = _model_with_film(40.0, 60.0) + total = model.total_thickness + assert total.value == 100.0 + assert total.independent is False + assert total not in model.get_fit_parameters() + with pytest.raises(AttributeError): + total.value = 5.0 + + def test_tracks_edits_and_structure_changes(self): + model = _model_with_film(40.0, 60.0) + model.sample[1].layers[0].thickness.value = 45.0 + assert model.total_thickness.value == 105.0 + + # layer appended inside an assembly: no notification path exists, the + # property re-derives on access + model.sample[2].layers.append(Layer(Material(1.0, 0.0, 'x'), thickness=10.0, roughness=1.0, name='X')) + assert model.total_thickness.value == 115.0 + + model.remove_assembly(1) + assert model.total_thickness.value == 70.0 + + def test_no_film_gives_zero_and_independent(self): + model = _model_with_film() + assert model.total_thickness.value == 0.0 + assert model.total_thickness.independent is True + assert model.total_thickness.fixed is True + model.add_assemblies(Multilayer(Layer(Material(1.0, 0.0, 'x'), thickness=10.0, roughness=1.0, name='X'))) + # the new assembly became the last layer (subphase); the former Si (0 A) is now film + assert model.total_thickness.value == 0.0 + assert model.total_thickness.independent is False + + def test_emptied_film_does_not_leak_a_free_fit_parameter(self): + """Becoming dependent clears `fixed`; losing the film must put it back. + + Otherwise the parameter returns to the fit as a free variable sitting at + zero, and a fitter would happily vary it. + """ + model = _model_with_film(40.0) + assert model.total_thickness.independent is False + + model.remove_assembly(1) + total = model.total_thickness + assert total.value == 0.0 + assert total.independent is True + assert total.fixed is True + assert 'total_thickness' not in [parameter.name for parameter in model.get_fit_parameters()] + + def test_not_serialized_but_rebuilt_on_load(self): + project = Project() + project.default_model() + model = project.models[0] + before = model.total_thickness.value + project_dict = json.loads(json.dumps(project.as_dict())) + assert 'total_thickness' not in json.dumps(project_dict) + + global_object.map._clear() + reloaded = Project() + reloaded.from_dict(project_dict) + assert reloaded.models[0].total_thickness.value == before + assert reloaded.models[0].total_thickness.independent is False + + +class TestAssemblyConformalToggles: + def _assembly(self): + layers = [ + Layer(Material(1.0, 0.0, f'm{i}'), thickness=10.0 * (i + 1), roughness=float(i + 1), name=f'L{i}') for i in range(3) + ] + return Multilayer(layers) + + def test_conformal_thickness(self): + assembly = self._assembly() + assert assembly.conformal_thickness is False + assembly.conformal_thickness = True + assert assembly.conformal_thickness is True + assert [layer.thickness.value for layer in assembly.layers] == [10.0, 10.0, 10.0] + assembly.layers[0].thickness.value = 25.0 + assert assembly.layers[2].thickness.value == 25.0 + assembly.conformal_thickness = False + assert assembly.conformal_thickness is False + assert assembly.layers[1].thickness.independent is True + + def test_conformal_roughness(self): + assembly = self._assembly() + assert assembly.conformal_roughness is False + assembly.conformal_roughness = True + assert assembly.conformal_roughness is True + assembly.layers[0].roughness.value = 7.0 + assert [layer.roughness.value for layer in assembly.layers] == [7.0, 7.0, 7.0] + assembly.conformal_roughness = False + assert assembly.layers[2].roughness.independent is True + + def test_single_layer_assembly_is_never_conformal(self): + assembly = Multilayer(Layer(Material(1.0, 0.0, 'm'), thickness=1.0, roughness=1.0, name='L')) + assert assembly.conformal_thickness is False + assembly.conformal_thickness = True + assert assembly.conformal_thickness is False diff --git a/tests/unit/test_inequality_constraints.py b/tests/unit/test_inequality_constraints.py new file mode 100644 index 00000000..b24d5adf --- /dev/null +++ b/tests/unit/test_inequality_constraints.py @@ -0,0 +1,326 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Tests for cross-parameter inequality constraints (specs, translation, project integration).""" + +import json + +import numpy as np +import pytest +from easyscience import global_object +from easyscience.fitting import AvailableMinimizers +from easyscience.variable import Parameter + +from easyreflectometry import Project +from easyreflectometry.constraints import constrain +from easyreflectometry.constraints import derived_parameter +from easyreflectometry.data import DataSet1D +from easyreflectometry.fitting import MultiFitter +from easyreflectometry.inequality_constraints import InequalitySpec +from easyreflectometry.inequality_constraints import UnitError +from easyreflectometry.inequality_constraints import build_constraints_factory +from easyreflectometry.inequality_constraints import check_units +from easyreflectometry.inequality_constraints import evaluate_spec +from easyreflectometry.sample import Layer +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer + + +@pytest.fixture(autouse=True) +def clear_global_map(): + global_object.map._clear() + yield + global_object.map._clear() + + +class _FakeBumpsParameter: + def __init__(self, value): + self.value = value + + +def _resolver(mapping): + return lambda path: mapping[path] + + +# --------------------------------------------------------------------------- spec + + +class TestInequalitySpec: + def test_normalizes_relation_aliases(self): + assert InequalitySpec('a', '≤', 'b', {'a': 'x'}, {'b': 'y'}).op == '<=' + assert InequalitySpec('a', '≥', 'b', {'a': 'x'}, {'b': 'y'}).op == '>=' + + def test_rejects_unknown_relation(self): + with pytest.raises(ValueError, match='Unsupported relation'): + InequalitySpec('a', '==', 'b', {'a': 'x'}, {'b': 'y'}) + + def test_rejects_unmapped_identifiers(self): + with pytest.raises(ValueError, match='unmapped names: c'): + InequalitySpec('a + c', '<', 'b', {'a': 'x'}, {'b': 'y'}) + + def test_rejects_empty_side_and_bad_syntax(self): + with pytest.raises(ValueError, match='cannot be empty'): + InequalitySpec('', '<', 'b', {}, {'b': 'y'}) + with pytest.raises(SyntaxError): + InequalitySpec('a +', '<', 'b', {'a': 'x'}, {'b': 'y'}) + + def test_numeric_rhs_and_math_symbols_need_no_mapping(self): + spec = InequalitySpec('sqrt(a) * pi', '<', '90', {'a': 'x'}, {}) + assert spec.rhs_paths == {} + + def test_round_trip_dict(self): + spec = InequalitySpec('a + b', '<=', 'c', {'a': 'p/a', 'b': 'p/b'}, {'c': 'p/c'}, name='n', enabled=False) + restored = InequalitySpec.from_dict(json.loads(json.dumps(spec.to_dict()))) + assert restored == spec + assert str(restored) == 'a + b <= c' + + def test_rejects_alias_mapped_to_different_paths_on_the_two_sides(self): + # `paths` merges both sides; without the check the right side would silently win. + with pytest.raises(ValueError, match='different parameters'): + InequalitySpec('a', '<', 'a + b', {'a': 'p/x'}, {'a': 'p/y', 'b': 'p/z'}) + # The same alias for the same parameter on both sides is fine. + spec = InequalitySpec('a', '<', 'a + b', {'a': 'p/x'}, {'a': 'p/x', 'b': 'p/z'}) + assert spec.paths == {'a': 'p/x', 'b': 'p/z'} + + +# --------------------------------------------------------------------------- translation + + +class TestTranslation: + def _params(self): + a = Parameter('a', 10.0, unit='angstrom', min=0, max=100) + b = Parameter('b', 20.0, unit='angstrom', min=0, max=100) + c = Parameter('c', 5.0, unit='angstrom', min=0, max=100, fixed=True) + return a, b, c + + def test_operands_read_bumps_values_not_easyscience_values(self): + a, b, c = self._params() + spec = InequalitySpec('a + b', '<', '25', {'a': 'pa', 'b': 'pb'}, {}) + factory = build_constraints_factory([spec], _resolver({'pa': a, 'pb': b})) + bumps_a, bumps_b = _FakeBumpsParameter(10.0), _FakeBumpsParameter(20.0) + (constraint,) = factory({'p' + a.unique_name: bumps_a, 'p' + b.unique_name: bumps_b}) + + assert float(constraint) == pytest.approx(5.0) # 30 - 25, linear violation + bumps_a.value = 1.0 # optimizer trial point: EasyScience `a` is still 10 + assert a.value == 10.0 + assert float(constraint) == 0.0 + assert str(constraint) == 'a + b < 25' + + def test_fixed_parameters_are_constants(self): + a, b, c = self._params() + spec = InequalitySpec('a', '>', 'c', {'a': 'pa'}, {'c': 'pc'}) + factory = build_constraints_factory([spec], _resolver({'pa': a, 'pc': c})) + (constraint,) = factory({'p' + a.unique_name: _FakeBumpsParameter(3.0)}) # c not in the problem + assert float(constraint) == pytest.approx(2.0) # 5 - 3 + + def test_dependent_parameters_are_expanded_to_free_leaves(self): + a, b, c = self._params() + total = derived_parameter('total', 'x + y + z', x=a, y=b, z=c) + half = Parameter('half', 0.0, unit='angstrom', min=-1e6, max=1e6) + constrain(half, 't / 2', t=total) # dependent on a dependent + spec = InequalitySpec('a', '<', 'h', {'a': 'pa'}, {'h': 'ph'}) + factory = build_constraints_factory([spec], _resolver({'pa': a, 'ph': half})) + bumps_a, bumps_b = _FakeBumpsParameter(10.0), _FakeBumpsParameter(20.0) + (constraint,) = factory({'p' + a.unique_name: bumps_a, 'p' + b.unique_name: bumps_b}) + + # half = (10 + 20 + 5) / 2 = 17.5 > a = 10 -> satisfied + assert float(constraint) == 0.0 + bumps_b.value = 0.0 # half = 7.5 < a = 10 -> violated by 2.5, read from the trial vector + assert float(constraint) == pytest.approx(2.5) + + def test_disabled_specs_give_no_factory(self): + a, b, c = self._params() + spec = InequalitySpec('a', '<', 'b', {'a': 'pa'}, {'b': 'pb'}, enabled=False) + assert build_constraints_factory([spec], _resolver({'pa': a, 'pb': b})) is None + assert build_constraints_factory([], _resolver({})) is None + + @pytest.mark.parametrize( + 'op, lhs, rhs, violation', + [('<', 3.0, 5.0, 0.0), ('<', 5.0, 5.0, 0.0), ('<', 7.0, 5.0, 2.0), ('>', 3.0, 5.0, 2.0), ('>=', 6.0, 5.0, 0.0)], + ) + def test_evaluate_spec_violation(self, op, lhs, rhs, violation): + a = Parameter('a', lhs, min=-100, max=100) + b = Parameter('b', rhs, min=-100, max=100) + spec = InequalitySpec('a', op, 'b', {'a': 'pa'}, {'b': 'pb'}) + result = evaluate_spec(spec, _resolver({'pa': a, 'pb': b})) + assert result.lhs == lhs and result.rhs == rhs + assert result.violation == pytest.approx(violation) + assert result.satisfied is (violation == 0.0) + + def test_check_units(self): + a, b, c = self._params() + sld = Parameter('sld', 2.0, unit='1/angstrom**2', min=-10, max=10) + check_units( + InequalitySpec('a + b', '<', 'c', {'a': 'pa', 'b': 'pb'}, {'c': 'pc'}), + _resolver({'pa': a, 'pb': b, 'pc': c}), + ) + check_units(InequalitySpec('a', '<', '90', {'a': 'pa'}, {}), _resolver({'pa': a})) + with pytest.raises(ValueError, match='Incompatible units'): + check_units(InequalitySpec('a', '<', 's', {'a': 'pa'}, {'s': 'ps'}), _resolver({'pa': a, 'ps': sld})) + + def test_check_units_raises_typed_unit_error(self): + # A typed exception so callers do not have to match message substrings; + # it subclasses ValueError, so the assertions above stay valid too. + a, _, _ = self._params() + sld = Parameter('sld_t', 2.0, unit='1/angstrom**2', min=-10, max=10) + with pytest.raises(UnitError): + check_units(InequalitySpec('a', '<', 's', {'a': 'pa'}, {'s': 'ps'}), _resolver({'pa': a, 'ps': sld})) + + def test_check_units_mixed_literals_fall_back_to_numeric(self): + # '90 - b' cannot be evaluated with units (a literal has none); it is + # checked numerically and its literals read in the other side's unit. + a, b, c = self._params() + check_units(InequalitySpec('a', '<', '90 - b', {'a': 'pa'}, {'b': 'pb'}), _resolver({'pa': a, 'pb': b})) + # broken syntax is still rejected (at spec construction) + with pytest.raises(SyntaxError): + InequalitySpec('a', '<', '90 - b +', {'a': 'pa'}, {'b': 'pb'}) + + +# --------------------------------------------------------------------------- project integration + + +def _two_layer_project(): + project = Project() + project.default_model() + model = project.models[0] + film_a = Multilayer(Layer(Material(3.0, 0.0, 'A'), thickness=40.0, roughness=3.0, name='A'), name='A') + film_b = Multilayer(Layer(Material(5.0, 0.0, 'B'), thickness=60.0, roughness=3.0, name='B'), name='B') + substrate = model.sample[-1] + model.remove_assembly(len(model.sample) - 1) + model.remove_assembly(len(model.sample) - 1) + model.add_assemblies(film_a, film_b, substrate) + return project, model + + +class TestProjectPaths: + def test_parameter_path_round_trip(self): + project, model = _two_layer_project() + t_a = model.sample[1].layers[0].thickness + path = project.parameter_path(t_a) + assert path == 'models/0/sample/1/layers/0/thickness' + assert project.resolve_parameter_path(path) is t_a + assert project.parameter_path(model.scale) == 'models/0/scale' + assert project.parameter_path(model.total_thickness) == 'models/0/total_thickness' + sld_path = project.parameter_path(model.sample[1].layers[0].material.sld) + assert sld_path == 'models/0/sample/1/layers/0/material/sld' + assert project.resolve_parameter_path(sld_path) is model.sample[1].layers[0].material.sld + + def test_unreachable_parameter_and_bad_paths(self): + project, model = _two_layer_project() + assert project.parameter_path(Parameter('loose', 1.0)) is None + with pytest.raises(KeyError): + project.resolve_parameter_path('models/0/sample/99/layers/0/thickness') + with pytest.raises(KeyError): + project.resolve_parameter_path('models/0/nope') + with pytest.raises(KeyError): + project.resolve_parameter_path('models/0/sample') # not a parameter + with pytest.raises(KeyError): + project.resolve_parameter_path('models/0/_sample/0') # private attributes are off limits + + +class TestProjectInequalities: + def test_registry_validation_and_persistence(self): + project, model = _two_layer_project() + t_a = model.sample[1].layers[0].thickness + t_b = model.sample[2].layers[0].thickness + pa, pb = project.parameter_path(t_a), project.parameter_path(t_b) + project.add_inequality_constraint(InequalitySpec('a', '<', 'b', {'a': pa}, {'b': pb}, name='order')) + project.add_inequality_constraint(InequalitySpec('a + b', '<', '90', {'a': pa, 'b': pb}, {}, name='budget')) + with pytest.raises(ValueError, match='Incompatible units'): + project.add_inequality_constraint( + InequalitySpec('a', '<', 's', {'a': pa}, {'s': project.parameter_path(model.scale)}) + ) + assert [s.name for s in project.violated_inequality_constraints()] == ['budget'] + + project_dict = json.loads(json.dumps(project.as_dict())) + assert len(project_dict['inequality_constraints']) == 2 + global_object.map._clear() + reloaded = Project() + reloaded.from_dict(project_dict) + assert [str(s) for s in reloaded.inequality_constraints] == ['a < b', 'a + b < 90'] + evaluations = reloaded.evaluate_inequality_constraints() + assert [e.satisfied for e in evaluations] == [True, False] + + reloaded.remove_inequality_constraint('budget') + assert [s.name for s in reloaded.inequality_constraints] == ['order'] + reloaded.remove_inequality_constraint(0) + assert reloaded.inequality_constraints == [] + assert 'inequality_constraints' not in reloaded.as_dict() + + def test_old_project_files_without_inequalities_load(self): + project, _ = _two_layer_project() + project_dict = project.as_dict() + project_dict.pop('inequality_constraints', None) + global_object.map._clear() + reloaded = Project() + reloaded.from_dict(project_dict) + assert reloaded.inequality_constraints == [] + + +class TestInequalityFit: + def test_bumps_fit_respects_inequality_and_lmfit_is_rejected(self): + project, model = _two_layer_project() + project.minimizer = AvailableMinimizers.Bumps + layers = [layer for assembly in model.sample for layer in assembly.layers] + t_a, t_b = layers[1].thickness, layers[2].thickness + q = np.linspace(0.01, 0.3, 150) + t_a.value, t_b.value = 45.0, 55.0 # truth sums to 100 + r_true = model.interface.fit_func(q, model.unique_name) + t_a.value, t_b.value = 30.0, 50.0 # feasible start + for layer in layers: + for par in (layer.thickness, layer.roughness, layer.material.sld, layer.material.isld): + par.fixed = True + t_a.fixed = False + t_b.fixed = False + model.scale.fixed = True + model.background.fixed = True + pa, pb = project.parameter_path(t_a), project.parameter_path(t_b) + project.add_inequality_constraint(InequalitySpec('a + b', '<', '90', {'a': pa, 'b': pb}, {}, name='budget')) + project.add_inequality_constraint(InequalitySpec('a', '<', 'b', {'a': pa}, {'b': pb}, name='order')) + dataset = DataSet1D(name='sim', x=q, y=r_true, ye=(0.05 * r_true) ** 2) + + result = project.fitter.fit_single_data_set_1d(dataset) + + assert result.success + assert t_a.value + t_b.value <= 90.0 + 1e-3 + assert t_a.value + t_b.value == pytest.approx(90.0, abs=0.05) # lands on the boundary + assert t_a.value <= t_b.value + 1e-3 + + project.minimizer = AvailableMinimizers.LMFit + with pytest.raises(ValueError, match='require the BUMPS engine'): + project.fitter.fit_single_data_set_1d(dataset) + + def test_for_experiments_raw_fit_applies_project_inequalities(self): + """The documented GUI path — `for_experiments` then driving the raw + `easy_science_multi_fitter.fit(...)` — must apply the project's + inequality constraints (and refuse non-BUMPS engines) instead of + silently fitting an unconstrained problem.""" + project, model = _two_layer_project() + layers = [layer for assembly in model.sample for layer in assembly.layers] + t_a, t_b = layers[1].thickness, layers[2].thickness + q = np.linspace(0.01, 0.3, 150) + t_a.value, t_b.value = 45.0, 55.0 # truth sums to 100 + r_true = model.interface.fit_func(q, model.unique_name) + t_a.value, t_b.value = 30.0, 50.0 # feasible start + for layer in layers: + for par in (layer.thickness, layer.roughness, layer.material.sld, layer.material.isld): + par.fixed = True + t_a.fixed = False + t_b.fixed = False + model.scale.fixed = True + model.background.fixed = True + pa, pb = project.parameter_path(t_a), project.parameter_path(t_b) + project.add_inequality_constraint(InequalitySpec('a + b', '<', '90', {'a': pa, 'b': pb}, {}, name='budget')) + dataset = DataSet1D(name='sim', x=q, y=r_true, ye=(0.05 * r_true) ** 2, model=model, auto_background=False) + + fitter = MultiFitter.for_experiments([dataset], constraints_factory_provider=project.build_constraints_factory) + fitter.easy_science_multi_fitter.switch_minimizer(AvailableMinimizers.Bumps) + weights = 1.0 / np.sqrt(np.asarray(dataset.ye)) + results = fitter.easy_science_multi_fitter.fit([np.asarray(dataset.x)], [np.asarray(dataset.y)], weights=[weights]) + + assert results[0].success + assert t_a.value + t_b.value <= 90.0 + 1e-3 # unconstrained optimum (100) is refused + + fitter.easy_science_multi_fitter.switch_minimizer(AvailableMinimizers.LMFit) + with pytest.raises(ValueError, match='require the BUMPS engine'): + fitter.easy_science_multi_fitter.fit([np.asarray(dataset.x)], [np.asarray(dataset.y)], weights=[weights]) From 8b135210b6f14b1cd408ac776b56a13efe91c2ee Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Wed, 2 Sep 2026 11:33:06 +0200 Subject: [PATCH 27/38] Orso improvements (#402) * initial commit * code review fixes * more unit tests for orso functionality --- .github/workflows/python-package.yml | 4 +- .github/workflows/test.yml | 43 +- CHANGELOG.md | 45 + CONTRIBUTING.md | 22 + pixi.lock | 3 +- pixi.toml | 23 +- pyproject.toml | 17 +- src/easyreflectometry/data/__init__.py | 6 + src/easyreflectometry/data/measurement.py | 90 +- src/easyreflectometry/data/polarized.py | 61 +- src/easyreflectometry/model/model.py | 19 +- .../model/resolution_functions.py | 6 + src/easyreflectometry/orso_utils.py | 805 ++++++++++++++++-- src/easyreflectometry/project.py | 247 ++++-- tests/_static/example.orb | Bin 0 -> 21869 bytes tests/_static/fwhm_sqz.ort | 39 + tests/_static/nan_sqz.ort | 39 + tests/_static/nm_units.ort | 67 ++ tests/_static/partial_nan_sqz.ort | 39 + tests/_static/polarized_2ch.ort | 57 ++ tests/integration/fitting/test_dummy.py | 1 + .../integration/scipp-analysis/test_dummy.py | 1 + tests/integration/test_ort_file_fitting.py | 1 + tests/test_fitting.py | 4 + tests/test_measurement_comprehensive.py | 43 + tests/test_orso_support.py | 733 ++++++++++++++++ tests/test_polarized_fitting.py | 4 + tests/unit/test_bumps_constraints_shim.py | 1 + tests/unit/test_inequality_constraints.py | 1 + tests/unit/test_project_core.py | 20 +- 30 files changed, 2257 insertions(+), 184 deletions(-) create mode 100644 tests/_static/example.orb create mode 100644 tests/_static/fwhm_sqz.ort create mode 100644 tests/_static/nan_sqz.ort create mode 100644 tests/_static/nm_units.ort create mode 100644 tests/_static/partial_nan_sqz.ort create mode 100644 tests/_static/polarized_2ch.ort create mode 100644 tests/test_orso_support.py diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index c0717847..ac08b092 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -34,7 +34,9 @@ jobs: - name: Test with pytest run: | cd ./dist - pytest ../ + # This workflow only runs on master/pre-release, so include the + # `slow` tests that pytest's addopts deselect by default. + pytest ../ -m 'slow or not slow' - uses: actions/upload-artifact@v4 with: name: EasyReflectometrys - Python ${{ matrix.python-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 51de8d5c..8290d7e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,17 +54,37 @@ jobs: outputs: pytest-marks: ${{ steps.set-mark.outputs.pytest_marks }} + unit-task: ${{ steps.set-mark.outputs.unit_task }} + integration-task: ${{ steps.set-mark.outputs.integration_task }} steps: - # Determine if integration tests should be run fully or only the fast ones - # (to save time on branches other than master and develop) - - name: Set mark for integration tests + # Tests marked `slow` (real minimizer runs, samplers, long fits) are + # deselected by pytest's addopts, so the `*-tests` tasks run the fast + # subset and the `*-tests-all` tasks add the slow ones back. Run the + # full set where it matters -- pull requests (the release PR included), + # master/develop, and manual dispatch -- and the fast subset on ordinary + # feature-branch pushes. Integration tests keep their own `fast` gate on + # top; the two are mutually exclusive by construction, so only one of + # `pytest-marks` / the `-all` task is ever in play. + - name: Set marks and task names id: set-mark run: | - if [[ "${{ env.CI_BRANCH }}" == "master" || "${{ env.CI_BRANCH }}" == "develop" ]]; then + full_run=false + case "${{ github.event_name }}" in + pull_request|workflow_dispatch) full_run=true ;; + esac + case "${{ env.CI_BRANCH }}" in + master|develop) full_run=true ;; + esac + + if [[ "$full_run" == "true" ]]; then echo "pytest_marks=" >> $GITHUB_OUTPUT + echo "unit_task=unit-tests-all" >> $GITHUB_OUTPUT + echo "integration_task=integration-tests-all" >> $GITHUB_OUTPUT else echo "pytest_marks=-m fast" >> $GITHUB_OUTPUT + echo "unit_task=unit-tests" >> $GITHUB_OUTPUT + echo "integration_task=integration-tests" >> $GITHUB_OUTPUT fi # Job 2: Test code @@ -87,7 +107,7 @@ jobs: with: environments: ${{ env.PIXI_ENVS }} - - name: Run unit tests + - name: Run unit tests (${{ needs.env-prepare.outputs.unit-task }}) shell: bash run: | set -euo pipefail @@ -99,7 +119,7 @@ jobs: env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.XX -> py-3XX-env echo "Running tests in environment: $env" - pixi run --environment $env unit-tests + pixi run --environment $env ${{ needs.env-prepare.outputs.unit-task }} done - name: Run functional tests @@ -117,7 +137,7 @@ jobs: pixi run --environment $env functional-tests done - - name: Run integration tests ${{ needs.env-prepare.outputs.pytest-marks }} + - name: Run integration tests (${{ needs.env-prepare.outputs.integration-task }}) shell: bash run: | set -euo pipefail @@ -129,7 +149,7 @@ jobs: env="py-$(echo $py_ver | tr -d .)-env" # Converts 3.XX -> py-3XX-env echo "Running tests in environment: $env" - pixi run --environment $env integration-tests ${{ needs.env-prepare.outputs.pytest-marks }} + pixi run --environment $env ${{ needs.env-prepare.outputs.integration-task }} ${{ needs.env-prepare.outputs.pytest-marks }} done # Delete all local tags when not on a tagged commit to force versioningit @@ -254,7 +274,8 @@ jobs: cd easyreflectometry_py$py_ver echo "Running tests" - pixi run python -m pytest ../tests/unit/ --color=yes -v + # This job only runs on master, so always include the `slow` tests. + pixi run python -m pytest ../tests/unit/ -m 'slow or not slow' --color=yes -v echo "Exiting pixi project directory" cd .. @@ -279,7 +300,7 @@ jobs: cd .. done - - name: Run integration tests ${{ needs.env-prepare.outputs.pytest-marks }} + - name: Run integration tests shell: bash run: | set -euo pipefail @@ -294,7 +315,7 @@ jobs: echo "Running tests" # No -n auto: concurrent xdist workers race on arviz's daily-warning # stamp file when they import easyreflectometry. See pixi.toml. - pixi run python -m pytest ../tests/integration/ --color=yes -v ${{ needs.env-prepare.outputs.pytest-marks }} + pixi run python -m pytest ../tests/integration/ -m 'slow or not slow' --color=yes -v echo "Exiting pixi project directory" cd .. diff --git a/CHANGELOG.md b/CHANGELOG.md index ffd25625..d1fdc054 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -223,6 +223,51 @@ returned. disable/re-enable cycle and are re-attached when magnetism is enabled again. `update_layer` also accepts the magnetism keys one at a time. +## ORSO file handling + +- Binary ORSO (`.orb`, NeXus/HDF5) files are read and written alongside + `.ort` text files. The format is detected from the ORSO banner line or + the HDF5 magic bytes, not the file extension, so a file that carries + the banner but fails to parse now raises instead of being silently + re-read as plain text (which dropped the whole header, polarization + included). `.orb` support needs `h5py`, available as the new `orb` + extra; `orsopy` is pinned to `>=1.2`. +- New ORSO export. `Project.save_experiment_as_orso(path, index=None)` + writes an experiment (`.ort` or `.orb`), with the model, when set, + serialized as `data_source.sample.model`. Backed by the new + `save_orso_experiment`, `orso_datasets_from_experiment` and + `sample_to_orso_model` in `easyreflectometry.orso_utils`. A polarized + experiment becomes one file with one `data_set:` block per spin + channel. `Model.as_orso` now returns the ORSO model-language + dictionary (slab representation) rather than the internal `as_dict`. +- Repeating multilayers survive a round trip. Loading resolves the ORSO + stack with `resolve_stack()` instead of flattening it, so a sub-stack + keeps its repetition count and comes back as a `RepeatingMultilayer`; + export writes it with the inline `N ( ... )` stack syntax. +- Units declared in the file are honoured: `Qz` in `1/nm`, lengths in + `nm` (the ORSO default) and SLDs in `1/nm^2` are converted on load, + instead of being read as angstrom-based numbers. +- Resolution and error columns are read more carefully. A column + declared `value_is: FWHM` is converted to sigma on load, `nan` entries + in `sQz` are filled by interpolating over the valid points, and + partially missing error columns warn rather than propagating `nan` + into a fit. Stored `Pointwise` resolutions remain variances, so saved + projects round-trip without migration. +- `Project.load_polarized_experiment_from_file(path)` loads a polarized + experiment from a single multi-dataset ORSO file, classifying each + `data_set:` block by its own `instrument_settings.polarization` + header. Only `pp/pm/mp/mm` are mapped; a file with an unmappable or + duplicated channel raises rather than guessing. Supported by the new + `channel_from_orso_polarization` and + `detect_polarization_channels_per_dataset` in + `easyreflectometry.data`. +- New `easyreflectometry.data.dataset_from_datagroup` builds a + `DataSet1D` from one dataset of an already-loaded `DataGroup`, and + keeps the parsed ORSO header on the dataset as `orso_header` so + exporters can reuse the original provenance. `load_as_dataset` and the + project loaders accept a pre-loaded `DataGroup`, so importing a file + no longer parses it three or four times. + ## Documentation - The documentation is now MkDocs (Material) only. The legacy Sphinx diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a6ceeebd..3aff51c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -242,6 +242,28 @@ You can run individual checks, for example, to run only unit tests: pixi run unit-tests ``` +### Slow tests + +A handful of tests run a real minimiser, a sampler or a long fit. They +are marked `slow` and are **deselected by default**, so +`pixi run unit-tests` (and a bare `pytest`) gives you the fast feedback +loop. CI runs the full set on pull requests, on `master`/`develop`, and +on manual dispatch, so nothing is skipped before a change is merged or +released. + +To include them locally: + +```bash +pixi run unit-tests-all # fast + slow, same tree as unit-tests +pixi run slow-tests # only the slow ones, whole tree +pixi run test-all # unit + functional + integration, everything +``` + +Mark a new test with `@pytest.mark.slow` (it works on a test function or +a whole class) when it costs roughly a second or more of actual work. +Note that `-m` on the command line overrides the default, so running a +slow test by node id needs `-m 'slow or not slow'`. + or to run only Python linting checks: ```bash diff --git a/pixi.lock b/pixi.lock index e457f388..b2006eae 100644 --- a/pixi.lock +++ b/pixi.lock @@ -8316,7 +8316,7 @@ packages: - asteval - bumps - easyscience @ git+https://github.com/easyscience/easyscience.git@sampler-engine-structure-280 - - orsopy + - orsopy>=1.2 - plotly - pooch - refl1d>=1.0.0 @@ -8358,6 +8358,7 @@ packages: - spdx-headers ; extra == 'dev' - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' + - h5py ; extra == 'orb' requires_python: '>=3.11' - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 name: easyscience diff --git a/pixi.toml b/pixi.toml index 70a2a773..a0a12b19 100644 --- a/pixi.toml +++ b/pixi.toml @@ -89,6 +89,11 @@ user = { features = ['py-max', 'user'] } # (pending migration into tests/unit/), so run the whole tree minus the # functional and integration subtrees -- otherwise CI coverage only sees # the handful of files under tests/unit/. +# Expensive tests (real minimizer runs, samplers, long fits) carry the `slow` +# marker and are deselected by default -- see the `addopts` in pyproject.toml. +# Use `unit-tests-all` / `test-all` to include them, or `slow-tests` to run +# only them. CI runs the full set on pull requests, on master/develop and on +# manual dispatch; plain feature-branch pushes get the fast subset. unit-tests = 'python -m pytest tests/ --ignore=tests/functional --ignore=tests/integration --color=yes -v' functional-tests = 'python -m pytest tests/functional/ --color=yes -v' # No -n auto: importing easyreflectometry pulls in arviz, and arviz 0.23.4 @@ -102,7 +107,19 @@ functional-tests = 'python -m pytest tests/functional/ --color=yes -v' integration-tests = 'python -m pytest tests/integration/ --color=yes -v' notebook-tests = 'python -m pytest --nbmake docs/docs/tutorials/**/ --nbmake-timeout=1200 --color=yes -n auto -v' +# Same trees as above, but with the `slow` tests selected back in. `-m` on the +# command line overrides the `-m "not slow"` in the pyproject addopts. +unit-tests-all = 'pixi run unit-tests -m "slow or not slow"' +integration-tests-all = 'pixi run integration-tests -m "slow or not slow"' +# Only the expensive tests, across the whole tree. +slow-tests = 'python -m pytest tests/ -m slow --color=yes -v' + test = { depends-on = ['unit-tests'] } +test-all = { depends-on = [ + 'unit-tests-all', + 'functional-tests', + 'integration-tests-all', +] } ########### # ✔️ Checks @@ -156,9 +173,11 @@ raw-metrics-json = 'radon raw -s -j src/' # 📊 Coverage ############# -unit-tests-coverage = 'pixi run unit-tests --cov=src/easyreflectometry --cov-report=term-missing' +# Coverage always includes the slow tests: they cover the fitting paths that +# nothing else reaches, so leaving them out would understate the numbers. +unit-tests-coverage = 'pixi run unit-tests-all --cov=src/easyreflectometry --cov-report=term-missing' functional-tests-coverage = 'pixi run functional-tests --cov=src/easyreflectometry --cov-report=term-missing' -integration-tests-coverage = 'pixi run integration-tests --cov=src/easyreflectometry --cov-report=term-missing' +integration-tests-coverage = 'pixi run integration-tests-all --cov=src/easyreflectometry --cov-report=term-missing' docstring-coverage = 'interrogate -c pyproject.toml src/easyreflectometry' cov = { depends-on = [ diff --git a/pyproject.toml b/pyproject.toml index f646fd75..b000ceb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,8 @@ dependencies = [ 'scipp', 'refnx', 'refl1d>=1.0.0', - 'orsopy', + # >=1.2: model language resolve_to_blocks and nexus (.orb) I/O verified in 1.2.2 + 'orsopy>=1.2', 'svglib<1.6 ; platform_system=="Linux" or sys_platform == "darwin"', 'xhtml2pdf', 'bumps', @@ -37,6 +38,8 @@ dependencies = [ ] [project.optional-dependencies] +# Binary ORSO (.orb / NeXus) read and write support (orsopy uses h5py for it) +orb = ['h5py'] dev = [ 'GitPython', # Interact with Git repositories 'build', # Building the package @@ -166,8 +169,16 @@ fail_under = 0 # Minimum coverage percentage to pass # https://docs.pytest.org/en/stable/ [tool.pytest.ini_options] -addopts = '--import-mode=importlib' -markers = ['fast: mark test as fast (should be run on every push)'] +# `-m 'not slow'` makes the expensive tests opt-in: a plain `pytest` (and the +# `unit-tests` pixi task) runs the fast suite, while `-m` on the command line +# overrides this -- `pixi run slow-tests` for only the expensive ones, +# `pixi run unit-tests-all` for everything. CI runs the full set on pull +# requests, on master/develop, and on manual dispatch. +addopts = '--import-mode=importlib -m "not slow"' +markers = [ + 'fast: mark test as fast (should be run on every push)', + 'slow: expensive test (real minimizer run, sampler, long fit); deselected by default', +] testpaths = ['tests'] ######################## diff --git a/src/easyreflectometry/data/__init__.py b/src/easyreflectometry/data/__init__.py index e63470eb..4650b63d 100644 --- a/src/easyreflectometry/data/__init__.py +++ b/src/easyreflectometry/data/__init__.py @@ -3,18 +3,24 @@ from .data_store import DataSet1D from .data_store import ProjectData +from .measurement import dataset_from_datagroup from .measurement import load from .measurement import load_as_dataset from .measurement import merge_datagroups from .polarized import PolarizedDataSet +from .polarized import channel_from_orso_polarization from .polarized import detect_polarization_channel +from .polarized import detect_polarization_channels_per_dataset __all__ = [ 'load', 'load_as_dataset', + 'dataset_from_datagroup', 'merge_datagroups', 'ProjectData', 'DataSet1D', 'PolarizedDataSet', + 'channel_from_orso_polarization', 'detect_polarization_channel', + 'detect_polarization_channels_per_dataset', ] diff --git a/src/easyreflectometry/data/measurement.py b/src/easyreflectometry/data/measurement.py index 12117162..bd3c7432 100644 --- a/src/easyreflectometry/data/measurement.py +++ b/src/easyreflectometry/data/measurement.py @@ -3,6 +3,7 @@ import os +from typing import Optional from typing import TextIO from typing import Union @@ -10,40 +11,97 @@ import scipp as sc from easyreflectometry.data import DataSet1D +from easyreflectometry.orso_utils import is_orso_file from easyreflectometry.orso_utils import load_data_from_orso_file def load(fname: Union[TextIO, str]) -> sc.DataGroup: - """Load data from an ORSO .ort file. + """Load data from an ORSO file (.ort/.orb) or a plain text file. + + The discriminator is the ORSO banner line (or the HDF5 magic for binary + files), **not** the file extension: a file carrying the banner that fails + to parse raises instead of being silently re-read as plain text (which + would drop the entire header, including polarization). Parameters ---------- fname : Union[TextIO, str] The file to be read. + + Returns + ------- + sc.DataGroup + The loaded data. """ - try: + if is_orso_file(str(fname)): return load_data_from_orso_file(fname) - except (IndexError, ValueError): - return _load_txt(fname) + return _load_txt(fname) -def load_as_dataset(fname: Union[TextIO, str]) -> DataSet1D: - """Load data from an ORSO .ort file as a DataSet1D.""" - data_group = load(fname) - basename = os.path.splitext(os.path.basename(fname))[0] - data_name = 'R_' + basename - coords_name = 'Qz_' + basename - coords_name = list(data_group['coords'].keys())[0] if coords_name not in data_group['coords'] else coords_name - data_name = list(data_group['data'].keys())[0] if data_name not in data_group['data'] else data_name +def dataset_from_datagroup(data_group: sc.DataGroup, data_key: Optional[str] = None) -> DataSet1D: + """Build a DataSet1D from one dataset of a loaded DataGroup. + + The ORSO header (when present) is attached to the returned dataset as the + ``orso_header`` attribute (a plain dict), so exporters can reuse the + original ``data_source``/``reduction`` provenance. + + Parameters + ---------- + data_group : sc.DataGroup + A DataGroup as returned by :func:`load`. + data_key : Optional[str], optional + The data entry to use (e.g. ``'R_0'``). By default, the first entry. + + Returns + ------- + DataSet1D + The dataset. + """ + if data_key is None: + data_key = list(data_group['data'].keys())[0] + coords_key = 'Qz_' + data_key[len('R_') :] + if coords_key not in data_group['coords']: + coords_key = list(data_group['coords'].keys())[0] dataset = DataSet1D( - x=data_group['coords'][coords_name].values, - y=data_group['data'][data_name].values, - ye=data_group['data'][data_name].variances, - xe=data_group['coords'][coords_name].variances, + x=data_group['coords'][coords_key].values, + y=data_group['data'][data_key].values, + ye=data_group['data'][data_key].variances, + xe=data_group['coords'][coords_key].variances, ) + header = None + if 'attrs' in data_group and data_key in data_group['attrs']: + try: + header = data_group['attrs'][data_key]['orso_header'].values + except (KeyError, AttributeError): + header = None + dataset.orso_header = header return dataset +def load_as_dataset(fname: Union[TextIO, str], data_group: Optional[sc.DataGroup] = None) -> DataSet1D: + """Load data from an ORSO .ort file as a DataSet1D. + + Parameters + ---------- + fname : Union[TextIO, str] + The file to be read. + data_group : Optional[sc.DataGroup], optional + Pre-loaded DataGroup for *fname* (avoids re-parsing the file). + By default, None. + + Returns + ------- + DataSet1D + The (first) dataset in the file. + """ + if data_group is None: + data_group = load(fname) + basename = os.path.splitext(os.path.basename(fname))[0] + data_name = 'R_' + basename + data_name = list(data_group['data'].keys())[0] if data_name not in data_group['data'] else data_name + return dataset_from_datagroup(data_group, data_key=data_name) + + def extract_orso_title(data_group: sc.DataGroup, data_name: str) -> str | None: """Extract orso title.""" try: diff --git a/src/easyreflectometry/data/polarized.py b/src/easyreflectometry/data/polarized.py index ae8fcac8..75eaa39c 100644 --- a/src/easyreflectometry/data/polarized.py +++ b/src/easyreflectometry/data/polarized.py @@ -244,6 +244,60 @@ def detect_polarization_channel(path: str) -> Optional[PolarizationChannel]: return _channel_from_filename(path) +def channel_from_orso_polarization(polarization) -> Optional[PolarizationChannel]: + """Map an ORSO ``instrument_settings.polarization`` value to a spin channel. + + Parameters + ---------- + polarization : + The header value (orsopy ``Polarization`` enum, string, or None). + + Returns + ------- + Optional[PolarizationChannel] + The mapped channel, or None for absent/unmapped values (``po``, ``mo``, + ``op``, ``om``, ``unpolarized``, ``vector`` are deliberately unmapped). + """ + if polarization is None: + return None + value = getattr(polarization, 'value', polarization) + return _ORSO_POLARIZATION_TO_CHANNEL.get(str(value).lower()) + + +def _dataset_polarization(orso_dataset): + """The declared polarization of one parsed ORSO dataset, or None.""" + try: + return orso_dataset.info.data_source.measurement.instrument_settings.polarization + except AttributeError: + return None + + +def detect_polarization_channels_per_dataset( + orso_data, +) -> list[tuple[bool, Optional[PolarizationChannel]]]: + """Classify every dataset of a parsed ORSO file by its own header. + + Unlike :func:`detect_polarization_channel`, which reads only the first + dataset, this honours per-dataset ``polarization:`` overrides in + multi-dataset files. + + Parameters + ---------- + orso_data : list + Parsed ORSO dataset list (as returned by ``orso.load_orso``). + + Returns + ------- + list[tuple[bool, Optional[PolarizationChannel]]] + Per dataset: (header declares a polarization, mapped channel or None). + """ + result = [] + for orso_dataset in orso_data: + polarization = _dataset_polarization(orso_dataset) + result.append((polarization is not None, channel_from_orso_polarization(polarization))) + return result + + def _channel_from_orso_header(path: str) -> tuple[bool, Optional[PolarizationChannel]]: """Read the polarization of the first dataset in an ORSO file. @@ -254,16 +308,15 @@ def _channel_from_orso_header(path: str) -> tuple[bool, Optional[PolarizationCha False when the file is unreadable or carries no polarization field. """ try: - from orsopy.fileio import orso + from easyreflectometry.orso_utils import _load_orso_any - orso_data = orso.load_orso(str(path)) + orso_data = _load_orso_any(str(path)) polarization = orso_data[0].info.data_source.measurement.instrument_settings.polarization except Exception: return False, None if polarization is None: return False, None - value = getattr(polarization, 'value', polarization) - return True, _ORSO_POLARIZATION_TO_CHANNEL.get(str(value).lower()) + return True, channel_from_orso_polarization(polarization) def _channel_from_filename(path: str) -> Optional[PolarizationChannel]: diff --git a/src/easyreflectometry/model/model.py b/src/easyreflectometry/model/model.py index 4cb47142..22b35f3f 100644 --- a/src/easyreflectometry/model/model.py +++ b/src/easyreflectometry/model/model.py @@ -344,8 +344,23 @@ def as_dict(self, skip: Optional[list[str]] = None) -> dict: return self.to_dict(skip=skip) def as_orso(self) -> dict: - """Convert the model to a dictionary suitable for ORSO.""" - return self.as_dict() + """The sample as an ORSO simple-model (``sample.model``) dictionary. + + Slab representation: lengths in angstrom, SLDs in 1/angstrom^2, + repeating multilayers via the inline ``N ( ... )`` stack syntax. + + Returns + ------- + dict + The ORSO model-language dictionary (the content of an .ort file's + ``data_source.sample.model`` section). + """ + # Circular import if hoisted to module-top. + from orsopy.fileio import Header + + from easyreflectometry.orso_utils import sample_to_orso_model + + return Header.asdict(sample_to_orso_model(self.sample)) @classmethod def from_dict(cls, passed_dict: dict) -> Model: diff --git a/src/easyreflectometry/model/resolution_functions.py b/src/easyreflectometry/model/resolution_functions.py index 9579ad66..646556f6 100644 --- a/src/easyreflectometry/model/resolution_functions.py +++ b/src/easyreflectometry/model/resolution_functions.py @@ -119,6 +119,12 @@ class Pointwise(ResolutionFunction): widths from the ``[Qz, R, sQz]`` triple loaded from a data file; the returned widths are consumed by the calculators (refnx ``x_err`` / refl1d ``dq``), which perform the actual convolution against the model. + + Serialization contract: ``as_dict``/``from_dict`` store ``sQz_data_points`` + as **variances** (sigma squared). This is deliberately unchanged by the + ORSO ``value_is: FWHM`` support — FWHM columns are converted to sigma at + load time, so stored values are always sigma squared and saved projects + round-trip without migration. """ def __init__(self, q_data_points: List[np.ndarray]): diff --git a/src/easyreflectometry/orso_utils.py b/src/easyreflectometry/orso_utils.py index 15cb717e..75984abc 100644 --- a/src/easyreflectometry/orso_utils.py +++ b/src/easyreflectometry/orso_utils.py @@ -1,19 +1,45 @@ # SPDX-FileCopyrightText: 2025 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +"""ORSO file support: reading and writing `.ort` (text) and `.orb` (binary) files. + +Reading is built on ``orsopy.fileio``. Data columns follow the ORSO +specification: the first four columns are Qz, R, sR, sQz (fixed order); sR and +sQz default to sigma but a ``value_is: FWHM`` declaration is honoured and +converted to sigma on load. Q values declared in ``1/nm`` are converted to the +internal ``1/angstrom`` unit. Errors are stored as **variances** on the scipp +arrays (sigma squared), which is also the convention used by +:class:`~easyreflectometry.model.resolution_functions.Pointwise` and by project +serialization (``sQz_data_points``); any exporter must convert back to sigma. + +The ORSO "simple model" language is resolved with units honoured (the language +default length unit is **nm**; internal thicknesses/roughnesses are angstrom) +and with sub-stack repetitions mapped to +:class:`~easyreflectometry.sample.assemblies.repeating_multilayer.RepeatingMultilayer`. +""" + import logging import warnings +from typing import List +from typing import Optional +from typing import Union import numpy as np import scipp as sc from orsopy.fileio import Header from orsopy.fileio import model_language from orsopy.fileio import orso +from orsopy.fileio.base import Column from orsopy.fileio.base import ComplexValue +from orsopy.fileio.base import ErrorColumn +from orsopy.fileio.base import Value +from orsopy.fileio.orso import Orso +from orsopy.fileio.orso import OrsoDataset from easyreflectometry.data import DataSet1D from .sample.assemblies.multilayer import Multilayer +from .sample.assemblies.repeating_multilayer import RepeatingMultilayer from .sample.collections.sample import Sample from .sample.elements.layers.layer import Layer from .sample.elements.materials.material import Material @@ -22,9 +48,115 @@ # Set up logging logger = logging.getLogger(__name__) +# Mirrors resolution_functions.SIGMA_TO_FWHM; kept local to avoid importing the +# model package from this low-level module (data <-> model import cycle). +SIGMA_TO_FWHM = 2 * np.sqrt(2 * np.log(2)) + +# The mandatory first line of a text ORSO file; the discriminator for the +# ORSO-vs-plain-text decision (the file extension is not reliable). +ORSO_BANNER = 'ORSO reflectivity data file' + +# Magic bytes of an HDF5 container -- how a binary ORSO (.orb, NeXus) file starts. +_HDF5_MAGIC = b'\x89HDF\r\n\x1a\n' + +# Length unit -> angstrom conversion factors for model-language values. +_LENGTH_UNIT_TO_ANGSTROM = { + None: 1.0, + 'angstrom': 1.0, + 'A': 1.0, + 'nm': 10.0, + 'um': 1.0e4, + 'mm': 1.0e7, +} + +# SLD unit -> 1/angstrom^2 conversion factors. +_SLD_UNIT_TO_INV_ANGSTROM_SQUARED = { + None: 1.0, + '1/angstrom^2': 1.0, + '1/nm^2': 1.0e-2, +} + +# Qz column unit -> 1/angstrom conversion factors. +_Q_UNIT_TO_INV_ANGSTROM = { + None: 1.0, + '1/angstrom': 1.0, + '1/nm': 0.1, +} + +# Mass density unit -> g/cm^3 conversion factors. +_MASS_DENSITY_UNIT_TO_G_CM3 = { + None: 1.0, + 'g/cm^3': 1.0, + 'g/ml': 1.0, + 'kg/m^3': 1.0e-3, +} + + +def is_orso_file(fname: str) -> bool: + """Whether *fname* is an ORSO file: text with the ORSO banner line, or HDF5 (.orb). + + Parameters + ---------- + fname : str + Path to the file. + + Returns + ------- + bool + True when the file starts with the ORSO banner or the HDF5 magic bytes. + """ + try: + with open(fname, 'rb') as f: + head = f.read(128) + except OSError: + return False + if head.startswith(_HDF5_MAGIC): + return True + try: + first_line = head.decode('utf-8', errors='replace').splitlines()[0] + except IndexError: + return False + return first_line.lstrip().startswith('#') and ORSO_BANNER in first_line + + +def _is_binary_orso(fname: str) -> bool: + """Whether *fname* is a binary (HDF5 / .orb) ORSO file.""" + try: + with open(fname, 'rb') as f: + return f.read(8).startswith(_HDF5_MAGIC) + except OSError: + return False + + +def _load_orso_any(fname: str) -> List[OrsoDataset]: + """Parse an ORSO file, text (`.ort`) or binary (`.orb`), into OrsoDataset objects. + + Parameters + ---------- + fname : str + Path to the file. + + Returns + ------- + List[OrsoDataset] + The parsed datasets. + + Raises + ------ + ValueError : + If the file cannot be parsed as ORSO (the original error is chained). + """ + fname = str(fname) + try: + if _is_binary_orso(fname): + return orso.load_nexus(fname) + return orso.load_orso(fname) + except Exception as e: + raise ValueError(f'Error loading ORSO file {fname!r}: {e}') from e + def LoadOrso(orso_data): - """Load a model from an ORSO file.""" + """Load a model and data from an ORSO file (path or pre-parsed datasets).""" orso_obj = _coerce_orso_object(orso_data) sample = load_orso_model(orso_obj) @@ -39,28 +171,252 @@ def _coerce_orso_object(orso_input): return orso_input except (TypeError, IndexError): pass - return orso.load_orso(orso_input) + return _load_orso_any(orso_input) def load_data_from_orso_file(fname: str) -> sc.DataGroup: - """Load data from an ORSO file.""" - try: - orso_data = orso.load_orso(fname) - except Exception as e: - raise ValueError(f'Error loading ORSO file: {e}') + """Load data from an ORSO file (`.ort` text or `.orb` binary). + + Parameters + ---------- + fname : str + Path to the file. + + Returns + ------- + sc.DataGroup + A scipp DataGroup with data, coords, and attrs. + + Raises + ------ + ValueError : + If the file cannot be parsed as ORSO. Parse failures are **not** + swallowed here; falling back to plain-text loading is the caller's + decision and only valid when the file carries no ORSO banner. + """ + orso_data = _load_orso_any(fname) return load_orso_data(orso_data) +def _orso_dataset_key(o, index: int) -> Union[str, int]: + """The name a dataset is stored under: its ``data_set`` label or its index.""" + if o.info.data_set is not None: + return o.info.data_set + return index + + +def _validate_columns(columns, dataset_label) -> None: + """Warn when the leading four columns do not follow the ORSO layout. + + The spec fixes the order Qz, R, sR, sQz; data is read by position, so a + file with different columns is very likely misread. The names in + ``info.columns`` are used as validation only. + """ + expected = ('Qz', 'R', 'R', 'Qz') + for position, expected_name in enumerate(expected): + if position >= len(columns): + warnings.warn( + f'ORSO dataset {dataset_label!r} declares only {len(columns)} columns; the specification ' + f'requires Qz, R, sR, sQz (nan-filled when unknown). Missing error columns are treated as absent.', + UserWarning, + stacklevel=3, + ) + return + column = columns[position] + name = getattr(column, 'error_of', None) or getattr(column, 'name', None) + # Legacy (0.1 standard) files use plain columns named 'sR'/'sQz'. + if position >= 2 and isinstance(name, str) and name.startswith('s'): + name = name[1:] + if name != expected_name: + warnings.warn( + f'ORSO dataset {dataset_label!r} columns do not follow the specified order ' + f'(Qz, R, sR, sQz): column {position} is {name!r}. Data is read by position ' + f'and may be misinterpreted.', + UserWarning, + stacklevel=3, + ) + return + + +def _q_unit_scale(column, dataset_label) -> float: + """Conversion factor from the Qz column unit to the internal 1/angstrom.""" + unit = getattr(column, 'unit', None) + try: + return _Q_UNIT_TO_INV_ANGSTROM[unit] + except KeyError: + warnings.warn( + f'ORSO dataset {dataset_label!r} declares Qz unit {unit!r}; expected 1/angstrom or 1/nm. ' + f'Values are used as-is (assumed 1/angstrom).', + UserWarning, + stacklevel=3, + ) + return 1.0 + + +def _error_column_sigma(o, position: int) -> Optional[np.ndarray]: + """Extract an error column as sigma values, or None when the column is absent. + + Honours ``value_is: FWHM`` by converting to sigma (dividing by 2.3548...). + """ + if o.data.ndim != 2 or o.data.shape[1] <= position: + return None + values = np.asarray(o.data[:, position], dtype=float) + if position < len(o.info.columns): + value_is = getattr(o.info.columns[position], 'value_is', None) + if value_is == 'FWHM': + values = values / SIGMA_TO_FWHM + return values + + +def _clean_sqz(sqz: Optional[np.ndarray], dataset_label) -> Optional[np.ndarray]: + """Apply the nan policy to the sQz column. + + All-nan (spec-valid "resolution unknown") returns None so that no q-variance + is stored and the caller falls back to the default percentage smearing. + Partial nan (common at the low/high-Q extremes of real reductions) is filled + by interpolating sigma over the valid points -- otherwise a single nan would + propagate through ``np.interp`` in ``Pointwise`` across the whole range. + """ + if sqz is None: + return None + nan_mask = np.isnan(sqz) + if not nan_mask.any(): + return sqz + if nan_mask.all(): + return None + warnings.warn( + f'ORSO dataset {dataset_label!r}: {nan_mask.sum()} of {sqz.size} sQz values are nan; ' + f'they are filled by interpolating the resolution over the valid points.', + UserWarning, + stacklevel=3, + ) + valid = ~nan_mask + indices = np.arange(sqz.size) + filled = sqz.copy() + filled[nan_mask] = np.interp(indices[nan_mask], indices[valid], sqz[valid]) + return filled + + +def _clean_sr(sr: Optional[np.ndarray], dataset_label) -> Optional[np.ndarray]: + """Apply the nan policy to the sR column. + + All-nan returns None (uncertainty unknown). Partial nan is kept as-is -- + interpolating measurement uncertainties would fabricate fit weights -- but + is warned about, since nan weights degrade fitting. + """ + if sr is None: + return None + nan_mask = np.isnan(sr) + if nan_mask.all() and sr.size: + return None + if nan_mask.any(): + warnings.warn( + f'ORSO dataset {dataset_label!r}: {nan_mask.sum()} of {sr.size} sR values are nan; ' + f'these points carry no uncertainty and will degrade fit weighting.', + UserWarning, + stacklevel=3, + ) + return sr + + +def load_orso_data(orso_data) -> sc.DataGroup: + """Convert parsed ORSO dataset objects into a scipp DataGroup. + + Q values are converted to 1/angstrom; sR/sQz are converted to sigma when + declared as FWHM and stored as variances; nan-filled error columns follow + the policy documented on the cleaning helpers. + + Parameters + ---------- + orso_data : list + Parsed ORSO dataset list (as returned by ``orso.load_orso``). + + Returns + ------- + sc.DataGroup + A scipp DataGroup with data, coords, and attrs. + """ + data = {} + coords = {} + attrs = {} + for i, o in enumerate(orso_data): + name = _orso_dataset_key(o, i) + _validate_columns(o.info.columns, name) + + q_scale = _q_unit_scale(o.info.columns[0], name) + qz = np.asarray(o.data[:, 0], dtype=float) * q_scale + reflectivity = np.asarray(o.data[:, 1], dtype=float) + + sr = _clean_sr(_error_column_sigma(o, 2), name) + sqz = _clean_sqz(_error_column_sigma(o, 3), name) + if sqz is not None: + sqz = sqz * q_scale + + dims = [f'{o.info.columns[0].name}_{name}'] + coords[f'Qz_{name}'] = sc.array( + dims=dims, + values=qz, + variances=np.square(sqz) if sqz is not None else None, + unit=sc.Unit('1/angstrom'), + ) + r_unit = getattr(o.info.columns[1], 'unit', None) + try: + data[f'R_{name}'] = sc.array( + dims=dims, + values=reflectivity, + variances=np.square(sr) if sr is not None else None, + unit=sc.Unit(r_unit) if r_unit is not None else None, + ) + except TypeError: + data[f'R_{name}'] = sc.array( + dims=dims, + values=reflectivity, + variances=np.square(sr) if sr is not None else None, + ) + attrs[f'R_{name}'] = {'orso_header': sc.scalar(Header.asdict(o.info))} + data_group = sc.DataGroup(data=data, coords=coords, attrs=attrs) + return data_group + + +# --------------------------------------------------------------------------- +# Model language -> Sample +# --------------------------------------------------------------------------- + + +def _length_to_angstrom(value) -> float: + """Convert a model-language length (Value with unit, or bare number) to angstrom.""" + if value is None: + return 0.0 + magnitude = getattr(value, 'magnitude', value) + if magnitude is None: + return 0.0 + unit = getattr(value, 'unit', None) + try: + factor = _LENGTH_UNIT_TO_ANGSTROM[unit] + except KeyError: + warnings.warn( + f'Unknown ORSO length unit {unit!r}; value used as angstrom.', + UserWarning, + stacklevel=4, + ) + factor = 1.0 + return float(magnitude) * factor + + def load_orso_model(orso_data) -> Sample: """Load a model from an ORSO file and return a Sample object. - The ORSO file .ort contains information about the sample, saved - as a simple "stack" string, e.g. 'air | m1 | SiO2 | Si'. - This gets parsed by the ORSO library and converted into an ORSO Dataset object. + The **original** ``sample.model`` object is resolved (keeping ``globals``, + ``materials``, ``sub_stacks`` and ``composits``), so declared units -- + including the model-language default length unit of **nm** -- are honoured + and converted to the internal angstrom. Sub-stacks with repetitions map to + :class:`RepeatingMultilayer`; density-defined materials stay density-defined + (:class:`MaterialDensity`) instead of being flattened to a numeric SLD. The stack is converted to a proper Sample structure: - First layer -> Superphase assembly (thickness=0, roughness=0, both fixed) - - Middle layers -> 'Loaded layer' Multilayer assembly (parameters enabled) + - Middle layers -> 'Loaded layer' Multilayer assembly (parameters enabled), + with repeated sub-stacks as RepeatingMultilayer assemblies - Last layer -> Subphase assembly (thickness=0 fixed, roughness enabled) Parameters @@ -78,7 +434,6 @@ def load_orso_model(orso_data) -> Sample: Sample An EasyReflectometry Sample object. """ - # Extract stack string and layer definitions from ORSO sample model sample_model = orso_data[0].info.data_source.sample.model if sample_model is None: warnings.warn( @@ -105,29 +460,39 @@ def load_orso_model(orso_data) -> Sample: globals=getattr(sample_model, 'globals', None), ) - # Try to resolve layers using different methods - try: - orso_layers = orso_sample.resolve_to_layers() - except ValueError: - orso_layers = orso_sample.resolve_stack() + # Resolve the original model (globals/materials/sub_stacks intact) at the + # stack level: resolve_stack() keeps SubStack objects (and with them the + # repetition counts) that resolve_to_layers()/resolve_to_blocks() flatten. + orso_blocks = orso_sample.resolve_stack() # Handle case where layers are not resolved correctly - if not orso_layers: + if not orso_blocks: raise ValueError('Could not resolve ORSO layers.') - if len(orso_layers) < 2: + # Plain layers still need their material resolved (what + # resolve_to_layers() would have done); a failure is warned about instead + # of silently re-resolving the whole stack a different way. + for block in orso_blocks: + if isinstance(block, model_language.Layer): + _generate_layer_material(block) + + # The ambient (first) and substrate (last) entries must be plain layers; + # flatten pathological edge sub-stacks. + if not isinstance(orso_blocks[0], model_language.Layer): + warnings.warn('First ORSO stack item is a sub-stack; its layers are used directly.', UserWarning, stacklevel=2) + orso_blocks = list(orso_blocks[0].resolve_to_layers()) + orso_blocks[1:] + if not isinstance(orso_blocks[-1], model_language.Layer): + warnings.warn('Last ORSO stack item is a sub-stack; its layers are used directly.', UserWarning, stacklevel=2) + orso_blocks = orso_blocks[:-1] + list(orso_blocks[-1].resolve_to_layers()) + + total_layers = sum(1 if isinstance(block, model_language.Layer) else max(len(block.sequence), 1) for block in orso_blocks) + if total_layers < 2: raise ValueError('ORSO stack must contain at least 2 layers (superphase and subphase).') - logger.debug(f'Resolved layers: {orso_layers}') - - # Convert ORSO layers to EasyReflectometry layers - erl_layers = [] - for layer in orso_layers: - erl_layer = _convert_orso_layer_to_erl(layer) - erl_layers.append(erl_layer) + logger.debug(f'Resolved blocks: {orso_blocks}') # Create Superphase from first layer (thickness=0, roughness=0, both fixed) - superphase_layer = erl_layers[0] + superphase_layer = _convert_orso_layer_to_erl(orso_blocks[0]) superphase_layer.thickness.value = 0.0 superphase_layer.roughness.value = 0.0 superphase_layer.thickness.fixed = True @@ -135,51 +500,139 @@ def load_orso_model(orso_data) -> Sample: superphase = Multilayer(superphase_layer, name='Superphase') # Create Subphase from last layer (thickness=0 fixed, roughness enabled) - subphase_layer = erl_layers[-1] + subphase_layer = _convert_orso_layer_to_erl(orso_blocks[-1]) subphase_layer.thickness.value = 0.0 subphase_layer.thickness.fixed = True subphase_layer.roughness.fixed = False subphase = Multilayer(subphase_layer, name='Subphase') + # Middle blocks: consecutive plain layers group into one Multilayer; + # sub-stacks become their own (repeating) assemblies. + middle_assemblies = [] + pending_layers = [] + + def flush_pending(): + if pending_layers: + middle_assemblies.append(Multilayer(list(pending_layers), name='Loaded layer')) + pending_layers.clear() + + for block in orso_blocks[1:-1]: + if isinstance(block, model_language.Layer): + pending_layers.append(_convert_orso_layer_to_erl(block)) + else: + flush_pending() + middle_assemblies.append(_convert_orso_substack_to_erl(block)) + flush_pending() + + # Keep the historic single-group name; disambiguate multiple plain groups. + plain_groups = [a for a in middle_assemblies if a.name == 'Loaded layer'] + if len(plain_groups) > 1: + for k, assembly in enumerate(plain_groups): + assembly.name = f'Loaded layer {k}' + # Create Sample from the file sample_info = orso_data[0].info.data_source.sample sample_name = sample_info.name if sample_info.name else 'ORSO Sample' - # Build Sample based on number of layers - if len(erl_layers) == 2: - # Only superphase and subphase, no middle layers - sample = Sample(superphase, subphase, name=sample_name) - else: - # Create middle layer assembly from layers between first and last - middle_layers = erl_layers[1:-1] - loaded_layer = Multilayer(middle_layers, name='Loaded layer') - sample = Sample(superphase, loaded_layer, subphase, name=sample_name) - + sample = Sample(superphase, *middle_assemblies, subphase, name=sample_name) return sample +def _generate_layer_material(orso_layer) -> None: + """Resolve a model-language layer's material in place (formula -> SLD). + + This is the per-layer half of orsopy's ``resolve_to_layers()``; a failure + is warned about (the material then imports with its declared values, or an + SLD of 0) instead of silently re-resolving the stack a different way. + """ + try: + if orso_layer.material is None: + orso_layer.generate_material() + orso_layer.material.generate_density() + except Exception as e: + warnings.warn( + f'Could not resolve material for ORSO layer {getattr(orso_layer, "original_name", None) or orso_layer!r}: {e}', + UserWarning, + stacklevel=3, + ) + + +def _convert_orso_substack_to_erl(block) -> Multilayer: + """Convert an ORSO SubStack block into a (repeating) multilayer assembly.""" + repetitions = int(getattr(block, 'repetitions', 1) or 1) + # SubStack.resolve_to_layers() returns layers * repetitions; resolve one + # period by temporarily neutralizing the repetition count. + original_repetitions = block.repetitions + try: + block.repetitions = 1 + period = block.resolve_to_layers() + except Exception as e: + warnings.warn( + f'Could not fully resolve ORSO sub-stack ({e}); using its raw layer sequence.', + UserWarning, + stacklevel=3, + ) + period = [item for item in block.sequence if isinstance(item, model_language.Layer)] + finally: + block.repetitions = original_repetitions + erl_layers = [_convert_orso_layer_to_erl(orso_layer) for orso_layer in period] + name = getattr(block, 'original_name', None) or 'Multilayer' + if repetitions > 1: + return RepeatingMultilayer(erl_layers, repetitions=repetitions, name=name) + return Multilayer(erl_layers, name=name) + + def _convert_orso_layer_to_erl(layer): - r"""Helper function to convert an ORSO layer to an EasyReflectometry laye.""" + r"""Helper function to convert an ORSO layer to an EasyReflectometry layer.""" material = layer.material # Prefer original_name for the material name, fall back to the formula; a # material defined only by its SLD has neither, so never leave it None. - m_name = layer.original_name if layer.original_name is not None else material.formula + formula = getattr(material, 'formula', None) + m_name = layer.original_name if layer.original_name is not None else formula if m_name is None: m_name = 'material' - # Get SLD values (use formula for density calculation if available) - formula_for_calc = material.formula if material.formula is not None else m_name - m_sld, m_isld = _get_sld_values(material, formula_for_calc) + erl_material = _convert_orso_material_to_erl(material, m_name) - # Create and return ERL layer + # Create and return ERL layer; lengths honour the declared unit (nm default). return Layer( - material=Material(sld=m_sld, isld=m_isld, name=m_name), - thickness=layer.thickness.magnitude if layer.thickness is not None else 0.0, - roughness=layer.roughness.magnitude if layer.roughness is not None else 0.0, + material=erl_material, + thickness=_length_to_angstrom(layer.thickness), + roughness=_length_to_angstrom(layer.roughness), name=layer.original_name if layer.original_name is not None else m_name, ) +def _convert_orso_material_to_erl(material, material_name): + """Convert an ORSO material to an ERL material. + + Density-defined materials (formula + mass density, no SLD) stay + density-defined as :class:`MaterialDensity`, so formula and density remain + editable/recoverable instead of being flattened to a numeric SLD. + """ + formula = getattr(material, 'formula', None) + mass_density = getattr(material, 'mass_density', None) + if material.sld is None and mass_density is not None and (formula or material_name): + magnitude = getattr(mass_density, 'magnitude', mass_density) + unit = getattr(mass_density, 'unit', None) + try: + factor = _MASS_DENSITY_UNIT_TO_G_CM3[unit] + except KeyError: + warnings.warn( + f'Unknown ORSO mass density unit {unit!r}; value used as g/cm^3.', + UserWarning, + stacklevel=4, + ) + factor = 1.0 + return MaterialDensity( + chemical_structure=formula if formula is not None else material_name, + density=float(magnitude) * factor, + name=material_name if material_name is not None else formula, + ) + m_sld, m_isld = _get_sld_values(material, material_name) + return Material(sld=m_sld, isld=m_isld, name=material_name) + + def _get_sld_values(material, material_name): """Extract SLD values from material, calculating from density if needed @@ -190,7 +643,7 @@ def _get_sld_values(material, material_name): if material.sld is None and material.mass_density is not None: # Calculate SLD from mass density # MaterialDensity already returns values in 10^-6 A^-2 units - m_density = material.mass_density.magnitude + m_density = getattr(material.mass_density, 'magnitude', material.mass_density) density = MaterialDensity(chemical_structure=material_name, density=m_density) m_sld = density.sld.value m_isld = density.isld.value @@ -199,16 +652,26 @@ def _get_sld_values(material, material_name): m_sld = 0.0 m_isld = 0.0 else: - # ORSO stores SLD in absolute units (A^-2) - # Convert to internal representation (10^-6 A^-2) by multiplying by 1e6 - if isinstance(material.sld, ComplexValue): - raw_sld = material.sld.real - m_sld = raw_sld * 1e6 - m_isld = (material.sld.imag or 0.0) * 1e6 + # ORSO stores SLD in absolute units (A^-2, or 1/nm^2 when declared). + # Convert to internal representation (10^-6 A^-2). + sld = material.sld + unit = getattr(sld, 'unit', None) + try: + unit_factor = _SLD_UNIT_TO_INV_ANGSTROM_SQUARED[unit] + except KeyError: + warnings.warn( + f'Unknown ORSO SLD unit {unit!r}; value used as 1/angstrom^2.', + UserWarning, + stacklevel=4, + ) + unit_factor = 1.0 + if isinstance(sld, ComplexValue): + raw_sld = sld.real + m_sld = raw_sld * unit_factor * 1e6 + m_isld = (sld.imag if sld.imag is not None else 0.0) * unit_factor * 1e6 else: - # A plain number, or an orsopy ``Value`` (unwrap its magnitude). - raw_sld = getattr(material.sld, 'magnitude', material.sld) - m_sld = raw_sld * 1e6 + raw_sld = getattr(sld, 'magnitude', sld) + m_sld = raw_sld * unit_factor * 1e6 m_isld = 0.0 if raw_sld != 0.0 and abs(raw_sld) > 1e-2: warnings.warn( @@ -222,45 +685,207 @@ def _get_sld_values(material, material_name): return m_sld, m_isld -def load_orso_data(orso_data) -> DataSet1D: - """Convert parsed ORSO dataset objects into a scipp DataGroup. +# --------------------------------------------------------------------------- +# Sample -> model language, and .ort/.orb export +# --------------------------------------------------------------------------- + + +def _sanitize_model_key(name: str) -> str: + """Make a layer name safe for use in the model-language stack string.""" + cleaned = ''.join('_' if character in '|()' else character for character in str(name)).strip() + return cleaned or 'layer' + + +def _unique_model_key(existing: dict, name: str) -> str: + """A stack key not yet present in *existing* (appends a counter if needed).""" + key = _sanitize_model_key(name) + if key not in existing: + return key + counter = 2 + while f'{key}_{counter}' in existing: + counter += 1 + return f'{key}_{counter}' + + +def _material_to_orso(material) -> model_language.Material: + """Convert an ERL material to an ORSO model-language material.""" + if isinstance(material, MaterialDensity): + return model_language.Material( + formula=material.chemical_structure, + mass_density=Value(float(material.density.value), 'g/cm^3'), + ) + return model_language.Material( + sld=ComplexValue( + real=float(material.sld.value) * 1e-6, + imag=float(material.isld.value) * 1e-6, + unit='1/angstrom^2', + ) + ) + + +def sample_to_orso_model(sample: Sample) -> model_language.SampleModel: + """Convert an ERL Sample into an ORSO simple-model ``SampleModel`` (slab model). + + Lengths are written in angstrom (declared via ``globals.length_unit``), SLDs + in 1/angstrom^2. :class:`RepeatingMultilayer` assemblies are written with + the inline repetition syntax ``N ( layer1 | layer2 )``. Parameters ---------- - orso_data : list - Parsed ORSO dataset list (as returned by ``orso.load_orso``). + sample : Sample + The sample to convert. Returns ------- - sc.DataGroup - A scipp DataGroup with data, coords, and attrs. + model_language.SampleModel + The ORSO model-language representation. """ - data = {} - coords = {} - attrs = {} - for i, o in enumerate(orso_data): - name = i - if o.info.data_set is not None: - name = o.info.data_set - coords[f'Qz_{name}'] = sc.array( - dims=[f'{o.info.columns[0].name}_{name}'], - values=o.data[:, 0], - variances=np.square(o.data[:, 3]), - unit=sc.Unit(o.info.columns[0].unit), + layers = {} + stack_parts = [] + + def add_layer(erl_layer) -> str: + key = _unique_model_key(layers, erl_layer.name or erl_layer.material.name) + layers[key] = model_language.Layer( + thickness=Value(float(erl_layer.thickness.value), 'angstrom'), + roughness=Value(float(erl_layer.roughness.value), 'angstrom'), + material=_material_to_orso(erl_layer.material), ) + return key + + for assembly in sample: + keys = [add_layer(erl_layer) for erl_layer in assembly.layers] + if isinstance(assembly, RepeatingMultilayer) and int(assembly.repetitions.value) > 1: + stack_parts.append(f'{int(assembly.repetitions.value)} ( ' + ' | '.join(keys) + ' )') + else: + stack_parts.extend(keys) + + return model_language.SampleModel( + stack=' | '.join(stack_parts), + layers=layers, + globals=model_language.ModelParameters(length_unit='angstrom', sld_unit='1/angstrom^2'), + ) + + +_STANDARD_COLUMNS = [ + Column(name='Qz', unit='1/angstrom', physical_quantity='wavevector transfer'), + Column(name='R', physical_quantity='reflectivity'), + ErrorColumn(error_of='R', error_type='uncertainty', value_is='sigma'), + ErrorColumn(error_of='Qz', error_type='resolution', value_is='sigma'), +] + + +def _orso_header_for_dataset(dataset: DataSet1D, model=None) -> Orso: + """Build an Orso header for one dataset, reusing a preserved header when present. + + A header captured at load time (``dataset.orso_header``) keeps + ``data_source``/``reduction`` provenance; otherwise a minimal header is + synthesized. Columns are always (re)set to the standard four with the + sigma convention, matching the exported data. + """ + import copy as _copy + + header_dict = getattr(dataset, 'orso_header', None) + if header_dict: try: - data[f'R_{name}'] = sc.array( - dims=[f'{o.info.columns[0].name}_{name}'], - values=o.data[:, 1], - variances=np.square(o.data[:, 2]), - unit=sc.Unit(o.info.columns[1].unit), - ) - except TypeError: - data[f'R_{name}'] = sc.array( - dims=[f'{o.info.columns[0].name}_{name}'], - values=o.data[:, 1], - variances=np.square(o.data[:, 2]), - ) - attrs[f'R_{name}'] = {'orso_header': sc.scalar(Header.asdict(o.info))} - data_group = sc.DataGroup(data=data, coords=coords, attrs=attrs) - return data_group + info = Orso.from_dict(_copy.deepcopy(dict(header_dict))) + except Exception: + info = Orso.empty() + else: + info = Orso.empty() + info.columns = _copy.deepcopy(_STANDARD_COLUMNS) + if model is not None and getattr(model, 'sample', None) is not None: + info.data_source.sample.model = sample_to_orso_model(model.sample) + if info.data_source.sample.name is None: + info.data_source.sample.name = model.sample.name + return info + + +def _dataset_to_data_array(dataset: DataSet1D) -> np.ndarray: + """Assemble the Qz/R/sR/sQz array for export. + + ``DataSet1D`` stores **variances** in ``ye``/``xe``; ORSO columns are sigma, + so the square root is taken here. Absent errors are written as nan + (the spec's "unknown" marker), never as zeros. + """ + x = np.asarray(dataset.x, dtype=float) + y = np.asarray(dataset.y, dtype=float) + + def sigma_column(variances) -> np.ndarray: + if variances is None: + return np.full_like(x, np.nan) + variances = np.asarray(variances, dtype=float) + if variances.size != x.size or not np.any(np.nan_to_num(variances) > 0): + return np.full_like(x, np.nan) + return np.sqrt(variances) + + return np.column_stack([x, y, sigma_column(dataset.ye), sigma_column(dataset.xe)]) + + +def orso_datasets_from_experiment(experiment, model=None) -> List[OrsoDataset]: + """Convert an experiment into a list of ``OrsoDataset`` objects. + + A plain :class:`DataSet1D` becomes one dataset. A ``PolarizedDataSet`` + becomes one dataset per spin channel in a single multi-dataset file -- + the format's intended packing for spin states -- with ``data_set`` labels + and per-dataset ``instrument_settings.polarization`` set to the channel. + + Parameters + ---------- + experiment : + A DataSet1D or PolarizedDataSet. + model : optional + The model whose sample is written into ``sample.model``. Defaults to + ``experiment.model``. + + Returns + ------- + List[OrsoDataset] + The datasets ready for ``save_orso``/``save_nexus``. + """ + from orsopy.fileio.data_source import InstrumentSettings + from orsopy.fileio.data_source import Polarization + + from easyreflectometry.data.polarized import PolarizedDataSet + + if model is None: + model = getattr(experiment, 'model', None) + + datasets = [] + if isinstance(experiment, PolarizedDataSet): + for channel, channel_dataset in experiment.channels.items(): + info = _orso_header_for_dataset(channel_dataset, model) + info.data_set = channel.value + measurement = info.data_source.measurement + if measurement.instrument_settings is None: + measurement.instrument_settings = InstrumentSettings(incident_angle=None, wavelength=None) + measurement.instrument_settings.polarization = Polarization(channel.value) + datasets.append(OrsoDataset(info=info, data=_dataset_to_data_array(channel_dataset))) + else: + info = _orso_header_for_dataset(experiment, model) + if info.data_set is None: + info.data_set = 0 + datasets.append(OrsoDataset(info=info, data=_dataset_to_data_array(experiment))) + return datasets + + +def save_orso_experiment(experiment, fname: str, model=None) -> None: + """Write an experiment to an ORSO file (`.ort` text, or `.orb` binary). + + Parameters + ---------- + experiment : + A DataSet1D or PolarizedDataSet (the latter is written as one file with + one ``data_set`` block per spin channel). + fname : str + Destination path; a ``.orb`` extension selects the binary (NeXus/HDF5) + representation, anything else the text one. + model : optional + The model whose sample is exported as the ORSO ``sample.model``. + Defaults to the experiment's model. + """ + fname = str(fname) + datasets = orso_datasets_from_experiment(experiment, model=model) + if fname.lower().endswith('.orb'): + orso.save_nexus(datasets, fname) + else: + orso.save_orso(datasets, fname) diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index e5250a91..21ef26ee 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -30,8 +30,11 @@ from easyreflectometry.data import DataSet1D from easyreflectometry.data import PolarizedDataSet from easyreflectometry.data import detect_polarization_channel +from easyreflectometry.data import detect_polarization_channels_per_dataset from easyreflectometry.data import load_as_dataset +from easyreflectometry.data.measurement import dataset_from_datagroup from easyreflectometry.data.measurement import extract_orso_title +from easyreflectometry.data.measurement import load as load_measurement_file from easyreflectometry.data.measurement import load_data_from_orso_file from easyreflectometry.fitting import MultiFitter from easyreflectometry.inequality_constraints import InequalityEvaluation @@ -44,6 +47,8 @@ from easyreflectometry.model import ModelCollection from easyreflectometry.model import PercentageFwhm from easyreflectometry.model import Pointwise +from easyreflectometry.orso_utils import is_orso_file +from easyreflectometry.orso_utils import save_orso_experiment from easyreflectometry.sample import Layer from easyreflectometry.sample import Material from easyreflectometry.sample import MaterialCollection @@ -774,22 +779,36 @@ def get_index_d2o(self) -> int: return [material.name for material in self._materials].index('D2O') def load_orso_file(self, path: Union[Path, str]) -> None: - """Load an ORSO file and optionally create a model and a data from it.""" - from easyreflectometry.orso_utils import LoadOrso - - model, data = LoadOrso(path) - if model is not None: - if isinstance(model, Sample): - model = Model(sample=model, name=model.name) - self.models = ModelCollection([model]) + """Load an ORSO file, creating a model from its ``sample.model`` (when present) and an experiment. + + .. deprecated:: + Use :meth:`load_new_experiment` (data) together with + :meth:`set_sample_from_orso` / + :func:`easyreflectometry.orso_utils.load_orso_model` (model) + instead. This wrapper now routes through the same + ``DataSet1D`` + title + resolution path as every other importer. + + Parameters + ---------- + path : Union[Path, str] + Path to the ORSO file. + """ + warnings.warn( + 'Project.load_orso_file is deprecated; use load_new_experiment (data) and ' + 'set_sample_from_orso/load_orso_model (model) instead.', + DeprecationWarning, + stacklevel=2, + ) + from easyreflectometry.orso_utils import _load_orso_any + from easyreflectometry.orso_utils import load_orso_model + + orso_data = _load_orso_any(str(path)) + sample = load_orso_model(orso_data) + if sample is not None: + self.models = ModelCollection([Model(sample=sample, name=sample.name)]) else: self.default_model() - if data is not None: - self._experiments[0] = data - self._experiments[0].name = 'Experiment from ORSO' - self._experiments[0].model = self.models[0] - self._with_experiments = True - pass + self.load_experiment_for_model_at_index(path, 0) def set_sample_from_orso(self, sample: Sample) -> None: """Replace the current project model collection with a single model built from an ORSO-parsed sample. @@ -938,7 +957,9 @@ def _apply_resolution_function( model : Model The model whose resolution function is set. """ - if experiment.xe is not None and np.any(experiment.xe): + # nan-robust gate: nan is truthy for np.any, but a nan-carrying xe must + # not build a Pointwise (np.interp would propagate the nan everywhere). + if experiment.xe is not None and np.any(np.nan_to_num(experiment.xe) > 0): model.resolution_function = Pointwise(q_data_points=[experiment.x, experiment.y, experiment.xe]) else: model.resolution_function = PercentageFwhm(5.0) @@ -949,16 +970,27 @@ def _auto_set_background(experiment: DataSet1D) -> None: if experiment.model is not None and len(experiment.y) > 0: experiment.model.background = max(np.min(experiment.y), 1e-10) - def load_new_experiment(self, path: Union[Path, str]) -> None: - """Load new experiment.""" - new_experiment = load_as_dataset(str(path)) + def load_new_experiment(self, path: Union[Path, str], data_group=None) -> None: + """Load new experiment. + + Parameters + ---------- + path : Union[Path, str] + Path to the experiment data file. + data_group : + Pre-loaded scipp DataGroup for *path* (avoids re-parsing the + file). By default, None. + """ + if data_group is None: + data_group = load_measurement_file(str(path)) + new_experiment = load_as_dataset(str(path), data_group=data_group) new_index = len(self._experiments) model_index = 0 if new_index < len(self.models): model_index = new_index - self._apply_experiment_metadata(path, new_experiment, f'Experiment {new_index}') + self._apply_experiment_metadata(path, new_experiment, f'Experiment {new_index}', data_group=data_group) new_experiment.model = self.models[model_index] self._auto_set_background(new_experiment) self._experiments[new_index] = new_experiment @@ -976,12 +1008,16 @@ def count_datasets_in_file(self, path: Union[Path, str]) -> int: Returns ------- int - Number of datasets found; 1 if the file cannot be introspected. + Number of datasets found; 1 if a non-ORSO file cannot be + introspected. A corrupt ORSO file (banner present but unparsable) + raises instead of being silently miscounted. """ try: - data_group = load_data_from_orso_file(str(path)) + data_group = load_measurement_file(str(path)) return len(data_group['data']) except Exception: + if is_orso_file(str(path)): + raise return 1 def load_all_experiments_from_file(self, path: Union[Path, str]) -> int: @@ -989,8 +1025,9 @@ def load_all_experiments_from_file(self, path: Union[Path, str]) -> int: For a multi-dataset ORSO file (e.g. a multi-angle measurement), each dataset is registered as an independent experiment. All experiments share the model that is - currently selected. Falls back to :meth:`load_new_experiment` for single-dataset - files or on any loading error. + currently selected. Single-dataset files go through + :meth:`load_new_experiment`. A corrupt ORSO file raises (no silent + plain-text fallback). Parameters ---------- @@ -1002,32 +1039,19 @@ def load_all_experiments_from_file(self, path: Union[Path, str]) -> int: int Number of experiments that were added. """ - try: - data_group = load_data_from_orso_file(str(path)) - except Exception: - self.load_new_experiment(path) - return 1 + data_group = load_measurement_file(str(path)) data_keys = sorted(data_group['data'].keys()) if len(data_keys) <= 1: - self.load_new_experiment(path) + self.load_new_experiment(path, data_group=data_group) return 1 model_index = self._current_model_index for data_key in data_keys: - coord_key = data_key.replace('R_', 'Qz_') new_index = len(self._experiments) - d = data_group['data'][data_key] - c = data_group['coords'][coord_key] - - new_experiment = DataSet1D( - name=f'Experiment {new_index}', - x=c.values, - y=d.values, - ye=d.variances, - xe=c.variances if c.variances is not None else None, - ) + new_experiment = dataset_from_datagroup(data_group, data_key=data_key) + new_experiment.name = f'Experiment {new_index}' self._apply_experiment_metadata( path, new_experiment, @@ -1091,19 +1115,119 @@ def load_polarized_experiment( """ paths = {PolarizationChannel(channel): path for channel, path in paths.items()} channels = {} + title_data_group = None for channel, path in paths.items(): + # Parse each file once: dataset count, data, and title all come + # from the same DataGroup (previously up to 3-4 parses per file). + data_group = load_measurement_file(str(path)) # One file per channel means one dataset per file; a multi-dataset ORSO # file has no defined channel-to-dataset assignment here. - if self.count_datasets_in_file(path) > 1: + if len(data_group['data']) > 1: raise ValueError( - f"File '{path}' contains multiple datasets; polarized loading requires one dataset " - f'per channel file. Export the {channel.value} channel to its own file.' + f"File '{path}' contains multiple datasets; use load_polarized_experiment_from_file " + f'for a single multi-dataset file, or export the {channel.value} channel to its own file.' ) - dataset = load_as_dataset(str(path)) + dataset = load_as_dataset(str(path), data_group=data_group) # Keep the source file visible per channel (file → channel provenance). dataset.name = f'{channel.value}: {Path(path).name}' channels[channel] = dataset + if title_data_group is None: + title_data_group = data_group + + first_path = next(iter(paths.values())) + return self._register_polarized_experiment(channels, model_index, str(first_path), title_data_group) + + def load_polarized_experiment_from_file( + self, + path: Union[Path, str], + model_index: Optional[int] = None, + ) -> int: + """Load a polarized experiment from a single multi-dataset ORSO file. + + Multi-dataset packing is the ORSO format's intended way to store spin + states: each ``data_set:`` block is classified by its **own** header + (``instrument_settings.polarization``), so per-dataset overrides are + honoured. Only the fully-analysed cross-sections ``pp/pm/mp/mm`` are + mapped; ``po/mo/op/om/unpolarized/vector`` are not coerced. + + Parameters + ---------- + path : Union[Path, str] + Path to the multi-dataset ORSO file. + model_index : Optional[int], optional + Index of the model the experiment belongs to. By default, the + current model. + + Returns + ------- + int + Index of the newly loaded experiment. + + Raises + ------ + ValueError : + If any dataset lacks a mappable polarization header, or two + datasets declare the same channel. Such files cannot be classified + unambiguously; use :meth:`load_all_experiments_from_file` (e.g. for + multi-angle files) or per-channel files instead. + """ + from easyreflectometry.orso_utils import _load_orso_any + from easyreflectometry.orso_utils import _orso_dataset_key + from easyreflectometry.orso_utils import load_orso_data + + orso_data = _load_orso_any(str(path)) + classified = detect_polarization_channels_per_dataset(orso_data) + + channels = {} + for i, (declared, channel) in enumerate(classified): + label = _orso_dataset_key(orso_data[i], i) + if channel is None: + reason = 'declares no mappable spin channel' if declared else 'declares no polarization' + raise ValueError( + f"Dataset '{label}' in '{path}' {reason} " + f'(only pp/pm/mp/mm are mapped; po/mo/op/om/unpolarized are not coerced). ' + f'Use load_all_experiments_from_file for non-polarized multi-dataset files, ' + f'or assign channels explicitly via load_polarized_experiment.' + ) + if channel in channels: + raise ValueError( + f"Duplicate spin channel '{channel.value}' in '{path}': more than one dataset " + f'declares it. Assign channels explicitly via load_polarized_experiment.' + ) + channels[channel] = label + + data_group = load_orso_data(orso_data) + channel_datasets = {} + for channel, label in channels.items(): + dataset = dataset_from_datagroup(data_group, data_key=f'R_{label}') + dataset.name = f'{channel.value}: {label}' + channel_datasets[channel] = dataset + + return self._register_polarized_experiment(channel_datasets, model_index, str(path), data_group) + + def _register_polarized_experiment(self, channels, model_index, title_path, title_data_group) -> int: + """Shared tail of the polarized loaders: build, name, and register the experiment. + Background and resolution follow the first (in canonical order) + channel; per-channel resolution functions are not supported (one per + experiment). + + Parameters + ---------- + channels : + Channel → DataSet1D mapping. + model_index : + Index of the model, or None for the current one. + title_path : + Path used for the fallback experiment title lookup. + title_data_group : + Pre-loaded DataGroup for the title lookup (avoids re-parsing). + + Returns + ------- + int + Index of the newly loaded experiment. + """ new_index = len(self._experiments) if model_index is None: model_index = self._current_model_index @@ -1115,13 +1239,11 @@ def load_polarized_experiment( model=model, ) # Name from the ORSO title of the first file, when available. - first_channel = experiment.available_channels[0] - first_path = paths[first_channel] - self._apply_experiment_metadata(first_path, experiment, f'Polarized experiment {new_index}') + self._apply_experiment_metadata( + title_path, experiment, f'Polarized experiment {new_index}', data_group=title_data_group + ) - # Background and resolution follow the first (in canonical order) channel; - # per-channel resolution functions are not supported (one per experiment). - first_dataset = experiment[first_channel] + first_dataset = experiment[experiment.available_channels[0]] self._auto_set_background(first_dataset) self._apply_resolution_function(first_dataset, model) @@ -1129,11 +1251,32 @@ def load_polarized_experiment( self._with_experiments = True return new_index + def save_experiment_as_orso(self, path: Union[Path, str], index: Optional[int] = None) -> None: + """Write an experiment to an ORSO file (`.ort` text, or `.orb` binary). + + The experiment's model (when set) is exported as the ORSO + ``sample.model`` (slab representation); data columns are written as + sigma. A polarized experiment becomes one file with one ``data_set:`` + block per spin channel. + + Parameters + ---------- + path : Union[Path, str] + Destination path; a ``.orb`` extension selects the binary format. + index : Optional[int], optional + Index of the experiment to save. By default, the current one. + """ + if index is None: + index = self._current_experiment_index + experiment = self._experiments[index] + save_orso_experiment(experiment, str(path), model=experiment.model) + def load_experiment_for_model_at_index(self, path: Union[Path, str], index: Optional[int] = 0) -> None: """Load experiment for model at index.""" - experiment = load_as_dataset(str(path)) + data_group = load_measurement_file(str(path)) + experiment = load_as_dataset(str(path), data_group=data_group) - self._apply_experiment_metadata(path, experiment, f'Experiment {index}') + self._apply_experiment_metadata(path, experiment, f'Experiment {index}', data_group=data_group) experiment.model = self.models[index] self._auto_set_background(experiment) self._experiments[index] = experiment diff --git a/tests/_static/example.orb b/tests/_static/example.orb new file mode 100644 index 0000000000000000000000000000000000000000..a490002726f247427dc987130c59550ac70ae777 GIT binary patch literal 21869 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 QJX>-FE6|8`@5)pD56cM(#{d8T literal 0 HcmV?d00001 diff --git a/tests/_static/fwhm_sqz.ort b/tests/_static/fwhm_sqz.ort new file mode 100644 index 00000000..e847b96f --- /dev/null +++ b/tests/_static/fwhm_sqz.ort @@ -0,0 +1,39 @@ +# # ORSO reflectivity data file | 1.2 standard | YAML encoding | https://www.reflectometry.org/ +# data_source: +# owner: +# name: Generated fixture +# affiliation: EasyScience +# experiment: +# title: FWHM sQz fixture +# instrument: fixture +# start_date: null +# probe: neutron +# sample: +# name: FWHM sQz fixture +# measurement: +# instrument_settings: +# incident_angle: {magnitude: 0.5, unit: deg} +# wavelength: {magnitude: 4.0, unit: angstrom} +# polarization: null +# data_files: [] +# reduction: +# software: {name: make_fixtures} +# data_set: 0 +# columns: +# - {name: Qz, unit: 1/angstrom, physical_quantity: wavevector transfer} +# - {name: R, physical_quantity: reflectivity} +# - {error_of: R, error_type: uncertainty, value_is: sigma} +# - {error_of: Qz, error_type: resolution, value_is: FWHM} +# # Qz (1/angstrom) R sR sQz +1.0000000000000000e-02 6.7032104603563936e-01 3.3516052301781972e-02 4.7096400900618988e-04 +3.6363636363636362e-02 2.3350747909091338e-01 1.1675373954545669e-02 1.7125963963861449e-03 +6.2727272727272729e-02 8.1343153050479095e-02 4.0671576525239551e-03 2.9542287837661003e-03 +8.9090909090909082e-02 2.8336598603717900e-02 1.4168299301858950e-03 4.1958611711460552e-03 +1.1545454545454545e-01 9.8717265313318422e-03 4.9358632656659209e-04 5.4374935585260100e-03 +1.4181818181818182e-01 3.4394748181587544e-03 1.7197374090793772e-04 6.6791259459059657e-03 +1.6818181818181818e-01 1.1987952218190572e-03 5.9939761090952858e-05 7.9207583332859214e-03 +1.9454545454545455e-01 4.1825284298601604e-04 2.0912642149300804e-05 9.1623907206658754e-03 +2.2090909090909092e-01 1.4635033351987538e-04 7.3175166759937690e-06 1.0404023108045831e-02 +2.4727272727272728e-01 5.1632895160517905e-05 2.5816447580258954e-06 1.1645655495425785e-02 +2.7363636363636362e-01 1.8638006121158546e-05 9.3190030605792728e-07 1.2887287882805741e-02 +2.9999999999999999e-01 7.1442123533282095e-06 3.5721061766641050e-07 1.4128920270185696e-02 diff --git a/tests/_static/nan_sqz.ort b/tests/_static/nan_sqz.ort new file mode 100644 index 00000000..c10b221d --- /dev/null +++ b/tests/_static/nan_sqz.ort @@ -0,0 +1,39 @@ +# # ORSO reflectivity data file | 1.2 standard | YAML encoding | https://www.reflectometry.org/ +# data_source: +# owner: +# name: Generated fixture +# affiliation: EasyScience +# experiment: +# title: All-nan sQz fixture +# instrument: fixture +# start_date: null +# probe: neutron +# sample: +# name: All-nan sQz fixture +# measurement: +# instrument_settings: +# incident_angle: {magnitude: 0.5, unit: deg} +# wavelength: {magnitude: 4.0, unit: angstrom} +# polarization: null +# data_files: [] +# reduction: +# software: {name: make_fixtures} +# data_set: 0 +# columns: +# - {name: Qz, unit: 1/angstrom, physical_quantity: wavevector transfer} +# - {name: R, physical_quantity: reflectivity} +# - {error_of: R, error_type: uncertainty, value_is: sigma} +# - {error_of: Qz, error_type: resolution, value_is: sigma} +# # Qz (1/angstrom) R sR sQz +1.0000000000000000e-02 6.7032104603563936e-01 3.3516052301781972e-02 nan +3.6363636363636362e-02 2.3350747909091338e-01 1.1675373954545669e-02 nan +6.2727272727272729e-02 8.1343153050479095e-02 4.0671576525239551e-03 nan +8.9090909090909082e-02 2.8336598603717900e-02 1.4168299301858950e-03 nan +1.1545454545454545e-01 9.8717265313318422e-03 4.9358632656659209e-04 nan +1.4181818181818182e-01 3.4394748181587544e-03 1.7197374090793772e-04 nan +1.6818181818181818e-01 1.1987952218190572e-03 5.9939761090952858e-05 nan +1.9454545454545455e-01 4.1825284298601604e-04 2.0912642149300804e-05 nan +2.2090909090909092e-01 1.4635033351987538e-04 7.3175166759937690e-06 nan +2.4727272727272728e-01 5.1632895160517905e-05 2.5816447580258954e-06 nan +2.7363636363636362e-01 1.8638006121158546e-05 9.3190030605792728e-07 nan +2.9999999999999999e-01 7.1442123533282095e-06 3.5721061766641050e-07 nan diff --git a/tests/_static/nm_units.ort b/tests/_static/nm_units.ort new file mode 100644 index 00000000..95a6f212 --- /dev/null +++ b/tests/_static/nm_units.ort @@ -0,0 +1,67 @@ +# # ORSO reflectivity data file | 1.2 standard | YAML encoding | https://www.reflectometry.org/ +# data_source: +# owner: +# name: Generated fixture +# affiliation: EasyScience +# experiment: +# title: nm units fixture +# instrument: fixture +# start_date: null +# probe: neutron +# sample: +# name: nm units fixture +# model: +# stack: air | 3 ( A | B ) | film | Si +# layers: +# air: +# thickness: 0.0 +# roughness: 0.0 +# material: +# sld: {real: 0.0, imag: 0.0} +# A: +# thickness: 2.0 +# roughness: 0.3 +# material: +# sld: {real: 4.0e-06, imag: 0.0} +# B: +# thickness: 1.0 +# roughness: 0.3 +# material: +# sld: {real: 2.0e-06, imag: 0.0} +# film: +# thickness: 10.0 +# roughness: 0.5 +# material: +# sld: {real: 3.47e-06, imag: 0.0} +# Si: +# thickness: 0.0 +# roughness: 0.3 +# material: +# sld: {real: 2.07e-06, imag: 0.0} +# measurement: +# instrument_settings: +# incident_angle: {magnitude: 0.5, unit: deg} +# wavelength: {magnitude: 4.0, unit: angstrom} +# polarization: null +# data_files: [] +# reduction: +# software: {name: make_fixtures} +# data_set: 0 +# columns: +# - {name: Qz, unit: 1/nm, physical_quantity: wavevector transfer} +# - {name: R, physical_quantity: reflectivity} +# - {error_of: R, error_type: uncertainty, value_is: sigma} +# - {error_of: Qz, error_type: resolution, value_is: sigma} +# # Qz (1/nm) R sR sQz +1.0000000000000001e-01 6.7032104603563936e-01 3.3516052301781972e-02 2.0000000000000000e-03 +3.6363636363636365e-01 2.3350747909091338e-01 1.1675373954545669e-02 7.2727272727272727e-03 +6.2727272727272732e-01 8.1343153050479095e-02 4.0671576525239551e-03 1.2545454545454545e-02 +8.9090909090909087e-01 2.8336598603717900e-02 1.4168299301858950e-03 1.7818181818181816e-02 +1.1545454545454545e+00 9.8717265313318422e-03 4.9358632656659209e-04 2.3090909090909092e-02 +1.4181818181818182e+00 3.4394748181587544e-03 1.7197374090793772e-04 2.8363636363636365e-02 +1.6818181818181819e+00 1.1987952218190572e-03 5.9939761090952858e-05 3.3636363636363638e-02 +1.9454545454545455e+00 4.1825284298601604e-04 2.0912642149300804e-05 3.8909090909090914e-02 +2.2090909090909090e+00 1.4635033351987538e-04 7.3175166759937690e-06 4.4181818181818183e-02 +2.4727272727272727e+00 5.1632895160517905e-05 2.5816447580258954e-06 4.9454545454545452e-02 +2.7363636363636363e+00 1.8638006121158546e-05 9.3190030605792728e-07 5.4727272727272722e-02 +3.0000000000000000e+00 7.1442123533282095e-06 3.5721061766641050e-07 5.9999999999999998e-02 diff --git a/tests/_static/partial_nan_sqz.ort b/tests/_static/partial_nan_sqz.ort new file mode 100644 index 00000000..895ace5c --- /dev/null +++ b/tests/_static/partial_nan_sqz.ort @@ -0,0 +1,39 @@ +# # ORSO reflectivity data file | 1.2 standard | YAML encoding | https://www.reflectometry.org/ +# data_source: +# owner: +# name: Generated fixture +# affiliation: EasyScience +# experiment: +# title: Partial-nan sQz fixture +# instrument: fixture +# start_date: null +# probe: neutron +# sample: +# name: Partial-nan sQz fixture +# measurement: +# instrument_settings: +# incident_angle: {magnitude: 0.5, unit: deg} +# wavelength: {magnitude: 4.0, unit: angstrom} +# polarization: null +# data_files: [] +# reduction: +# software: {name: make_fixtures} +# data_set: 0 +# columns: +# - {name: Qz, unit: 1/angstrom, physical_quantity: wavevector transfer} +# - {name: R, physical_quantity: reflectivity} +# - {error_of: R, error_type: uncertainty, value_is: sigma} +# - {error_of: Qz, error_type: resolution, value_is: sigma} +# # Qz (1/angstrom) R sR sQz +1.0000000000000000e-02 6.7032104603563936e-01 3.3516052301781972e-02 nan +3.6363636363636362e-02 2.3350747909091338e-01 1.1675373954545669e-02 nan +6.2727272727272729e-02 8.1343153050479095e-02 4.0671576525239551e-03 1.2545454545454546e-03 +8.9090909090909082e-02 2.8336598603717900e-02 1.4168299301858950e-03 1.7818181818181817e-03 +1.1545454545454545e-01 9.8717265313318422e-03 4.9358632656659209e-04 2.3090909090909091e-03 +1.4181818181818182e-01 3.4394748181587544e-03 1.7197374090793772e-04 2.8363636363636364e-03 +1.6818181818181818e-01 1.1987952218190572e-03 5.9939761090952858e-05 3.3636363636363638e-03 +1.9454545454545455e-01 4.1825284298601604e-04 2.0912642149300804e-05 3.8909090909090911e-03 +2.2090909090909092e-01 1.4635033351987538e-04 7.3175166759937690e-06 4.4181818181818185e-03 +2.4727272727272728e-01 5.1632895160517905e-05 2.5816447580258954e-06 4.9454545454545454e-03 +2.7363636363636362e-01 1.8638006121158546e-05 9.3190030605792728e-07 5.4727272727272723e-03 +2.9999999999999999e-01 7.1442123533282095e-06 3.5721061766641050e-07 nan diff --git a/tests/_static/polarized_2ch.ort b/tests/_static/polarized_2ch.ort new file mode 100644 index 00000000..fd91ffc8 --- /dev/null +++ b/tests/_static/polarized_2ch.ort @@ -0,0 +1,57 @@ +# # ORSO reflectivity data file | 1.2 standard | YAML encoding | https://www.reflectometry.org/ +# data_source: +# owner: +# name: Generated fixture +# affiliation: EasyScience +# experiment: +# title: Polarized fixture +# instrument: fixture +# start_date: null +# probe: neutron +# sample: +# name: Polarized fixture +# measurement: +# instrument_settings: +# incident_angle: {magnitude: 0.5, unit: deg} +# wavelength: {magnitude: 4.0, unit: angstrom} +# polarization: pp +# data_files: [] +# reduction: +# software: {name: make_fixtures} +# data_set: pp +# columns: +# - {name: Qz, unit: 1/angstrom, physical_quantity: wavevector transfer} +# - {name: R, physical_quantity: reflectivity} +# - {error_of: R, error_type: uncertainty, value_is: sigma} +# - {error_of: Qz, error_type: resolution, value_is: sigma} +# # Qz (1/angstrom) R sR sQz +1.0000000000000000e-02 7.3735315063920337e-01 3.3516052301781972e-02 2.0000000000000001e-04 +3.6363636363636362e-02 2.5685822700000471e-01 1.1675373954545669e-02 7.2727272727272723e-04 +6.2727272727272729e-02 8.9477468355527015e-02 4.0671576525239551e-03 1.2545454545454546e-03 +8.9090909090909082e-02 3.1170258464089693e-02 1.4168299301858950e-03 1.7818181818181817e-03 +1.1545454545454545e-01 1.0858899184465027e-02 4.9358632656659209e-04 2.3090909090909091e-03 +1.4181818181818182e-01 3.7834222999746300e-03 1.7197374090793772e-04 2.8363636363636364e-03 +1.6818181818181818e-01 1.3186747440009630e-03 5.9939761090952858e-05 3.3636363636363638e-03 +1.9454545454545455e-01 4.6007812728461768e-04 2.0912642149300804e-05 3.8909090909090911e-03 +2.2090909090909092e-01 1.6098536687186293e-04 7.3175166759937690e-06 4.4181818181818185e-03 +2.4727272727272728e-01 5.6796184676569701e-05 2.5816447580258954e-06 4.9454545454545454e-03 +2.7363636363636362e-01 2.0501806733274400e-05 9.3190030605792728e-07 5.4727272727272723e-03 +2.9999999999999999e-01 7.8586335886610303e-06 3.5721061766641050e-07 6.0000000000000001e-03 +# data_set: mm +# data_source: +# measurement: +# instrument_settings: +# polarization: mm +# # Qz (1/angstrom) R sR sQz +1.0000000000000000e-02 6.0328894143207545e-01 3.3516052301781972e-02 2.0000000000000001e-04 +3.6363636363636362e-02 2.1015673118182204e-01 1.1675373954545669e-02 7.2727272727272723e-04 +6.2727272727272729e-02 7.3208837745431188e-02 4.0671576525239551e-03 1.2545454545454546e-03 +8.9090909090909082e-02 2.5502938743346110e-02 1.4168299301858950e-03 1.7818181818181817e-03 +1.1545454545454545e-01 8.8845538781986578e-03 4.9358632656659209e-04 2.3090909090909091e-03 +1.4181818181818182e-01 3.0955273363428792e-03 1.7197374090793772e-04 2.8363636363636364e-03 +1.6818181818181818e-01 1.0789156996371516e-03 5.9939761090952858e-05 3.3636363636363638e-03 +1.9454545454545455e-01 3.7642755868741445e-04 2.0912642149300804e-05 3.8909090909090911e-03 +2.2090909090909092e-01 1.3171530016788786e-04 7.3175166759937690e-06 4.4181818181818185e-03 +2.4727272727272728e-01 4.6469605644466116e-05 2.5816447580258954e-06 4.9454545454545454e-03 +2.7363636363636362e-01 1.6774205509042691e-05 9.3190030605792728e-07 5.4727272727272723e-03 +2.9999999999999999e-01 6.4297911179953888e-06 3.5721061766641050e-07 6.0000000000000001e-03 diff --git a/tests/integration/fitting/test_dummy.py b/tests/integration/fitting/test_dummy.py index 2256189c..389d6cb0 100644 --- a/tests/integration/fitting/test_dummy.py +++ b/tests/integration/fitting/test_dummy.py @@ -11,6 +11,7 @@ def test_dummy_fast(): assert calculated == expected +@pytest.mark.slow def test_dummy_slow(): calculated = sum(i * j for i in range(10000) for j in range(10000)) expected = 2499500025000000 diff --git a/tests/integration/scipp-analysis/test_dummy.py b/tests/integration/scipp-analysis/test_dummy.py index 2256189c..389d6cb0 100644 --- a/tests/integration/scipp-analysis/test_dummy.py +++ b/tests/integration/scipp-analysis/test_dummy.py @@ -11,6 +11,7 @@ def test_dummy_fast(): assert calculated == expected +@pytest.mark.slow def test_dummy_slow(): calculated = sum(i * j for i in range(10000) for j in range(10000)) expected = 2499500025000000 diff --git a/tests/integration/test_ort_file_fitting.py b/tests/integration/test_ort_file_fitting.py index ded7cd99..01176a09 100644 --- a/tests/integration/test_ort_file_fitting.py +++ b/tests/integration/test_ort_file_fitting.py @@ -120,6 +120,7 @@ def fit_model(load_data): return analysed +@pytest.mark.slow def test_analyze_reduced_data__fit_model_success(fit_model): assert fit_model['success'] is True diff --git a/tests/test_fitting.py b/tests/test_fitting.py index b1a1cf32..ddb12c77 100644 --- a/tests/test_fitting.py +++ b/tests/test_fitting.py @@ -28,6 +28,7 @@ PATH_STATIC = os.path.join(os.path.dirname(easyreflectometry.__file__), '..', '..', 'tests', '_static') +@pytest.mark.slow @pytest.mark.parametrize('minimizer', [AvailableMinimizers.Bumps, AvailableMinimizers.LMFit]) def test_fitting(minimizer): fpath = os.path.join(PATH_STATIC, 'example.ort') @@ -89,6 +90,7 @@ def test_fitting(minimizer): assert analysed['success'] +@pytest.mark.slow def test_fitting_with_zero_variance(): """Test that zero variance points are handled via Mighell substitution (hybrid default).""" import warnings @@ -173,6 +175,7 @@ def test_fitting_with_zero_variance(): assert 'success' in analysed.keys() +@pytest.mark.slow def test_fitting_with_manual_zero_variance(): """Test the fit method with manually created zero variance points using hybrid (default).""" import warnings @@ -1201,6 +1204,7 @@ def _analytic_wls(design, y, point_weights): return beta, covariance +@pytest.mark.slow @pytest.mark.parametrize('minimizer', [AvailableMinimizers.LMFit, AvailableMinimizers.Bumps]) def test_fit_weight_convention_matches_analytic_wls(minimizer): """Pin the weights = 1/sigma convention end-to-end against analytic WLS. diff --git a/tests/test_measurement_comprehensive.py b/tests/test_measurement_comprehensive.py index baffb2b8..2a35e669 100644 --- a/tests/test_measurement_comprehensive.py +++ b/tests/test_measurement_comprehensive.py @@ -21,6 +21,7 @@ from easyreflectometry.data.data_store import DataStore from easyreflectometry.data.data_store import ProjectData from easyreflectometry.data.measurement import _load_txt +from easyreflectometry.data.measurement import dataset_from_datagroup from easyreflectometry.data.measurement import load from easyreflectometry.data.measurement import load_as_dataset from easyreflectometry.data.measurement import merge_datagroups @@ -408,3 +409,45 @@ def test_data_consistency_checks(self): if __name__ == '__main__': # Run all tests if script is executed directly pytest.main([__file__, '-v']) + + +class TestDatasetFromDatagroup: + """The DataGroup -> DataSet1D step shared by the loaders that parse once.""" + + def test_defaults_to_the_first_dataset(self): + """Without a data_key the first entry is taken (the documented default).""" + fpath = os.path.join(PATH_STATIC, 'test_example1.txt') + data_group = load(fpath) + + dataset = dataset_from_datagroup(data_group) + + first_data_key = list(data_group['data'].keys())[0] + first_coords_key = list(data_group['coords'].keys())[0] + assert_array_equal(dataset.y, data_group['data'][first_data_key].values) + assert_array_equal(dataset.x, data_group['coords'][first_coords_key].values) + + def test_explicit_data_key_selects_that_dataset(self): + """A multi-dataset ORSO file is addressed one dataset at a time.""" + fpath = os.path.join(PATH_STATIC, 'polarized_2ch.ort') + data_group = load(fpath) + keys = list(data_group['data'].keys()) + assert len(keys) > 1 + + second = dataset_from_datagroup(data_group, data_key=keys[1]) + + assert_array_equal(second.y, data_group['data'][keys[1]].values) + + def test_orso_header_is_attached_for_orso_files(self): + """The parsed header rides along so the exporter can reuse provenance.""" + fpath = os.path.join(PATH_STATIC, 'test_example1.ort') + dataset = dataset_from_datagroup(load(fpath)) + + assert dataset.orso_header is not None + assert 'data_source' in dataset.orso_header + + def test_orso_header_is_none_for_plain_text(self): + """A text file carries no header; the attribute is still set, to None.""" + fpath = os.path.join(PATH_STATIC, 'test_example1.txt') + dataset = dataset_from_datagroup(load(fpath)) + + assert dataset.orso_header is None diff --git a/tests/test_orso_support.py b/tests/test_orso_support.py new file mode 100644 index 00000000..0561c1a6 --- /dev/null +++ b/tests/test_orso_support.py @@ -0,0 +1,733 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause + +"""Tests for the ORSO support update (ORSO_UPDATE_TASK.md): + +reader hardening (banner discriminator, FWHM->sigma, nan policy, 1/nm Q), +model-language preservation (units, repeats, density materials), +single-file polarized import, the .ort/.orb exporter, and .orb reading. +""" + +import os + +import numpy as np +import pytest +from easyscience import global_object +from orsopy.fileio import model_language +from orsopy.fileio.base import Column +from orsopy.fileio.base import ComplexValue +from orsopy.fileio.base import ErrorColumn +from orsopy.fileio.base import Value +from orsopy.fileio.orso import Orso +from orsopy.fileio.orso import OrsoDataset +from orsopy.fileio.orso import save_orso + +import easyreflectometry +from easyreflectometry.data import DataSet1D +from easyreflectometry.data import PolarizedDataSet +from easyreflectometry.data.measurement import load +from easyreflectometry.data.measurement import load_as_dataset +from easyreflectometry.model import PercentageFwhm +from easyreflectometry.model import Pointwise +from easyreflectometry.orso_utils import SIGMA_TO_FWHM +from easyreflectometry.orso_utils import _load_orso_any +from easyreflectometry.orso_utils import is_orso_file +from easyreflectometry.orso_utils import load_orso_model +from easyreflectometry.orso_utils import sample_to_orso_model +from easyreflectometry.orso_utils import save_orso_experiment +from easyreflectometry.project import Project +from easyreflectometry.sample import Layer +from easyreflectometry.sample import Material +from easyreflectometry.sample import Multilayer +from easyreflectometry.sample import RepeatingMultilayer +from easyreflectometry.sample import Sample +from easyreflectometry.sample.elements.materials.material_density import MaterialDensity + +PATH_STATIC = os.path.join(os.path.dirname(easyreflectometry.__file__), '..', '..', 'tests', '_static') + +# The Qz/sQz grids the generated fixtures were built from (see task 7a). +FIXTURE_QZ = np.linspace(0.01, 0.3, 12) +FIXTURE_SQZ = FIXTURE_QZ * 0.02 + + +@pytest.fixture(autouse=True) +def clear_global_map(): + global_object.map._clear() + yield + global_object.map._clear() + + +@pytest.fixture +def project() -> Project: + return Project() + + +class TestBannerDiscriminator: + def test_ort_file_is_orso(self): + assert is_orso_file(os.path.join(PATH_STATIC, 'Ni_example.ort')) is True + + def test_orb_file_is_orso(self): + assert is_orso_file(os.path.join(PATH_STATIC, 'example.orb')) is True + + def test_txt_file_is_not_orso(self): + assert is_orso_file(os.path.join(PATH_STATIC, 'test_example1.txt')) is False + + def test_bannered_but_corrupt_file_raises(self, tmp_path): + # A file carrying the ORSO banner that fails to parse must raise, not + # silently fall back to plain-text loading (which drops the header). + bad = tmp_path / 'bad.ort' + bad.write_text( + '# # ORSO reflectivity data file | 1.1 standard | YAML encoding | https://www.reflectometry.org/\n' + '# data_source: {[[not yaml\n' + '1.0 2.0 3.0 4.0\n' + ) + with pytest.raises(ValueError, match='Error loading ORSO file'): + load(str(bad)) + + def test_non_bannered_file_falls_back_to_txt(self, tmp_path): + plain = tmp_path / 'plain.dat' + plain.write_text('0.01 1.0 0.1 0.001\n0.02 0.5 0.05 0.002\n') + data_group = load(str(plain)) + assert 'R_plain' in data_group['data'] + + def test_count_datasets_raises_for_corrupt_orso(self, project, tmp_path): + bad = tmp_path / 'bad.ort' + bad.write_text('# # ORSO reflectivity data file | 1.1 standard | YAML encoding\n# data_source: {[[\n1 2 3 4\n') + with pytest.raises(ValueError): + project.count_datasets_in_file(str(bad)) + + def test_count_datasets_multi(self, project): + assert project.count_datasets_in_file(os.path.join(PATH_STATIC, 'polarized_2ch.ort')) == 2 + + +class TestNanPolicy: + def test_all_nan_sqz_leaves_xe_empty(self): + data_group = load(os.path.join(PATH_STATIC, 'nan_sqz.ort')) + coords = data_group['coords'][list(data_group['coords'])[0]] + assert coords.variances is None + + def test_all_nan_sqz_falls_back_to_percentage_fwhm(self, project): + project.default_model() + project.load_new_experiment(os.path.join(PATH_STATIC, 'nan_sqz.ort')) + assert isinstance(project.models[0].resolution_function, PercentageFwhm) + + def test_partial_nan_sqz_is_interpolated(self): + with pytest.warns(UserWarning, match='sQz values are nan'): + data_group = load(os.path.join(PATH_STATIC, 'partial_nan_sqz.ort')) + coords = data_group['coords'][list(data_group['coords'])[0]] + assert coords.variances is not None + assert np.all(np.isfinite(coords.variances)) + + def test_partial_nan_sqz_builds_finite_pointwise(self, project): + project.default_model() + with pytest.warns(UserWarning, match='sQz values are nan'): + project.load_new_experiment(os.path.join(PATH_STATIC, 'partial_nan_sqz.ort')) + resolution_function = project.models[0].resolution_function + assert isinstance(resolution_function, Pointwise) + assert np.all(np.isfinite(resolution_function.smearing(FIXTURE_QZ))) + + +class TestValueIsFwhm: + def test_fwhm_sqz_is_converted_to_sigma(self): + data_group = load(os.path.join(PATH_STATIC, 'fwhm_sqz.ort')) + coords = data_group['coords'][list(data_group['coords'])[0]] + # The fixture stores sigma * SIGMA_TO_FWHM declared as FWHM; loading + # must convert back so stored variances are sigma squared. + np.testing.assert_allclose(np.sqrt(coords.variances), FIXTURE_SQZ) + + def test_sigma_file_is_not_scaled(self): + data_group = load(os.path.join(PATH_STATIC, 'partial_nan_sqz.ort')) + coords = data_group['coords'][list(data_group['coords'])[0]] + valid = np.sqrt(coords.variances)[2:-1] + np.testing.assert_allclose(valid, FIXTURE_SQZ[2:-1]) + + +class TestQUnitConversion: + def test_1_per_nm_qz_is_converted_to_1_per_angstrom(self): + data_group = load(os.path.join(PATH_STATIC, 'nm_units.ort')) + coords = data_group['coords'][list(data_group['coords'])[0]] + np.testing.assert_allclose(coords.values, FIXTURE_QZ) + np.testing.assert_allclose(np.sqrt(coords.variances), FIXTURE_SQZ) + assert str(coords.unit) == '1/Å' + + +class TestModelLanguagePreservation: + def test_nm_default_length_unit_is_honoured(self): + # nm_units.ort declares thicknesses as bare magnitudes with no + # length_unit -> the model-language default (nm) applies. + sample = load_orso_model(_load_orso_any(os.path.join(PATH_STATIC, 'nm_units.ort'))) + film = [layer for assembly in sample for layer in assembly.layers if layer.name == 'film'][0] + assert film.thickness.value == pytest.approx(100.0) # 10 nm + assert film.roughness.value == pytest.approx(5.0) # 0.5 nm + + def test_default_roughness_from_globals(self): + sample = load_orso_model(_load_orso_any(os.path.join(PATH_STATIC, 'nm_units.ort'))) + subphase_layer = sample[-1].layers[0] + # Si declares roughness 0.3 (nm default) -> 3 A + assert subphase_layer.roughness.value == pytest.approx(3.0) + + def test_repeated_substack_becomes_repeating_multilayer(self): + sample = load_orso_model(_load_orso_any(os.path.join(PATH_STATIC, 'nm_units.ort'))) + repeating = [assembly for assembly in sample if isinstance(assembly, RepeatingMultilayer)] + assert len(repeating) == 1 + assert repeating[0].repetitions.value == 3 + assert [layer.name for layer in repeating[0].layers] == ['A', 'B'] + assert repeating[0].layers[0].thickness.value == pytest.approx(20.0) # 2 nm + + def test_density_material_stays_density_defined(self): + sample = load_orso_model(_load_orso_any(os.path.join(PATH_STATIC, 'Ni_example.ort'))) + m1 = sample[1].layers[0] + assert isinstance(m1.material, MaterialDensity) + assert m1.material.chemical_structure == 'Ni' + assert m1.material.density.value == pytest.approx(8.9) + # and the derived SLD is still sensible (Ni ~ 9.4e-6 A^-2) + assert m1.material.sld.value == pytest.approx(9.4, abs=0.1) + + +class TestPolarizedSingleFile: + def test_load_polarized_experiment_from_file(self, project): + project.default_model() + index = project.load_polarized_experiment_from_file(os.path.join(PATH_STATIC, 'polarized_2ch.ort')) + experiment = project.experiments[index] + assert isinstance(experiment, PolarizedDataSet) + assert [channel.value for channel in experiment.available_channels] == ['pp', 'mm'] + assert experiment.name == 'Polarized fixture' + assert experiment.model is project.models[0] + np.testing.assert_allclose(experiment['pp'].x, FIXTURE_QZ) + + def test_unclassifiable_dataset_raises(self, project): + project.default_model() + # nan_sqz.ort carries no spin-channel polarization -> not coerced. + with pytest.raises(ValueError, match='declares no (mappable spin channel|polarization)'): + project.load_polarized_experiment_from_file(os.path.join(PATH_STATIC, 'nan_sqz.ort')) + + def test_multidataset_file_rejected_by_per_channel_loader(self, project): + project.default_model() + with pytest.raises(ValueError, match='load_polarized_experiment_from_file'): + project.load_polarized_experiment({'pp': os.path.join(PATH_STATIC, 'polarized_2ch.ort')}) + + def test_duplicate_channel_raises(self, project, tmp_path): + # Build a file where two datasets declare the same channel. + datasets = _load_orso_any(os.path.join(PATH_STATIC, 'polarized_2ch.ort')) + from orsopy.fileio.data_source import Polarization + from orsopy.fileio.orso import save_orso + + for orso_dataset in datasets: + orso_dataset.info.data_source.measurement.instrument_settings.polarization = Polarization('pp') + duplicate_file = tmp_path / 'dup.ort' + save_orso(datasets, str(duplicate_file)) + + project.default_model() + with pytest.raises(ValueError, match="Duplicate spin channel 'pp'"): + project.load_polarized_experiment_from_file(str(duplicate_file)) + + +class TestExporter: + def test_ort_data_roundtrip_writes_sigma(self, project, tmp_path): + project.default_model() + project.load_new_experiment(os.path.join(PATH_STATIC, 'Ni_example.ort')) + experiment = project.experiments[0] + out = tmp_path / 'out.ort' + project.save_experiment_as_orso(str(out), 0) + + back = _load_orso_any(str(out)) + assert len(back) == 1 + data = back[0].data + np.testing.assert_allclose(data[:, 0], experiment.x) + np.testing.assert_allclose(data[:, 1], experiment.y) + # DataSet1D stores variances; the file must carry sigma. + np.testing.assert_allclose(data[:, 2], np.sqrt(experiment.ye)) + np.testing.assert_allclose(data[:, 3], np.sqrt(experiment.xe)) + # sigma convention declared in the columns + assert back[0].info.columns[2].value_is == 'sigma' + assert back[0].info.columns[3].value_is == 'sigma' + + def test_export_reuses_preserved_header(self, project, tmp_path): + project.default_model() + project.load_new_experiment(os.path.join(PATH_STATIC, 'Ni_example.ort')) + out = tmp_path / 'out.ort' + project.save_experiment_as_orso(str(out), 0) + info = _load_orso_any(str(out))[0].info + # data_source/reduction provenance from the original file, not synthesized + assert info.data_source.experiment.title == 'Metal films' + assert info.data_source.owner.name == 'Joe Bloggs' + + def test_export_writes_model_language(self, project, tmp_path): + project.default_model() + project.load_new_experiment(os.path.join(PATH_STATIC, 'Ni_example.ort')) + out = tmp_path / 'out.ort' + project.save_experiment_as_orso(str(out), 0) + model = _load_orso_any(str(out))[0].info.data_source.sample.model + assert model is not None + assert model.globals.length_unit == 'angstrom' + # the exported model resolves back into layers + assert len(model.resolve_to_layers()) >= 2 + + def test_absent_errors_written_as_nan(self, tmp_path): + dataset = DataSet1D(x=np.array([0.01, 0.02]), y=np.array([1.0, 0.5])) + out = tmp_path / 'nan.ort' + save_orso_experiment(dataset, str(out)) + data = _load_orso_any(str(out))[0].data + assert np.all(np.isnan(data[:, 2])) + assert np.all(np.isnan(data[:, 3])) + + def test_polarized_export_single_multidataset_file(self, project, tmp_path): + project.default_model() + index = project.load_polarized_experiment_from_file(os.path.join(PATH_STATIC, 'polarized_2ch.ort')) + out = tmp_path / 'pol.ort' + project.save_experiment_as_orso(str(out), index) + + back = _load_orso_any(str(out)) + assert [d.info.data_set for d in back] == ['pp', 'mm'] + polarizations = [str(d.info.data_source.measurement.instrument_settings.polarization.value) for d in back] + assert polarizations == ['pp', 'mm'] + + def test_repeating_multilayer_roundtrips_via_model_language(self): + air = Layer(material=Material(sld=0.0, isld=0.0, name='air'), thickness=0, roughness=0, name='air') + layer_a = Layer(material=Material(sld=4.0, isld=0.0, name='A'), thickness=20, roughness=3, name='A') + layer_b = Layer(material=Material(sld=2.0, isld=0.0, name='B'), thickness=10, roughness=3, name='B') + si = Layer(material=Material(sld=2.07, isld=0.0, name='Si'), thickness=0, roughness=3, name='Si') + sample = Sample( + Multilayer(air, name='Superphase'), + RepeatingMultilayer([layer_a, layer_b], repetitions=5, name='rep'), + Multilayer(si, name='Subphase'), + name='test', + ) + orso_model = sample_to_orso_model(sample) + assert '5 ( A | B )' in orso_model.stack + assert len(orso_model.resolve_to_layers()) == 12 # 1 + 5*2 + 1 + + def test_model_as_orso_returns_model_language_dict(self, project): + project.default_model() + orso_dict = project.models[0].as_orso() + assert 'stack' in orso_dict + assert 'layers' in orso_dict + assert orso_dict['globals']['length_unit'] == 'angstrom' + + +class TestOrbSupport: + def test_load_orb_file(self): + data_group = load(os.path.join(PATH_STATIC, 'example.orb')) + assert 'R_0' in data_group['data'] + coords = data_group['coords'][list(data_group['coords'])[0]] + np.testing.assert_allclose(coords.values, FIXTURE_QZ) + + def test_orb_write_and_read_roundtrip(self, project, tmp_path): + project.default_model() + project.load_new_experiment(os.path.join(PATH_STATIC, 'Ni_example.ort')) + experiment = project.experiments[0] + out = tmp_path / 'out.orb' + project.save_experiment_as_orso(str(out), 0) + back = _load_orso_any(str(out)) + np.testing.assert_allclose(back[0].data[:, 0], experiment.x) + np.testing.assert_allclose(back[0].data[:, 2], np.sqrt(experiment.ye)) + + def test_load_orb_as_dataset(self): + dataset = load_as_dataset(os.path.join(PATH_STATIC, 'example.orb')) + assert isinstance(dataset, DataSet1D) + np.testing.assert_allclose(dataset.x, FIXTURE_QZ) + assert dataset.orso_header is not None + + +class TestFwhmSigmaContract: + def test_pointwise_serialization_stays_variances(self): + # The saved-project contract: sQz_data_points round-trip as variances. + qz = np.array([0.01, 0.02]) + reflectivity = np.array([1.0, 0.5]) + variances = np.array([1e-8, 2e-8]) + pointwise = Pointwise([qz, reflectivity, variances]) + as_dict = pointwise.as_dict() + np.testing.assert_allclose(as_dict['sQz_data_points'], variances) + from easyreflectometry.model.resolution_functions import ResolutionFunction + + restored = ResolutionFunction.from_dict(as_dict) + np.testing.assert_allclose(restored.smearing(qz), np.sqrt(variances)) + + def test_sigma_to_fwhm_constant(self): + assert SIGMA_TO_FWHM == pytest.approx(2.3548, abs=1e-4) + + +# --------------------------------------------------------------------------- +# Synthetic .ort fixtures +# +# The static files in tests/_static cover the happy paths. The cases below are +# one-off malformed or unusual files, so they are built here rather than +# checked in: the header that provokes the behaviour stays next to the +# assertion about it. +# --------------------------------------------------------------------------- + +STANDARD_COLUMNS = [ + Column(name='Qz', unit='1/angstrom', physical_quantity='wavevector transfer'), + Column(name='R', physical_quantity='reflectivity'), + ErrorColumn(error_of='R', error_type='uncertainty', value_is='sigma'), + ErrorColumn(error_of='Qz', error_type='resolution', value_is='sigma'), +] + +SYNTHETIC_QZ = np.linspace(0.01, 0.3, 5) +SYNTHETIC_R = np.exp(-SYNTHETIC_QZ * 10.0) + + +def _write_ort(path, sample_model=None, columns=None, data=None, sample_name='synthetic fixture'): + """Write a single-dataset .ort file with the given model and columns. + + Parameters + ---------- + path : + Destination path. + sample_model : optional + An orsopy ``SampleModel`` for ``data_source.sample.model``. By default, None. + columns : optional + Column descriptors. By default, the standard Qz, R, sR, sQz four. + data : optional + The data array. By default, a four-column exponential decay. + sample_name : str, optional + ``data_source.sample.name``. By default, 'synthetic fixture'. + + Returns + ------- + str + The path written. + """ + info = Orso.empty() + info.data_set = 0 + info.data_source.sample.name = sample_name + info.data_source.sample.model = sample_model + info.columns = columns if columns is not None else list(STANDARD_COLUMNS) + if data is None: + data = np.column_stack([SYNTHETIC_QZ, SYNTHETIC_R, SYNTHETIC_R * 0.05, SYNTHETIC_QZ * 0.02]) + save_orso([OrsoDataset(info=info, data=data)], str(path)) + return str(path) + + +def _orso_layer(thickness=0.0, roughness=0.0, sld=0.0, **material_kwargs): + """A model-language layer, SLD-defined unless material keywords are given.""" + if material_kwargs: + material = model_language.Material(**material_kwargs) + else: + material = model_language.Material(sld=ComplexValue(real=sld)) + return model_language.Layer(thickness=thickness, roughness=roughness, material=material) + + +def _orso_sample_model(stack, layers=None, **kwargs): + """A model-language sample model, with an air/Si frame available by default.""" + frame = {'air': _orso_layer(), 'Si': _orso_layer(sld=2.07e-6)} + frame.update(layers or {}) + return model_language.SampleModel(stack=stack, layers=frame, **kwargs) + + +def _sample_from(path): + return load_orso_model(_load_orso_any(path)) + + +class TestColumnValidation: + def test_missing_error_columns_warn_and_leave_errors_absent(self, tmp_path): + # A two-column file is incomplete: warn, and treat the error columns as + # absent rather than inventing zeros (which would become fit weights). + path = _write_ort( + tmp_path / 'two_columns.ort', + columns=STANDARD_COLUMNS[:2], + data=np.column_stack([SYNTHETIC_QZ, SYNTHETIC_R]), + ) + with pytest.warns(UserWarning, match='declares only 2 columns'): + data_group = load(path) + assert data_group['data']['R_0'].variances is None + assert data_group['coords']['Qz_0'].variances is None + + def test_misordered_columns_warn(self, tmp_path): + # Data is read by position, so a file declaring R before Qz is read + # transposed. The warning is the only signal the user gets. + columns = [STANDARD_COLUMNS[1], STANDARD_COLUMNS[0]] + STANDARD_COLUMNS[2:] + path = _write_ort( + tmp_path / 'misordered.ort', + columns=columns, + data=np.column_stack([SYNTHETIC_R, SYNTHETIC_QZ, SYNTHETIC_R * 0.05, SYNTHETIC_QZ * 0.02]), + ) + with pytest.warns(UserWarning, match="column 0 is 'R'"): + load(path) + + def test_unknown_q_unit_warns_and_values_are_used_as_is(self, tmp_path): + columns = [Column(name='Qz', unit='1/m', physical_quantity='wavevector transfer')] + STANDARD_COLUMNS[1:] + path = _write_ort(tmp_path / 'q_unit.ort', columns=columns) + with pytest.warns(UserWarning, match="declares Qz unit '1/m'"): + data_group = load(path) + # Left unconverted: guessing a scale would be worse than not converting. + np.testing.assert_allclose(data_group['coords']['Qz_0'].values, SYNTHETIC_QZ) + + +class TestSrNanPolicy: + def test_all_nan_sr_leaves_variances_unset(self, tmp_path): + # All-nan is the spec's "uncertainty unknown": no variances are stored, + # and no warning is warranted. + data = np.column_stack([SYNTHETIC_QZ, SYNTHETIC_R, np.full_like(SYNTHETIC_QZ, np.nan), SYNTHETIC_QZ * 0.02]) + data_group = load(_write_ort(tmp_path / 'sr_all_nan.ort', data=data)) + assert data_group['data']['R_0'].variances is None + # the sQz column is unaffected by the sR policy + assert data_group['coords']['Qz_0'].variances is not None + + def test_partial_nan_sr_warns_and_is_not_interpolated(self, tmp_path): + # Unlike sQz, partial-nan sR is kept as-is: interpolating measurement + # uncertainties would fabricate fit weights. + sr = SYNTHETIC_R * 0.05 + sr[1] = np.nan + data = np.column_stack([SYNTHETIC_QZ, SYNTHETIC_R, sr, SYNTHETIC_QZ * 0.02]) + with pytest.warns(UserWarning, match='1 of 5 sR values are nan'): + data_group = load(_write_ort(tmp_path / 'sr_partial_nan.ort', data=data)) + variances = data_group['data']['R_0'].variances + assert np.isnan(variances[1]) + assert np.all(np.isfinite(np.delete(variances, 1))) + + +class TestStackShapes: + def test_multiple_plain_layer_groups_are_disambiguated(self, tmp_path): + # Two runs of plain layers separated by a sub-stack: the single-group + # name 'Loaded layer' would collide, so both runs get an index. + layers = { + 'f1': _orso_layer(thickness=Value(5.0, 'nm'), sld=3e-6), + 'f2': _orso_layer(thickness=Value(6.0, 'nm'), sld=1e-6), + 'A': _orso_layer(thickness=Value(2.0, 'nm'), sld=4e-6), + 'B': _orso_layer(thickness=Value(1.0, 'nm'), sld=2e-6), + } + model = _orso_sample_model('air | f1 | 2 ( A | B ) | f2 | Si', layers) + sample = _sample_from(_write_ort(tmp_path / 'two_groups.ort', model)) + assert [assembly.name for assembly in sample] == [ + 'Superphase', + 'Loaded layer 0', + 'Multilayer', + 'Loaded layer 1', + 'Subphase', + ] + + def test_single_repetition_substack_is_a_plain_multilayer(self, tmp_path): + layers = { + 'A': _orso_layer(thickness=Value(2.0, 'nm'), sld=4e-6), + 'B': _orso_layer(thickness=Value(1.0, 'nm'), sld=2e-6), + } + model = _orso_sample_model('air | 1 ( A | B ) | Si', layers) + sample = _sample_from(_write_ort(tmp_path / 'one_rep.ort', model)) + middle = sample[1] + assert isinstance(middle, Multilayer) + assert not isinstance(middle, RepeatingMultilayer) + assert [layer.name for layer in middle.layers] == ['A', 'B'] + + def test_substack_as_first_stack_item_is_flattened(self, tmp_path): + # The ambient entry must be a plain layer; a sub-stack there is + # pathological, so its layers are used directly. + layers = { + 'A': _orso_layer(thickness=Value(2.0, 'nm'), sld=4e-6), + 'B': _orso_layer(thickness=Value(1.0, 'nm'), sld=2e-6), + } + model = _orso_sample_model('2 ( A | B ) | air | Si', layers) + with pytest.warns(UserWarning, match='First ORSO stack item is a sub-stack'): + sample = _sample_from(_write_ort(tmp_path / 'first_sub.ort', model)) + assert sample[0].name == 'Superphase' + assert sample[0].layers[0].name == 'A' + + def test_substack_as_last_stack_item_is_flattened(self, tmp_path): + layers = { + 'A': _orso_layer(thickness=Value(2.0, 'nm'), sld=4e-6), + 'B': _orso_layer(thickness=Value(1.0, 'nm'), sld=2e-6), + } + model = _orso_sample_model('air | Si | 2 ( A | B )', layers) + with pytest.warns(UserWarning, match='Last ORSO stack item is a sub-stack'): + sample = _sample_from(_write_ort(tmp_path / 'last_sub.ort', model)) + assert sample[-1].name == 'Subphase' + assert sample[-1].layers[0].name == 'B' + + def test_unresolvable_stack_raises(self, tmp_path): + model = _orso_sample_model('') + with pytest.raises(ValueError, match='Could not resolve ORSO layers'): + _sample_from(_write_ort(tmp_path / 'empty_stack.ort', model)) + + def test_single_layer_stack_raises(self, tmp_path): + model = _orso_sample_model('air') + with pytest.raises(ValueError, match='at least 2 layers'): + _sample_from(_write_ort(tmp_path / 'one_layer.ort', model)) + + +class TestMaterialAndUnitFallbacks: + def test_layer_without_name_or_formula_falls_back_to_material(self, tmp_path): + # Layers declared inline in a sub-stack sequence carry no + # `original_name`, and an SLD-only material has no formula either, so + # neither the layer nor the material name may end up None. + sub_stack = model_language.SubStack( + repetitions=2, + sequence=[ + _orso_layer(thickness=Value(2.0, 'nm'), sld=4e-6), + _orso_layer(thickness=Value(1.0, 'nm'), sld=2e-6), + ], + ) + model = _orso_sample_model('air | multi | Si', sub_stacks={'multi': sub_stack}) + sample = _sample_from(_write_ort(tmp_path / 'unnamed.ort', model)) + repeating = sample[1] + assert isinstance(repeating, RepeatingMultilayer) + assert [layer.name for layer in repeating.layers] == ['material', 'material'] + assert all(layer.material.name == 'material' for layer in repeating.layers) + + def test_unknown_length_unit_warns_and_value_used_as_angstrom(self, tmp_path): + layers = {'film': _orso_layer(thickness=Value(12.0, 'furlong'), sld=3e-6)} + model = _orso_sample_model('air | film | Si', layers) + with pytest.warns(UserWarning, match="Unknown ORSO length unit 'furlong'"): + sample = _sample_from(_write_ort(tmp_path / 'length_unit.ort', model)) + assert sample[1].layers[0].thickness.value == pytest.approx(12.0) + + def test_absent_thickness_is_zero_and_roughness_takes_the_global_default(self, tmp_path): + # An omitted thickness has no model-language default, so it reads as 0; + # an omitted roughness picks up `globals.roughness` (0.3 nm) instead. + layers = {'film': model_language.Layer(material=model_language.Material(sld=ComplexValue(real=3e-6)))} + model = _orso_sample_model('air | film | Si', layers) + sample = _sample_from(_write_ort(tmp_path / 'no_thickness.ort', model)) + film = sample[1].layers[0] + assert film.thickness.value == 0.0 + assert film.roughness.value == pytest.approx(3.0) + + def test_null_length_magnitude_is_zero(self, tmp_path): + # A Value carrying an explicit null magnitude must not become nan. + layers = { + 'film': model_language.Layer( + thickness=Value(None, 'nm'), + roughness=Value(None, 'nm'), + material=model_language.Material(sld=ComplexValue(real=3e-6)), + ) + } + model = _orso_sample_model('air | film | Si', layers) + sample = _sample_from(_write_ort(tmp_path / 'null_magnitude.ort', model)) + film = sample[1].layers[0] + assert film.thickness.value == 0.0 + assert film.roughness.value == 0.0 + + def test_sld_in_inverse_nm_squared_is_converted(self, tmp_path): + # 4e-4 1/nm^2 == 4e-6 1/angstrom^2 == 4.0 in the internal 10^-6 units. + layers = { + 'film': model_language.Layer( + thickness=Value(10.0, 'nm'), + material=model_language.Material(sld=ComplexValue(real=4.0e-4, unit='1/nm^2')), + ) + } + model = _orso_sample_model('air | film | Si', layers) + sample = _sample_from(_write_ort(tmp_path / 'sld_nm.ort', model)) + assert sample[1].layers[0].material.sld.value == pytest.approx(4.0) + + def test_unknown_sld_unit_warns(self, tmp_path): + layers = { + 'film': model_language.Layer( + thickness=Value(10.0, 'nm'), + material=model_language.Material(sld=ComplexValue(real=4.0e-6, unit='1/barn')), + ) + } + model = _orso_sample_model('air | film | Si', layers) + with pytest.warns(UserWarning, match="Unknown ORSO SLD unit '1/barn'"): + sample = _sample_from(_write_ort(tmp_path / 'sld_unit.ort', model)) + assert sample[1].layers[0].material.sld.value == pytest.approx(4.0) + + def test_large_sld_warns_about_units(self, tmp_path): + # 3.47 in a field specified as 1/angstrom^2 almost certainly means the + # writer stored 10^-6 A^-2; multiplied by 1e6 it becomes 3.47e6. + layers = { + 'film': model_language.Layer( + thickness=Value(10.0, 'nm'), + material=model_language.Material(sld=ComplexValue(real=3.47)), + ) + } + model = _orso_sample_model('air | film | Si', layers) + with pytest.warns(UserWarning, match='seems large for'): + _sample_from(_write_ort(tmp_path / 'sld_large.ort', model)) + + def test_mass_density_in_kg_per_m3_is_converted(self, tmp_path): + layers = {'Ni': _orso_layer(thickness=Value(10.0, 'nm'), formula='Ni', mass_density=Value(8900.0, 'kg/m^3'))} + model = _orso_sample_model('air | Ni | Si', layers) + sample = _sample_from(_write_ort(tmp_path / 'density_kg.ort', model)) + material = sample[1].layers[0].material + assert isinstance(material, MaterialDensity) + assert material.density.value == pytest.approx(8.9) + + def test_unknown_mass_density_unit_warns(self, tmp_path): + layers = {'Ni': _orso_layer(thickness=Value(10.0, 'nm'), formula='Ni', mass_density=Value(8.9, 'stone/gallon'))} + model = _orso_sample_model('air | Ni | Si', layers) + with pytest.warns(UserWarning, match="Unknown ORSO mass density unit 'stone/gallon'"): + sample = _sample_from(_write_ort(tmp_path / 'density_unit.ort', model)) + assert sample[1].layers[0].material.density.value == pytest.approx(8.9) + + +class TestBannerDiscriminatorEdgeCases: + def test_missing_file_is_not_orso(self, tmp_path): + assert is_orso_file(str(tmp_path / 'does_not_exist.ort')) is False + + def test_directory_is_not_orso(self, tmp_path): + assert is_orso_file(str(tmp_path)) is False + + def test_empty_file_is_not_orso(self, tmp_path): + empty = tmp_path / 'empty.ort' + empty.write_bytes(b'') + assert is_orso_file(str(empty)) is False + + +class TestExporterFallbacks: + def test_duplicate_layer_names_get_unique_stack_keys(self): + # Layers legitimately share a name; the stack keys must not collide, or + # one definition would silently overwrite the other. Three of them, so + # the counter has to advance past the first free suffix. + air = Layer(material=Material(sld=0.0, isld=0.0, name='air'), thickness=0, roughness=0, name='air') + films = [ + Layer(material=Material(sld=sld, isld=0.0, name='film'), thickness=thickness, roughness=3, name='film') + for sld, thickness in ((4.0, 20), (2.0, 40), (1.0, 60)) + ] + si = Layer(material=Material(sld=2.07, isld=0.0, name='Si'), thickness=0, roughness=3, name='Si') + sample = Sample( + Multilayer(air, name='Superphase'), + Multilayer(films, name='Loaded layer'), + Multilayer(si, name='Subphase'), + name='duplicates', + ) + orso_model = sample_to_orso_model(sample) + assert {'film', 'film_2', 'film_3'} <= set(orso_model.layers) + resolved = orso_model.resolve_to_layers() + assert len(resolved) == 5 + # every definition kept its own thickness + assert [layer.thickness.magnitude for layer in resolved[1:4]] == pytest.approx([20.0, 40.0, 60.0]) + + def test_density_material_exports_formula_and_mass_density(self): + air = Layer(material=Material(sld=0.0, isld=0.0, name='air'), thickness=0, roughness=0, name='air') + nickel = Layer( + material=MaterialDensity(chemical_structure='Ni', density=8.9, name='Ni'), + thickness=100, + roughness=3, + name='Ni', + ) + si = Layer(material=Material(sld=2.07, isld=0.0, name='Si'), thickness=0, roughness=3, name='Si') + sample = Sample( + Multilayer(air, name='Superphase'), + Multilayer(nickel, name='Loaded layer'), + Multilayer(si, name='Subphase'), + name='density', + ) + material = sample_to_orso_model(sample).layers['Ni'].material + # written as formula + density, not flattened to a numeric SLD + assert material.formula == 'Ni' + assert material.mass_density.magnitude == pytest.approx(8.9) + assert material.mass_density.unit == 'g/cm^3' + assert material.sld is None + + def test_sample_name_falls_back_to_the_model_sample_name(self, project, tmp_path): + # A dataset with no preserved header gets a synthesized one, whose + # sample name is empty until the model supplies it. + project.default_model() + project.models[0].sample.name = 'my sample' + dataset = DataSet1D(x=np.array([0.01, 0.02]), y=np.array([1.0, 0.5])) + out = tmp_path / 'named.ort' + save_orso_experiment(dataset, str(out), model=project.models[0]) + assert _load_orso_any(str(out))[0].info.data_source.sample.name == 'my sample' + + def test_polarized_export_synthesizes_absent_instrument_settings(self, project, tmp_path): + # Some files omit instrument_settings entirely; the exporter must create + # one to hang the channel polarization off, rather than raising. + project.default_model() + index = project.load_polarized_experiment_from_file(os.path.join(PATH_STATIC, 'polarized_2ch.ort')) + experiment = project.experiments[index] + for channel_dataset in experiment.channels.values(): + channel_dataset.orso_header['data_source']['measurement']['instrument_settings'] = None + + out = tmp_path / 'no_settings.ort' + save_orso_experiment(experiment, str(out)) + back = _load_orso_any(str(out)) + polarizations = [str(d.info.data_source.measurement.instrument_settings.polarization.value) for d in back] + assert polarizations == ['pp', 'mm'] diff --git a/tests/test_polarized_fitting.py b/tests/test_polarized_fitting.py index fad69925..d8fd3d11 100644 --- a/tests/test_polarized_fitting.py +++ b/tests/test_polarized_fitting.py @@ -422,6 +422,7 @@ def test_spin_flip_channel_of_non_magnetic_model_raises(self): class TestFitPolarized: + @pytest.mark.slow def test_two_channel_nsf_fit_recovers_rho_m(self): truth = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=270.0)) truth.interface = _refl1d_interface() @@ -442,6 +443,7 @@ def test_two_channel_nsf_fit_recovers_rho_m(self): assert all(result.success for result in results.values()) assert_allclose(rho_m.value, 2.5, atol=0.01) + @pytest.mark.slow def test_four_channel_fit_recovers_rho_m_and_theta_m(self): truth = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=45.0)) truth.interface = _refl1d_interface() @@ -466,6 +468,7 @@ def test_four_channel_fit_recovers_rho_m_and_theta_m(self): assert_allclose(magnetism.rho_m.value, 2.5, atol=0.02) assert_allclose(magnetism.theta_m.value, 45.0, atol=0.5) + @pytest.mark.slow def test_shared_structural_parameter_fitted_across_channels(self): truth = _magnetic_model(LayerMagnetism(rho_m=2.0, theta_m=270.0)) truth.interface = _refl1d_interface() @@ -606,6 +609,7 @@ def test_empty_experiment_list_is_rejected(self): with pytest.raises(ValueError, match='At least one experiment'): MultiFitter.for_experiments([]) + @pytest.mark.slow def test_prepared_fitter_recovers_rho_m_when_run(self): """The fitter is usable exactly like `fit_polarized`, but caller-driven.""" truth = _magnetic_model(LayerMagnetism(rho_m=2.5, theta_m=270.0)) diff --git a/tests/unit/test_bumps_constraints_shim.py b/tests/unit/test_bumps_constraints_shim.py index 2256266a..e9aefb73 100644 --- a/tests/unit/test_bumps_constraints_shim.py +++ b/tests/unit/test_bumps_constraints_shim.py @@ -82,6 +82,7 @@ def test_patches_both_consumer_namespaces_and_is_idempotent(self, monkeypatch): assert (minimizer_bumps.build_curve_problem, sampler_dream.build_curve_problem) == patched +@pytest.mark.slow class TestShimAgainstARealFit: def test_the_patched_entry_point_is_the_one_a_fit_calls(self): """A signature check would not catch patching the wrong namespace.""" diff --git a/tests/unit/test_inequality_constraints.py b/tests/unit/test_inequality_constraints.py index b24d5adf..be58f3cb 100644 --- a/tests/unit/test_inequality_constraints.py +++ b/tests/unit/test_inequality_constraints.py @@ -257,6 +257,7 @@ def test_old_project_files_without_inequalities_load(self): assert reloaded.inequality_constraints == [] +@pytest.mark.slow class TestInequalityFit: def test_bumps_fit_respects_inequality_and_lmfit_is_rejected(self): project, model = _two_layer_project() diff --git a/tests/unit/test_project_core.py b/tests/unit/test_project_core.py index 5a227255..91383dbc 100644 --- a/tests/unit/test_project_core.py +++ b/tests/unit/test_project_core.py @@ -105,15 +105,31 @@ def test_percentage_fwhm_fallback_when_q_variances_absent(self, project: Project class TestLoadOrsoFile: def test_load_orso_file_creates_model_and_experiment(self, project: Project): - with pytest.warns(UserWarning): + # example.ort has no sample.model -> default model; deprecated wrapper + # now goes through the DataSet1D + title + resolution path. + with pytest.warns((UserWarning, DeprecationWarning)): project.load_orso_file(os.path.join(PATH_STATIC, 'example.ort')) assert len(project.models) == 1 assert len(project.experiments) == 1 - assert project.experiments[0].name == 'Experiment from ORSO' + assert isinstance(project.experiments[0], DataSet1D) + assert project.experiments[0].name == 'Example data file from refnx docs' assert project.experiments[0].model is project.models[0] assert project._with_experiments is True + def test_load_orso_file_with_model_builds_sample_from_file(self, project: Project): + # Ni_example.ort carries a sample.model -> the model comes from the file. + with pytest.warns(DeprecationWarning): + project.load_orso_file(os.path.join(PATH_STATIC, 'Ni_example.ort')) + + assert len(project.models) == 1 + assert project.models[0].sample.name == 'Ni on Si' + assert len(project.experiments) == 1 + assert isinstance(project.experiments[0], DataSet1D) + assert project.experiments[0].model is project.models[0] + # The measured sQz feeds a Pointwise resolution function. + assert isinstance(project.models[0].resolution_function, Pointwise) + class TestModelData: def test_model_data_for_model_at_index_returns_reflectivity(self, project: Project): From 35c0aa10e4628d38f79d6ae718cd4fa08a49ed17 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 7 Sep 2026 12:59:43 +0200 Subject: [PATCH 28/38] Enable SLD dependence on material data (#403) * enable SLD dependence on material data * extending methods for use in ERA * code review issues addressed * ruff * make molecular weight a descriptor --- docs/docs/tutorials/basic/material_library.md | 28 ++++ .../elements/materials/material_density.py | 122 ++++++++++++++++-- .../materials/test_material_density.py | 100 +++++++++++++- 3 files changed, 239 insertions(+), 11 deletions(-) diff --git a/docs/docs/tutorials/basic/material_library.md b/docs/docs/tutorials/basic/material_library.md index 78bdfcf1..655263a6 100644 --- a/docs/docs/tutorials/basic/material_library.md +++ b/docs/docs/tutorials/basic/material_library.md @@ -49,6 +49,34 @@ si = MaterialDensity(chemical_structure=chemical_structure, density=2.65, name=' The density should be in units of grams per cubic centimeter and the scattering length is calculated from `'SiO2'`. +By default the `sld` and `isld` of a `MaterialDensity` are _dependent_ +parameters, recomputed from the density, the formula's scattering length +and its molecular weight whenever any of those change - so `density` is +the parameter to vary in a fit, and assigning to `sld` directly is not +possible. This coupling can be switched off per material with the +`sld_coupled` property: + +```python +si.sld_coupled = False # sld/isld become independent, keep their values +si.sld.fixed = False # ...and can now be fitted directly +``` + +While decoupled, changes to `density` (or the formula) no longer +propagate to the SLD, and the density, molecular weight and scattering +length no longer affect the reflectivity. Setting `sld_coupled = True` +restores the dependency and **recalculates** `sld`/`isld` from the +current formula and density, discarding any manually set or fitted +values. The coupling state - and, when decoupled, the manual SLD +values - survive serialization (`as_dict`/`from_dict`); dictionaries +from before this feature restore as coupled. + +Assigning a new `chemical_structure` updates both the scattering length +and the molecular weight; a formula that does not parse to at least one +known atom raises `ValueError` and leaves the material unchanged. + +Note that `molecular_weight` is a read-only descriptor, not a fit +parameter: it is fully determined by the formula. + ## MaterialSolvated Sometimes it is desirable to have a layer that consists of a material diff --git a/src/easyreflectometry/sample/elements/materials/material_density.py b/src/easyreflectometry/sample/elements/materials/material_density.py index 5be1cf9d..1d6d046e 100644 --- a/src/easyreflectometry/sample/elements/materials/material_density.py +++ b/src/easyreflectometry/sample/elements/materials/material_density.py @@ -6,6 +6,7 @@ import numpy as np from easyscience import global_object +from easyscience.variable import DescriptorNumber from easyscience.variable import Parameter from easyreflectometry.special.calculations import density_to_sld @@ -27,20 +28,53 @@ 'max': np.inf, 'fixed': True, }, + # A DescriptorNumber, not a Parameter: the molecular weight is a constant + # of the chemical formula (recomputed whenever the formula changes) and + # must never enter a fit — it is fully degenerate with density in the + # derived SLD (only the ratio density/molecular_weight is observable). 'molecular_weight': { 'description': 'The molecular weight of a material.', 'url': 'https://en.wikipedia.org/wiki/Molecular_mass', 'value': 28.02, 'unit': 'g / mole', - 'min': -np.inf, - 'max': np.inf, - 'fixed': True, }, } DEFAULTS.update(MATERIAL_DEFAULTS) class MaterialDensity(Material): + """A material defined by chemical formula and mass density. + + The scattering length density is derived rather than set: from the + formula, the coherent neutron scattering length ``b`` (real and + imaginary parts, tabulated per isotope) and the molecular weight ``M`` + are computed, and ``sld``/``isld`` are wired as *dependent* parameters + + sld = N_A * density * b / M + + so ``density`` is the natural fit parameter and edits to the density or + the formula propagate to the SLD automatically. + + The coupling can be switched off per material via :attr:`sld_coupled`: + when ``False``, ``sld``/``isld`` are independent parameters that can be + set and fitted directly, while ``density``, ``molecular_weight`` and the + scattering lengths stop affecting anything until the coupling is + restored. Restoring it (``sld_coupled = True``) recomputes the SLDs from + the current formula and density, discarding manually set values. The + state round-trips through ``as_dict``/``from_dict``, including the + manual SLD values of a decoupled material; dictionaries from before + this feature deserialize as coupled. + + Assigning :attr:`chemical_structure` updates the scattering lengths and + the molecular weight together; an invalid formula raises ``ValueError`` + and leaves the material unchanged. + + :attr:`molecular_weight` is a read-only ``DescriptorNumber``, never a fit + parameter: it is fully determined by the formula, and freeing it alongside + density would make the fit degenerate (only ``density / molecular_weight`` + enters the derived SLD). + """ + def __init__( self, chemical_structure: Union[str, None] = None, @@ -79,11 +113,13 @@ def __init__( scattering_length = neutron_scattering_length(chemical_structure) - mw = get_as_parameter( + mw = DescriptorNumber( name='molecular_weight', value=molecular_weight(chemical_structure), - default_dict=DEFAULTS, - unique_name_prefix=f'{unique_name}_Mw', + unit=DEFAULTS['molecular_weight']['unit'], + description=DEFAULTS['molecular_weight']['description'], + url=DEFAULTS['molecular_weight']['url'], + unique_name=global_object.generate_unique_name(f'{unique_name}_Mw'), ) scattering_length_real = get_as_parameter( name='scattering_length_real', @@ -157,6 +193,41 @@ def _setup_sld_constraints(self) -> None: }, ) + @property + def sld_coupled(self) -> bool: + """Whether ``sld``/``isld`` are derived from formula & density (True, + the default) or independent, directly editable/fittable parameters + (False). The dependency state itself is the source of truth.""" + return not self._sld.independent + + @sld_coupled.setter + def sld_coupled(self, couple: bool) -> None: + if couple == self.sld_coupled: + return + if couple: + # Recomputes sld/isld from the current density/scattering + # length/molecular weight — manually set values are discarded. + self._setup_sld_constraints() + else: + # make_independent raises on an already-independent parameter, + # so guard each individually. Values are kept. + for parameter in (self._sld, self._isld): + if not parameter.independent: + parameter.make_independent() + + def _convert_to_dict(self, d: dict, serializer, skip: Optional[list] = None, **kwargs) -> dict: + """Serializer hook (see ``SerializerBase._convert_to_dict``). + + ``sld``/``isld`` are not constructor arguments, so the argspec-driven + encoder never persists them; in the decoupled state their manually + entered or fitted values would be lost on save/load without this. + """ + d['sld_coupled'] = self.sld_coupled + if not self.sld_coupled: + d['sld'] = self._sld.value + d['isld'] = self._isld.value + return d + @classmethod def from_dict(cls, obj_dict: dict) -> 'MaterialDensity': """Re-attach sld/isld dependencies after deserialization. @@ -166,9 +237,28 @@ def from_dict(cls, obj_dict: dict) -> 'MaterialDensity': the constraint graph built in `__init__` still references the temporary Parameter created from the float kwarg. Rebuild here so `q.density = X` propagates to the derived SLDs. + + The keys written by ``_convert_to_dict`` are not constructor + arguments and must be removed before the parent's ``cls(**data)`` + call; they are then used to restore the coupling state. A dict + without them (pre-feature project files) restores as coupled. """ + obj_dict = dict(obj_dict) + sld_coupled = obj_dict.pop('sld_coupled', True) + manual_sld = obj_dict.pop('sld', None) + manual_isld = obj_dict.pop('isld', None) + instance = super().from_dict(obj_dict) - instance._setup_sld_constraints() + if sld_coupled: + instance._setup_sld_constraints() + else: + # __init__ wired the dependencies; undo them and restore the + # saved manual values. + instance.sld_coupled = False + if manual_sld is not None: + instance._sld.value = manual_sld + if manual_isld is not None: + instance._isld.value = manual_isld return instance @property @@ -185,10 +275,21 @@ def chemical_structure(self, structure_string: str) -> None: structure_string : str String that defines the chemical structure. """ - self._chemical_structure = structure_string + # Derive everything before mutating any state: an invalid formula + # must leave the material fully unchanged. periodictable parses + # garbage to an *empty* formula (b=0, mw=0) instead of raising, and + # mw=0 would put a division by zero into the sld dependency. scattering_length = neutron_scattering_length(structure_string) + # The molecular weight enters the sld dependency alongside the + # scattering length; leaving it at the old formula's value would make + # the derived sld a mix of two formulas. + mw = molecular_weight(structure_string) + if not mw: + raise ValueError(f'Invalid chemical formula: {structure_string!r}') + self._chemical_structure = structure_string self._scattering_length_real.value = scattering_length.real self._scattering_length_imag.value = scattering_length.imag + self._molecular_weight.value = mw @property def density(self) -> Parameter: @@ -199,7 +300,10 @@ def density(self, value: float) -> None: self._density.value = value @property - def molecular_weight(self) -> Parameter: + def molecular_weight(self) -> DescriptorNumber: + """The molecular weight of the formula. A read-only descriptor, not a + fittable parameter: it is a constant of the chemical formula and is + recomputed whenever :attr:`chemical_structure` is assigned.""" return self._molecular_weight @property diff --git a/tests/sample/elements/materials/test_material_density.py b/tests/sample/elements/materials/test_material_density.py index 0b424cad..11a5677e 100644 --- a/tests/sample/elements/materials/test_material_density.py +++ b/tests/sample/elements/materials/test_material_density.py @@ -43,8 +43,11 @@ def test_chemical_structure_change(self): assert p.chemical_structure == 'Co' p.chemical_structure = 'B' assert p.density.value == 8.9 - assert_almost_equal(p.sld.value, 4.82010833570636) - assert_almost_equal(p.isld.value, -0.19098540517806603) + # The setter updates the molecular weight along with the scattering + # lengths; the derived sld reflects boron's mw, not cobalt's. + assert_almost_equal(p.molecular_weight.value, 10.81) + assert_almost_equal(p.sld.value, 26.277925961998147) + assert_almost_equal(p.isld.value, -1.0412008400037) assert p.chemical_structure == 'B' def test_dict_repr(self): @@ -65,6 +68,99 @@ def test_dict_round_trip(self): assert sorted(p.as_dict()) == sorted(q.as_dict()) + def test_chemical_structure_invalid_formula_leaves_material_unchanged(self): + p = MaterialDensity('Co', 8.9, 'Cobalt') + mw = p.molecular_weight.value + sld = p.sld.value + with self.assertRaises(ValueError): + p.chemical_structure = '###' + assert p.chemical_structure == 'Co' + assert_almost_equal(p.molecular_weight.value, mw) + assert_almost_equal(p.sld.value, sld) + + def test_sld_coupled_default_true(self): + p = MaterialDensity() + assert p.sld_coupled is True + assert p.sld.independent is False + assert p.isld.independent is False + + def test_decouple_keeps_values_and_detaches_density(self): + p = MaterialDensity(chemical_structure='Si', density=2.33) + coupled_sld = p.sld.value + p.sld_coupled = False + assert p.sld_coupled is False + assert p.sld.independent is True + assert p.isld.independent is True + assert_almost_equal(p.sld.value, coupled_sld) + # Density edits no longer propagate; sld is directly settable. + p.density.value = 9.99 + assert_almost_equal(p.sld.value, coupled_sld) + p.sld.value = 5.5 + assert p.sld.value == 5.5 + + def test_recouple_recomputes_and_discards_manual_sld(self): + p = MaterialDensity(chemical_structure='Si', density=2.33) + coupled_sld = p.sld.value + p.sld_coupled = False + p.sld.value = 5.5 + p.sld_coupled = True + assert p.sld_coupled is True + assert_almost_equal(p.sld.value, coupled_sld) + # And propagation is restored. + p.density.value = 4.66 + assert_almost_equal(p.sld.value, 2 * coupled_sld) + + def test_sld_coupled_setter_is_idempotent(self): + p = MaterialDensity() + p.sld_coupled = True # no-op, must not raise or rewire + p.sld_coupled = False + p.sld_coupled = False # no-op on the decoupled side too + assert p.sld_coupled is False + + def test_decoupled_sld_can_be_freed_for_fitting(self): + p = MaterialDensity() + p.sld_coupled = False + p.sld.fixed = False + free = p.get_fit_parameters() + assert any(parameter is p.sld for parameter in free) + + def test_dict_round_trip_decoupled_preserves_manual_sld(self): + p = MaterialDensity(chemical_structure='Si', density=2.33) + p.sld_coupled = False + p.sld.value = 7.25 + p.isld.value = -0.5 + p_dict = p.as_dict() + assert p_dict['sld_coupled'] is False + assert p_dict['sld'] == 7.25 + assert p_dict['isld'] == -0.5 + global_object.map._clear() + + q = MaterialDensity.from_dict(p_dict) + assert q.sld_coupled is False + assert_almost_equal(q.sld.value, 7.25) + assert_almost_equal(q.isld.value, -0.5) + # Still decoupled: density edits must not clobber the restored values. + q.density.value = 1.0 + assert_almost_equal(q.sld.value, 7.25) + + def test_dict_round_trip_coupled_carries_flag_but_no_sld(self): + p = MaterialDensity() + p_dict = p.as_dict() + assert p_dict['sld_coupled'] is True + assert 'sld' not in p_dict + assert 'isld' not in p_dict + + def test_from_dict_without_flag_restores_coupled(self): + """Project files predating the feature restore with current behavior.""" + p = MaterialDensity(chemical_structure='Si', density=2.33) + p_dict = {k: v for k, v in p.as_dict().items() if k != 'sld_coupled'} + global_object.map._clear() + + q = MaterialDensity.from_dict(p_dict) + assert q.sld_coupled is True + q.density.value = 4.66 + assert_almost_equal(q.sld.value, 2 * p.sld.value) + def test_density_mutation_propagates_after_round_trip(self): """Regression: after ``from_dict`` reattaches the saved ``_density`` Parameter, mutating it must propagate to ``sld`` / ``isld`` (which From eeb5957dc28609df93def6107b5419969089651f Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Wed, 9 Sep 2026 10:21:19 +0200 Subject: [PATCH 29/38] Improvements to the state tracking #401 (#405) * Improvements to the state tracking #401 * PR issues addressed * minor ruff NOQA * reparent to develop of core and fixed the functionality --- CHANGELOG.md | 18 +++ pixi.lock | 24 +-- pyproject.toml | 2 +- src/easyreflectometry/_bumps_constraints.py | 81 ++++++----- .../inequality_constraints.py | 7 +- src/easyreflectometry/project.py | 118 +++++++++++---- tests/test_project.py | 137 +++++++++++++++++- tests/unit/test_bumps_constraints_shim.py | 32 ++-- 8 files changed, 325 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1fdc054..850152e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Unreleased +## Project persistence + +- `Project.save_as_json` now writes atomically: the file is serialized + first, written to a temporary file in the project directory, synced + and moved into place, so a failure while serializing or writing leaves + a previously saved `project.json` untouched. An existing file keeps + its permissions; a new one gets the ordinary umask-derived mode + instead of the owner-only mode of a temporary file. +- Behaviour change: `save_as_json`, `create` and `load_from_json` raise + instead of printing to stdout. `save_as_json` raises `FileExistsError` + when the file exists and `overwrite` is False, `ValueError` when the + project cannot be serialized (a `TypeError` from the encoder is + reported as `ValueError` too) and `OSError` when the file cannot be + written. `create` raises `FileExistsError` when the project directory + already exists and `load_from_json` raises `FileNotFoundError` when + there is no file at the path. Scripts that relied on these calls + silently continuing must catch the exceptions. + ## Parameter constraints - New equality-constraint helpers `constrain`, `constrain_equal`, diff --git a/pixi.lock b/pixi.lock index b2006eae..e69d3faf 100644 --- a/pixi.lock +++ b/pixi.lock @@ -203,7 +203,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -514,7 +514,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -815,7 +815,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -1138,7 +1138,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1443,7 +1443,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1738,7 +1738,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2061,7 +2061,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2372,7 +2372,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2673,7 +2673,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 + - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -8315,7 +8315,7 @@ packages: requires_dist: - asteval - bumps - - easyscience @ git+https://github.com/easyscience/easyscience.git@sampler-engine-structure-280 + - easyscience @ git+https://github.com/easyscience/easyscience.git@develop - orsopy>=1.2 - plotly - pooch @@ -8360,9 +8360,9 @@ packages: - versioningit ; extra == 'dev' - h5py ; extra == 'orb' requires_python: '>=3.11' -- pypi: git+https://github.com/easyscience/easyscience.git?rev=sampler-engine-structure-280#3e598107beb2de6820e0e4c0a68da8e3c9af4ae4 +- pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae name: easyscience - version: 2.5.1+dev7 + version: 2.5.1+dev2 requires_dist: - asteval - bumps>=1.0.4 diff --git a/pyproject.toml b/pyproject.toml index b000ceb9..cc0fb6d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'easyscience @ git+https://github.com/easyscience/easyscience.git@sampler-engine-structure-280', + 'easyscience @ git+https://github.com/easyscience/easyscience.git@develop', 'scipp', 'refnx', 'refl1d>=1.0.0', diff --git a/src/easyreflectometry/_bumps_constraints.py b/src/easyreflectometry/_bumps_constraints.py index c76256df..e65795b8 100644 --- a/src/easyreflectometry/_bumps_constraints.py +++ b/src/easyreflectometry/_bumps_constraints.py @@ -5,21 +5,22 @@ EasyScience builds the ``FitProblem`` internally and offers no hook for attaching inequality penalties to it, so the enforcement lives here -rather than in the core: :func:`install` wraps ``build_curve_problem`` -and :func:`applied` makes a factory current for the duration of a fit, -attaching the constraints to each freshly built problem. +rather than in the core: :func:`install` wraps the ``FitDriver`` name in +the core's ``minimizer_bumps`` namespace and :func:`applied` makes a +factory current for the duration of a fit, attaching the constraints to +each problem as it is handed to the driver. ``FitProblem.constraints`` is a plain list read live by -``constraints_nllf()``, and ``build_curve_problem`` returns the ``Curve`` +``constraints_nllf()``, and the problem's single model is the ``Curve`` whose ``.pars`` is the very ``{prefixed name: BumpsParameter}`` mapping -the factory expects — so attaching after construction is equivalent to -passing ``constraints=`` to the constructor. +the factory expects — so attaching before the driver runs is equivalent +to passing ``constraints=`` to the ``FitProblem`` constructor. -Only problems built by ``build_curve_problem`` are covered. Calling -``Bumps.fit`` with a caller-supplied ``model=`` bypasses it (that branch -constructs ``FitProblem(model)`` itself) and the constraints would be -dropped. The library never passes ``model=``, so this is reachable only -by driving the core minimizer directly. +Both ``Bumps.fit`` and ``Bumps.mcmc_sample`` construct their driver via +the module-level ``FitDriver`` binding in ``minimizer_bumps``, so one +patched namespace covers classical fitting and DREAM sampling alike. +Only problems whose model exposes a ``.pars`` mapping are touched; any +other problem passes through the shim untouched. Nothing here is passed to the core as a keyword: ``Bumps.fit`` accepts arbitrary ``**kwargs`` and would swallow an unrecognised one without @@ -31,13 +32,11 @@ import contextlib import contextvars -import functools from typing import Callable from typing import Iterator from typing import Optional from easyscience.fitting.minimizers import minimizer_bumps -from easyscience.fitting.samplers import sampler_dream #: Raised for engines that cannot enforce inequality constraints. The wording is #: matched by the test suite and printed in the constraints tutorial, so keep it @@ -51,38 +50,52 @@ ) +def _attach_constraints(problem) -> None: + """Attach the active factory's constraints to a freshly built problem.""" + factory = _active.get() + if factory is None or problem is None: + return + model = next(iter(problem.models), None) + pars = getattr(model, 'pars', None) + if pars is None: + return + # ``model.pars`` is empty when no parameter is free; the factory then + # yields constant-only penalties, which is harmless. + problem.constraints = list(factory(dict(pars))) + # Only to get the warning BUMPS emits for an infeasible start point; + # the penalties themselves are already live without this. + problem.model_reset() + + def _patch(module) -> None: - """Wrap ``build_curve_problem`` in one consumer namespace.""" - original = module.build_curve_problem + """Wrap ``FitDriver`` in one consumer namespace.""" + original = module.FitDriver if getattr(original, '_easyreflectometry_shim', False): return - @functools.wraps(original) - def build_curve_problem(*args, **kwargs): - problem, fit_function, curve = original(*args, **kwargs) - factory = _active.get() - if factory is not None: - # ``curve.pars`` is empty when no parameter is free; the factory - # then yields constant-only penalties, which is harmless. - problem.constraints = list(factory(dict(curve.pars))) - # Only to get the warning BUMPS emits for an infeasible start - # point; the penalties themselves are already live without this. - problem.model_reset() - return problem, fit_function, curve + def FitDriver(*args, **kwargs): + # Both core call sites pass ``problem=`` by keyword; the positional + # fallback covers a caller using BUMPS' (fitclass, problem) order. + problem = kwargs.get('problem', args[1] if len(args) > 1 else None) + _attach_constraints(problem) + return original(*args, **kwargs) - build_curve_problem._easyreflectometry_shim = True - module.build_curve_problem = build_curve_problem + FitDriver.__name__ = getattr(original, '__name__', 'FitDriver') + FitDriver.__qualname__ = FitDriver.__name__ + FitDriver.__doc__ = getattr(original, '__doc__', None) + FitDriver.__wrapped__ = original + FitDriver._easyreflectometry_shim = True + module.FitDriver = FitDriver def install() -> None: - """Patch the fitting and sampling entry points. + """Patch the BUMPS driver entry point. - Both consumers bind ``build_curve_problem`` at import time, so each - namespace has to be patched; patching the defining module alone has no - effect. Idempotent. + ``minimizer_bumps`` binds ``FitDriver`` at import time and both + ``Bumps.fit`` and ``Bumps.mcmc_sample`` read the name from that + namespace, so patching it covers fitting and sampling. Idempotent. """ _patch(minimizer_bumps) - _patch(sampler_dream) def is_applied() -> bool: diff --git a/src/easyreflectometry/inequality_constraints.py b/src/easyreflectometry/inequality_constraints.py index 75158222..984af62f 100644 --- a/src/easyreflectometry/inequality_constraints.py +++ b/src/easyreflectometry/inequality_constraints.py @@ -32,8 +32,9 @@ which BUMPS sets for every trial point. * Those BUMPS parameters only exist for the *free* EasyScience parameters and are rebuilt per fit, which is why a *factory* (``constraints_factory``) - is passed down the fitting chain and invoked by easyscience's - ``build_curve_problem``. Fixed parameters are frozen as constants; + is passed down the fitting chain and invoked as each BUMPS problem is + built (see :mod:`easyreflectometry._bumps_constraints`). Fixed parameters + are frozen as constants; dependent (constrained or derived) parameters are expanded recursively into their independent leaves. * Each penalty term returns the *linear* violation; BUMPS squares it once, @@ -85,7 +86,7 @@ class UnitError(ValueError): _RELATION_ALIASES = {'≤': '<=', '≥': '>=', '=<': '<=', '=>': '>='} #: BUMPS prefixes every EasyScience parameter name; must match -#: ``easyscience.fitting.engine_base.PARAMETER_PREFIX``. +#: ``easyscience.fitting.minimizers.minimizer_base.MINIMIZER_PARAMETER_PREFIX``. _BUMPS_PREFIX = 'p' _SAFE_SYMBOLS = { diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 21ef26ee..27c75371 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -5,6 +5,8 @@ import json import logging import os +import stat +import tempfile import warnings import weakref from pathlib import Path @@ -1776,42 +1778,106 @@ def _default_info(self): ) def create(self): - """Create function.""" - if not os.path.exists(self.path): - os.makedirs(self.path) - os.makedirs(self.path / 'experiments') - self._created = True - self._timestamp_modification() - else: - print(f'ERROR: Directory {self.path} already exists') + """Create the project directory tree on disk. + + :raises FileExistsError: if the project directory already exists. Nothing is written in + that case and `created` stays False, so a caller cannot mistake a collision for a new + project. + """ + if os.path.exists(self.path): + raise FileExistsError(f'Directory {self.path} already exists. Choose a different name or location.') + os.makedirs(self.path) + os.makedirs(self.path / 'experiments') + self._created = True + self._timestamp_modification() def save_as_json(self, overwrite=False): - """Save as json.""" - if self.path_json.exists() and overwrite: - print(f'File already exists {self.path_json}. Overwriting...') - self.path_json.unlink() + """Save the project as json. + + The write is atomic: the serialized project is written to a temporary file in the + destination directory and then moved into place with `os.replace`. Any failure while + serializing or writing therefore leaves a previously saved project file untouched. + + Failures are raised rather than reported on stdout, so that callers (notably the GUI) + can tell a successful save from a failed one. + + :param overwrite: whether an existing project file may be replaced. + :raises FileExistsError: if the project file exists and `overwrite` is False. + :raises ValueError: if the project cannot be serialized, e.g. when a constraint depends + on a parameter that is not reachable from the models. + :raises OSError: if the file cannot be written or moved into place. + """ + if self.path_json.exists() and not overwrite: + raise FileExistsError(f'File already exists {self.path_json}. Pass overwrite=True to replace it.') + # Serialize before touching the file system, so that a serialization failure + # leaves no temporary file behind and no doubt about the existing file. try: project_json = json.dumps(self.as_dict(include_materials_not_in_model=True), indent=4) - self.path_json.parent.mkdir(exist_ok=True, parents=True) - with open(self.path_json, mode='x') as file: + except TypeError as error: + # json.dumps reports a value it cannot encode (a stray numpy scalar, say) as a + # TypeError; to the caller that is the same "cannot be serialized" failure. + raise ValueError(f'The project cannot be serialized: {error}') from error + self.path_json.parent.mkdir(exist_ok=True, parents=True) + # The temporary file must share a directory with the destination, otherwise the + # replace below is not guaranteed to be atomic (and fails across volumes on Windows). + # The name is not a dot-file so that a leftover is visible while debugging. + file_descriptor, temporary_name = tempfile.mkstemp( + dir=self.path_json.parent, prefix=f'{self.path_json.name}.', suffix='.tmp' + ) + temporary_path = Path(temporary_name) + try: + with os.fdopen(file_descriptor, mode='w') as file: file.write(project_json) - except Exception as exception: - print(exception) + file.flush() + # Rename without fsync can leave an empty file behind after a power loss. + os.fsync(file.fileno()) + self._apply_project_file_mode(temporary_path) + os.replace(temporary_path, self.path_json) + except Exception: + temporary_path.unlink(missing_ok=True) + raise + logger.debug(f'Saved project to {self.path_json}') + + def _apply_project_file_mode(self, temporary_path: Path) -> None: + """Give the temporary file the permissions the project file should end up with. + + `tempfile.mkstemp` creates files owner-only (0600) and `os.replace` carries that mode + onto the destination. Without this step every save on POSIX would strip group and other + read access, and overwriting a shared 0644 project file would silently make it private. + An existing file keeps its mode; a new file gets the ordinary umask-derived one. + """ + if self.path_json.exists(): + mode = stat.S_IMODE(self.path_json.stat().st_mode) + else: + umask = os.umask(0) + os.umask(umask) + mode = 0o666 & ~umask + try: + os.chmod(temporary_path, mode) + except OSError: + # Some file systems refuse chmod (network shares, some Windows mounts); the write + # itself is still fine, so this is not a reason to fail the save. + logger.debug(f'Could not set the mode of {temporary_path}') def load_from_json(self, path: Optional[Union[Path, str]] = None): - """Load from json.""" + """Load a project file, replacing the current project state. + + :param path: the project file; defaults to this project's own `path_json`. + :raises FileNotFoundError: if there is no file at `path`. The current project is left + untouched. + :raises ValueError: if the file is not valid JSON or predates the supported file format. + """ if path is None: path = self.path_json path = Path(path) - if path.exists(): - with open(path, 'r') as file: - project_dict = json.load(file) - self.reset() - self.from_dict(project_dict) - self._path_project_parent = path.parents[1] - self._created = True - else: - print(f'ERROR: File {path} does not exist') + if not path.exists(): + raise FileNotFoundError(f'File {path} does not exist') + with open(path, 'r') as file: + project_dict = json.load(file) + self.reset() + self.from_dict(project_dict) + self._path_project_parent = path.parents[1] + self._created = True #: Schema version embedded in every serialized project. Bumped from 1 → 2 #: when the sample/model classes migrated from the legacy diff --git a/tests/test_project.py b/tests/test_project.py index bdfbc0ce..63e0d3fb 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -3,8 +3,10 @@ import datetime import os +import sys from pathlib import Path from unittest.mock import MagicMock +from unittest.mock import patch import numpy as np import pytest @@ -589,11 +591,111 @@ def test_save_as_json_dont_overwrite(self, tmp_path): # Then project._info['short_description'] = 'short_description' project.default_model() - project.save_as_json() + with pytest.raises(FileExistsError): + project.save_as_json() # Expect assert str(file_info) == str(project.path_json.stat()) + def test_save_as_json_serialization_failure_keeps_previous_file(self, tmp_path): + # When + global_object.map._clear() + project = Project() + project.set_path_project_parent(tmp_path) + project.save_as_json() + previous_content = project.path_json.read_text() + + # Then + project.as_dict = MagicMock(side_effect=ValueError('unreachable constraint parameter')) + with pytest.raises(ValueError): + project.save_as_json(overwrite=True) + + # Expect: the previous save survives and no temporary file is left behind + assert project.path_json.exists() + assert project.path_json.read_text() == previous_content + assert list(project.path_json.parent.glob('*.tmp')) == [] + + def test_save_as_json_write_failure_keeps_previous_file(self, tmp_path): + # When + global_object.map._clear() + project = Project() + project.set_path_project_parent(tmp_path) + project.save_as_json() + previous_content = project.path_json.read_text() + + # Then: the destination cannot be replaced, e.g. a locked file on Windows + with patch('easyreflectometry.project.os.replace', side_effect=PermissionError('locked')): + with pytest.raises(PermissionError): + project.save_as_json(overwrite=True) + + # Expect + assert project.path_json.read_text() == previous_content + assert list(project.path_json.parent.glob('*.tmp')) == [] + + def test_save_as_json_reports_unserializable_content_as_value_error(self, tmp_path): + # When + global_object.map._clear() + project = Project() + project.set_path_project_parent(tmp_path) + project.save_as_json() + previous_content = project.path_json.read_text() + + # Then: json.dumps raises TypeError for a value it cannot encode + project.as_dict = MagicMock(return_value={'info': object()}) + with pytest.raises(ValueError, match='cannot be serialized'): + project.save_as_json(overwrite=True) + + # Expect + assert project.path_json.read_text() == previous_content + assert list(project.path_json.parent.glob('*.tmp')) == [] + + @pytest.mark.skipif(sys.platform == 'win32', reason='POSIX file modes') + def test_save_as_json_keeps_the_file_mode_of_an_existing_project_file(self, tmp_path): + # When + global_object.map._clear() + project = Project() + project.set_path_project_parent(tmp_path) + project.save_as_json() + # Group-writable on purpose: distinguishable from the umask default a new file would get. + os.chmod(project.path_json, 0o664) # noqa: S103 + + # Then + project._info['short_description'] = 'short_description' + project.save_as_json(overwrite=True) + + # Expect: the temporary file's 0600 must not leak onto the project file + assert project.path_json.stat().st_mode & 0o777 == 0o664 + + @pytest.mark.skipif(sys.platform == 'win32', reason='POSIX file modes') + def test_save_as_json_new_file_is_not_owner_only(self, tmp_path): + # When + global_object.map._clear() + project = Project() + project.set_path_project_parent(tmp_path) + previous_umask = os.umask(0o022) + try: + project.save_as_json() + finally: + os.umask(previous_umask) + + # Expect + assert project.path_json.stat().st_mode & 0o777 == 0o644 + + def test_save_as_json_overwrite_replaces_content(self, tmp_path): + # When + global_object.map._clear() + project = Project() + project.set_path_project_parent(tmp_path) + project.save_as_json() + + # Then + project._info['short_description'] = 'short_description' + project.save_as_json(overwrite=True) + + # Expect + assert 'short_description' in project.path_json.read_text() + assert list(project.path_json.parent.glob('*.tmp')) == [] + def test_load_from_json(self, tmp_path): # When global_object.map._clear() @@ -627,6 +729,22 @@ def test_load_from_json(self, tmp_path): assert new_project._path_project_parent == tmp_path assert new_project.created is True + def test_load_from_json_missing_file_raises_and_keeps_the_project(self, tmp_path): + # When + global_object.map._clear() + project = Project() + project.default_model() + project._info['name'] = 'untouched' + + # Then + with pytest.raises(FileNotFoundError): + project.load_from_json(tmp_path / 'nowhere' / 'project.json') + + # Expect + assert project._info['name'] == 'untouched' + assert len(project._models) == 1 + assert project.created is False + def test_create(self, tmp_path): # When project = Project() @@ -648,6 +766,23 @@ def test_create(self, tmp_path): 'modified': datetime.datetime.now().strftime('%d.%m.%Y %H:%M'), } + def test_create_existing_directory_raises(self, tmp_path): + # When + project = Project() + project.set_path_project_parent(tmp_path) + project._info['name'] = 'TestProject' + project._info['modified'] = 'modified' + (tmp_path / 'TestProject').mkdir() + + # Then + with pytest.raises(FileExistsError): + project.create() + + # Expect: nothing was made and the project does not claim to exist on disk + assert project.created is False + assert not (project.path / 'experiments').exists() + assert project._info['modified'] == 'modified' + def test_load_experiment(self): # When global_object.map._clear() diff --git a/tests/unit/test_bumps_constraints_shim.py b/tests/unit/test_bumps_constraints_shim.py index e9aefb73..f5bc5b61 100644 --- a/tests/unit/test_bumps_constraints_shim.py +++ b/tests/unit/test_bumps_constraints_shim.py @@ -10,7 +10,6 @@ from easyscience import global_object from easyscience.fitting import AvailableMinimizers from easyscience.fitting.minimizers import minimizer_bumps -from easyscience.fitting.samplers import sampler_dream from easyreflectometry import _bumps_constraints from easyreflectometry.data import DataSet1D @@ -53,33 +52,32 @@ def unpatched_modules(): """`install()` mutates the core modules and has no undo; keep these tests independent. Unwraps a shim left behind by an earlier test so the assertions start from - the core's own builder, and puts that state back afterwards. + BUMPS' own driver, and puts that state back afterwards. """ originals = [] - for module in (minimizer_bumps, sampler_dream): - builder = module.build_curve_problem - while getattr(builder, '_easyreflectometry_shim', False): - builder = builder.__wrapped__ - originals.append((module, builder)) - module.build_curve_problem = builder + for module in (minimizer_bumps,): + driver = module.FitDriver + while getattr(driver, '_easyreflectometry_shim', False): + driver = driver.__wrapped__ + originals.append((module, driver)) + module.FitDriver = driver yield - for module, builder in originals: - module.build_curve_problem = builder + for module, driver in originals: + module.FitDriver = driver @pytest.mark.usefixtures('unpatched_modules') class TestInstall: - def test_patches_both_consumer_namespaces_and_is_idempotent(self, monkeypatch): - """Both consumers bind the name at import, so each has to be patched.""" - monkeypatch.setattr(minimizer_bumps, 'build_curve_problem', minimizer_bumps.build_curve_problem) - monkeypatch.setattr(sampler_dream, 'build_curve_problem', sampler_dream.build_curve_problem) + def test_patches_the_consumer_namespace_and_is_idempotent(self, monkeypatch): + """The consumer binds ``FitDriver`` at import, so its namespace is patched.""" + monkeypatch.setattr(minimizer_bumps, 'FitDriver', minimizer_bumps.FitDriver) _bumps_constraints.install() - patched = (minimizer_bumps.build_curve_problem, sampler_dream.build_curve_problem) - assert all(getattr(function, '_easyreflectometry_shim', False) for function in patched) + patched = minimizer_bumps.FitDriver + assert getattr(patched, '_easyreflectometry_shim', False) _bumps_constraints.install() - assert (minimizer_bumps.build_curve_problem, sampler_dream.build_curve_problem) == patched + assert minimizer_bumps.FitDriver is patched @pytest.mark.slow From 4d4160f62933812d07962386f176e69242cf77b7 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 11 Sep 2026 11:05:50 +0200 Subject: [PATCH 30/38] minor material editor fix (#408) --- .../sample/collections/material_collection.py | 4 ++-- .../collections/test_material_collection.py | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/easyreflectometry/sample/collections/material_collection.py b/src/easyreflectometry/sample/collections/material_collection.py index 726678b2..bb5902db 100644 --- a/src/easyreflectometry/sample/collections/material_collection.py +++ b/src/easyreflectometry/sample/collections/material_collection.py @@ -68,6 +68,6 @@ def duplicate_material(self, index: int): Assembly to add. """ to_be_duplicated = self[index] - duplicate = Material.from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) + duplicate = type(to_be_duplicated).from_dict(to_be_duplicated.as_dict(skip=['unique_name'])) duplicate.name = duplicate.name + ' duplicate' - self.append(duplicate) + self.add_material(duplicate) diff --git a/tests/sample/collections/test_material_collection.py b/tests/sample/collections/test_material_collection.py index 3ca9f22b..a80fb4cc 100644 --- a/tests/sample/collections/test_material_collection.py +++ b/tests/sample/collections/test_material_collection.py @@ -5,10 +5,12 @@ Tests for LayerCollection class. """ +import pytest from easyscience import global_object from easyreflectometry.sample.collections.material_collection import MaterialCollection from easyreflectometry.sample.elements.materials.material import Material +from easyreflectometry.sample.elements.materials.material_density import MaterialDensity class TestMaterialCollection: @@ -100,3 +102,21 @@ def test_duplicate_material(self): # Expect assert p[4].name == 'Boron duplicate' + + def test_duplicate_material_of_a_subclass(self): + # When + p = MaterialCollection() + m = MaterialDensity('Si', 2.33, 'Silicon') + p.add_material(m) + + # Then + p.duplicate_material(3) + + # Expect + # The copy has to come back as its own class: rebuilding it as `Material` raised, + # so duplicating a density material was not a lossy copy but an outright failure. + assert isinstance(p[4], MaterialDensity) + assert p[4].name == 'Silicon duplicate' + assert p[4].chemical_structure == 'Si' + assert p[4].density.value == 2.33 + assert p[4].sld.value == pytest.approx(p[3].sld.value) From 10f01c762c20f3c1d20cd14ad59cf2a19151ca27 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 14 Sep 2026 09:35:56 +0200 Subject: [PATCH 31/38] fix default elements (#410) --- CHANGELOG.md | 8 ++++++++ src/easyreflectometry/model/model_collection.py | 2 +- tests/model/test_model_collection.py | 13 +++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 850152e4..f659b583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ there is no file at the path. Scripts that relied on these calls silently continuing must catch the exceptions. +## Models + +- `ModelCollection(interface=...)` with no models now builds its default + model correctly. The default was passing the calculator interface + positionally into `Model`, where it landed as the `sample` argument, + so constructing a collection with a real calculator and no models + failed. + ## Parameter constraints - New equality-constraint helpers `constrain`, `constrain_equal`, diff --git a/src/easyreflectometry/model/model_collection.py b/src/easyreflectometry/model/model_collection.py index cb1acce1..bb6e7148 100644 --- a/src/easyreflectometry/model/model_collection.py +++ b/src/easyreflectometry/model/model_collection.py @@ -15,7 +15,7 @@ # Needs to be a function, elements are added to the global_object.map def DEFAULT_ELEMENTS(interface): """Default elements.""" - return (Model(interface),) + return (Model(interface=interface),) class ModelCollection(BaseCollection): diff --git a/tests/model/test_model_collection.py b/tests/model/test_model_collection.py index 59db8f33..f926059a 100644 --- a/tests/model/test_model_collection.py +++ b/tests/model/test_model_collection.py @@ -3,9 +3,11 @@ from easyscience import global_object +from easyreflectometry.calculators import CalculatorFactory from easyreflectometry.model.model import COLORS from easyreflectometry.model.model import Model from easyreflectometry.model.model_collection import ModelCollection +from easyreflectometry.sample import Sample class TestModelCollection: @@ -19,6 +21,17 @@ def test_default(self): assert len(collection) == 1 assert collection[0].name == 'Model' + def test_default_with_real_interface(self): + # When - the default model is built with an actual calculator interface + interface = CalculatorFactory() + collection = ModelCollection(interface=interface) + + # Expect - the interface lands on the model as interface, not as sample + assert len(collection) == 1 + assert isinstance(collection[0].sample, Sample) + assert collection[0].interface is interface + assert collection.interface is interface + def test_dont_populate(self): p = ModelCollection(populate_if_none=False) assert p.name == 'Models' From dd3cb93f97c070a5ab829b54130eb24a8a137ff5 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 14 Sep 2026 10:46:35 +0200 Subject: [PATCH 32/38] 378 remove datastore (#409) * Removed DataStore * ruff on CHANGELOG * ruff --- CHANGELOG.md | 14 ++ src/easyreflectometry/data/__init__.py | 2 - src/easyreflectometry/data/data_store.py | 70 ---------- src/easyreflectometry/project.py | 6 +- tests/data/test_data_store.py | 167 ----------------------- tests/test_measurement_comprehensive.py | 150 ++------------------ tests/test_project.py | 27 ++++ 7 files changed, 58 insertions(+), 378 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f659b583..6936ee5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,20 @@ there is no file at the path. Scripts that relied on these calls silently continuing must catch the exceptions. +## Data containers + +- Removed the unused `DataStore` and `ProjectData` classes from + `easyreflectometry.data`. Their `as_dict`/`from_dict` methods had + never worked (the former raised a `TypeError`, the latter recursed + into a `KeyError`) and nothing in the library, the GUI or the docs + used them; `Project` keeps experiments in its own dictionary. Code + that imported `ProjectData` from `easyreflectometry.data` must switch + to `Project`. +- `Project.as_dict` now always records an experiment's name and model, + not only when the experiment carries x-uncertainties. Previously an + experiment with `xe` set to `None` saved without them and the project + then failed to load. + ## Models - `ModelCollection(interface=...)` with no models now builds its default diff --git a/src/easyreflectometry/data/__init__.py b/src/easyreflectometry/data/__init__.py index 4650b63d..6028fc00 100644 --- a/src/easyreflectometry/data/__init__.py +++ b/src/easyreflectometry/data/__init__.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: BSD-3-Clause from .data_store import DataSet1D -from .data_store import ProjectData from .measurement import dataset_from_datagroup from .measurement import load from .measurement import load_as_dataset @@ -17,7 +16,6 @@ 'load_as_dataset', 'dataset_from_datagroup', 'merge_datagroups', - 'ProjectData', 'DataSet1D', 'PolarizedDataSet', 'channel_from_orso_polarization', diff --git a/src/easyreflectometry/data/data_store.py b/src/easyreflectometry/data/data_store.py index b0cf001f..49818976 100644 --- a/src/easyreflectometry/data/data_store.py +++ b/src/easyreflectometry/data/data_store.py @@ -3,84 +3,14 @@ __author__ = 'github.com/wardsimon' -from collections.abc import Sequence from typing import Optional -from typing import TypeVar from typing import Union import numpy as np from easyscience.io import SerializerComponent -from easyscience.io import SerializerDict from easyreflectometry.model import Model -T = TypeVar('T') - - -class ProjectData(SerializerComponent): - def __init__(self, name='DataStore', exp_data=None, sim_data=None): - """Init function.""" - self.name = name - if exp_data is None: - exp_data = DataStore(name='Exp Datastore') - if sim_data is None: - sim_data = DataStore(name='Sim Datastore') - self.exp_data = exp_data - self.sim_data = sim_data - - -class DataStore(Sequence, SerializerComponent): - def __init__(self, *args, name='DataStore'): - """Init function.""" - self.name = name - self.items = list(args) - self.show_legend = False - - def __getitem__(self, i: int) -> T: - """Getitem function.""" - return self.items.__getitem__(i) - - def __len__(self) -> int: - """Len function.""" - return len(self.items) - - def __setitem__(self, key, value): - """Setitem function.""" - self.items[key] = value - - def __delitem__(self, key): - """Delitem function.""" - del self.items[key] - - def append(self, *args): - """Append function.""" - self.items.append(*args) - - def as_dict(self, skip: list = []) -> dict: - """As dict.""" - this_dict = super(DataStore, self).as_dict(self, skip=skip) - this_dict['items'] = [item.as_dict() for item in self.items if hasattr(item, 'as_dict')] - - @classmethod - def from_dict(cls, d): - """From dict.""" - items = d['items'] - del d['items'] - obj = cls.from_dict(d) - decoder = SerializerDict() - obj.items = [decoder.decode(item) for item in items] - return obj - - @property - def experiments(self): - """Experiments function.""" - return [self[idx] for idx in range(len(self)) if self[idx].is_experiment] - - @property - def simulations(self): - """Simulations function.""" - return [self[idx] for idx in range(len(self)) if self[idx].is_simulation] - class DataSet1D(SerializerComponent): def __init__( diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index 27c75371..f65b9f14 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -1944,8 +1944,8 @@ def _as_dict_add_experiments(self, project_dict: dict): ] if experiment.xe is not None: project_dict['experiments'][key].append(list(experiment.xe)) - project_dict['experiments_models'][key] = experiment.model.name - project_dict['experiments_names'][key] = experiment.name + project_dict['experiments_models'][key] = experiment.model.name + project_dict['experiments_names'][key] = experiment.name @staticmethod def _as_dict_add_polarized_experiment(project_dict: dict, key: int, experiment: PolarizedDataSet) -> None: @@ -2068,7 +2068,7 @@ def _from_dict_extract_experiments(self, project_dict: dict) -> Dict[int, Union[ x=raw[0], y=raw[1], ye=raw[2], - xe=raw[3], + xe=raw[3] if len(raw) > 3 else None, model=self._models[project_dict['experiments_models'][key]], auto_background=False, ) diff --git a/tests/data/test_data_store.py b/tests/data/test_data_store.py index 66ba9b04..1e473dd0 100644 --- a/tests/data/test_data_store.py +++ b/tests/data/test_data_store.py @@ -9,8 +9,6 @@ from numpy.testing import assert_array_equal from easyreflectometry.data.data_store import DataSet1D -from easyreflectometry.data.data_store import DataStore -from easyreflectometry.data.data_store import ProjectData class TestDataSet1D: @@ -149,168 +147,3 @@ def test_default_error_arrays_when_none(self): # Then assert_array_equal(data.ye, np.zeros(3)) assert_array_equal(data.xe, np.zeros(3)) - - -class TestDataStore: - def test_constructor_default(self): - # When - store = DataStore() - - # Then - assert store.name == 'DataStore' - assert len(store) == 0 - assert store.show_legend is False - - def test_constructor_with_name(self): - # When - store = DataStore(name='TestStore') - - # Then - assert store.name == 'TestStore' - - def test_constructor_with_items(self): - # Given - item1 = DataSet1D(name='item1') - item2 = DataSet1D(name='item2') - - # When - store = DataStore(item1, item2, name='TestStore') - - # Then - assert len(store) == 2 - assert store[0] == item1 - assert store[1] == item2 - - def test_getitem(self): - # Given - item = DataSet1D(name='test') - store = DataStore(item) - - # When/Then - assert store[0] == item - - def test_setitem(self): - # Given - item1 = DataSet1D(name='item1') - item2 = DataSet1D(name='item2') - store = DataStore(item1) - - # When - store[0] = item2 - - # Then - assert store[0] == item2 - - def test_delitem(self): - # Given - item1 = DataSet1D(name='item1') - item2 = DataSet1D(name='item2') - store = DataStore(item1, item2) - - # When - del store[0] - - # Then - assert len(store) == 1 - assert store[0] == item2 - - def test_append(self): - # Given - store = DataStore() - item = DataSet1D(name='test') - - # When - store.append(item) - - # Then - assert len(store) == 1 - assert store[0] == item - - def test_len(self): - # Given - store = DataStore() - - # When/Then - assert len(store) == 0 - - store.append(DataSet1D()) - assert len(store) == 1 - - def test_experiments_property(self): - # Given - exp_data = DataSet1D(name='exp', model=Mock()) - sim_data = DataSet1D(name='sim') - store = DataStore(exp_data, sim_data) - - # When - experiments = store.experiments - - # Then - assert len(experiments) == 1 - assert experiments[0] == exp_data - - def test_simulations_property(self): - # Given - exp_data = DataSet1D(name='exp', model=Mock()) - sim_data = DataSet1D(name='sim') - store = DataStore(exp_data, sim_data) - - # When - simulations = store.simulations - - # Then - assert len(simulations) == 1 - assert simulations[0] == sim_data - - def test_as_dict_with_serializable_items(self): - # Given - mock_item = Mock() - mock_item.as_dict.return_value = {'test': 'data'} - store = DataStore(mock_item, name='TestStore') - - # When - The as_dict method has implementation issues, so just test it exists - # and can be called without crashing - assert hasattr(store, 'as_dict') - assert callable(getattr(store, 'as_dict')) - - def test_from_dict_class_method(self): - # Given - Test that the method exists - # The actual implementation has dependencies that make it hard to test in isolation - - # When/Then - Just verify the method exists - assert hasattr(DataStore, 'from_dict') - assert callable(getattr(DataStore, 'from_dict')) - - -class TestProjectData: - def test_constructor_default(self): - # When - project = ProjectData() - - # Then - assert project.name == 'DataStore' - assert isinstance(project.exp_data, DataStore) - assert isinstance(project.sim_data, DataStore) - assert project.exp_data.name == 'Exp Datastore' - assert project.sim_data.name == 'Sim Datastore' - - def test_constructor_with_name(self): - # When - project = ProjectData(name='TestProject') - - # Then - assert project.name == 'TestProject' - - def test_constructor_with_custom_datastores(self): - # Given - exp_store = DataStore(name='CustomExp') - sim_store = DataStore(name='CustomSim') - - # When - project = ProjectData(name='TestProject', exp_data=exp_store, sim_data=sim_store) - - # Then - assert project.exp_data == exp_store - assert project.sim_data == sim_store - assert project.exp_data.name == 'CustomExp' - assert project.sim_data.name == 'CustomSim' diff --git a/tests/test_measurement_comprehensive.py b/tests/test_measurement_comprehensive.py index 2a35e669..26cbebf8 100644 --- a/tests/test_measurement_comprehensive.py +++ b/tests/test_measurement_comprehensive.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause """ -Comprehensive tests for measurement and data store functionality. +Comprehensive tests for measurement and DataSet1D functionality. Tests for all functions in measurement.py and data_store.py modules. """ @@ -18,8 +18,6 @@ import easyreflectometry from easyreflectometry.data.data_store import DataSet1D -from easyreflectometry.data.data_store import DataStore -from easyreflectometry.data.data_store import ProjectData from easyreflectometry.data.measurement import _load_txt from easyreflectometry.data.measurement import dataset_from_datagroup from easyreflectometry.data.measurement import load @@ -223,135 +221,27 @@ def test_repr_string_representation(self): assert str(dataset) == expected -class TestDataStoreComprehensive: - """Comprehensive tests for DataStore class.""" - - def test_datastore_as_sequence(self): - """Test DataStore behaves like a sequence.""" - item1 = DataSet1D(name='item1', x=[1], y=[2]) - item2 = DataSet1D(name='item2', x=[3], y=[4]) - - store = DataStore(item1, item2, name='TestStore') - - # Test sequence operations - assert len(store) == 2 - assert store[0].name == 'item1' - assert store[1].name == 'item2' - - # Test item replacement - item3 = DataSet1D(name='item3', x=[5], y=[6]) - store[0] = item3 - assert store[0].name == 'item3' - - # Test deletion - del store[0] - assert len(store) == 1 - assert store[0].name == 'item2' - - def test_datastore_experiments_and_simulations_filtering(self): - """Test experiments and simulations properties - filter correctly.""" - exp1 = DataSet1D(name='exp1', x=[1], y=[2], model=Mock()) - exp2 = DataSet1D(name='exp2', x=[3], y=[4], model=Mock()) - sim1 = DataSet1D(name='sim1', x=[5], y=[6]) - sim2 = DataSet1D(name='sim2', x=[7], y=[8]) - - store = DataStore(exp1, sim1, exp2, sim2) - - experiments = store.experiments - simulations = store.simulations - - assert len(experiments) == 2 - assert len(simulations) == 2 - assert all(item.is_experiment for item in experiments) - assert all(item.is_simulation for item in simulations) - - def test_datastore_append_method(self): - """Test append method adds items correctly.""" - store = DataStore() - item = DataSet1D(name='new_item', x=[1], y=[2]) - - store.append(item) - - assert len(store) == 1 - assert store[0] == item - - -class TestProjectDataComprehensive: - """Comprehensive tests for ProjectData class.""" - - def test_project_data_initialization(self): - """Test ProjectData initializes with correct - default values.""" - project = ProjectData() - - assert project.name == 'DataStore' - assert isinstance(project.exp_data, DataStore) - assert isinstance(project.sim_data, DataStore) - assert project.exp_data.name == 'Exp Datastore' - assert project.sim_data.name == 'Sim Datastore' - - def test_project_data_with_custom_stores(self): - """Test ProjectData with custom experiment and - simulation stores.""" - custom_exp = DataStore(name='CustomExp') - custom_sim = DataStore(name='CustomSim') - - project = ProjectData(name='MyProject', exp_data=custom_exp, sim_data=custom_sim) - - assert project.name == 'MyProject' - assert project.exp_data == custom_exp - assert project.sim_data == custom_sim - - def test_project_data_stores_independence(self): - """Test that exp_data and sim_data are independent stores.""" - project = ProjectData() - - exp_item = DataSet1D(name='exp', x=[1], y=[2], model=Mock()) - sim_item = DataSet1D(name='sim', x=[3], y=[4]) - - project.exp_data.append(exp_item) - project.sim_data.append(sim_item) - - assert len(project.exp_data) == 1 - assert len(project.sim_data) == 1 - assert project.exp_data[0] != project.sim_data[0] - - class TestIntegrationScenarios: """Integration tests for common usage scenarios.""" def test_complete_workflow_orso_file(self): - """Test complete workflow: load ORSO file - -> create dataset -> store in project.""" - # Load file + """Test complete workflow: load ORSO file -> create dataset.""" fpath = os.path.join(PATH_STATIC, 'test_example1.ort') dataset = load_as_dataset(fpath) - # Create project and add to experimental data - project = ProjectData(name='MyAnalysis') - project.exp_data.append(dataset) - - # Verify workflow - assert len(project.exp_data) == 1 - assert project.exp_data[0] == dataset - assert isinstance(project.exp_data[0], DataSet1D) + assert isinstance(dataset, DataSet1D) + assert len(dataset.x) > 0 + assert len(dataset.x) == len(dataset.y) == len(dataset.ye) def test_complete_workflow_txt_file(self): - """Test complete workflow: load txt file -> - create dataset -> store in project.""" - # Load file + """Test complete workflow: load txt file -> create dataset (no model).""" fpath = os.path.join(PATH_STATIC, 'ref_concat_1.txt') dataset = load_as_dataset(fpath) - # Create project and add to simulation data (no model) - project = ProjectData(name='MySimulation') - project.sim_data.append(dataset) - - # Verify workflow - assert len(project.sim_data) == 1 - assert project.sim_data[0] == dataset + assert isinstance(dataset, DataSet1D) + assert dataset.model is None assert dataset.is_simulation is True + assert dataset.is_experiment is False def test_merge_multiple_files_workflow(self): """Test workflow for merging multiple data files.""" @@ -376,12 +266,6 @@ def test_error_handling_robustness(self): with pytest.raises(ValueError, match='x and y must be the same length'): DataSet1D(x=[1, 2, 3], y=[4, 5]) - # Test empty DataStore operations - empty_store = DataStore() - assert len(empty_store) == 0 - assert len(empty_store.experiments) == 0 - assert len(empty_store.simulations) == 0 - # Test file not found with pytest.raises(FileNotFoundError): _load_txt('nonexistent_file.txt') @@ -393,17 +277,11 @@ def test_data_consistency_checks(self): original_y = [10, 20, 30, 40] dataset = DataSet1D(x=original_x, y=original_y) - # Store in datastore - store = DataStore(dataset) - - # Add to project - project = ProjectData() - project.sim_data = store - - # Verify data consistency - retrieved_dataset = project.sim_data[0] - assert_array_equal(retrieved_dataset.x, np.array(original_x)) - assert_array_equal(retrieved_dataset.y, np.array(original_y)) + # Verify the input lists are copied into arrays and remain consistent + assert_array_equal(dataset.x, np.array(original_x)) + assert_array_equal(dataset.y, np.array(original_y)) + assert_array_equal(dataset.ye, np.zeros(len(original_x))) + assert_array_equal(dataset.xe, np.zeros(len(original_x))) if __name__ == '__main__': diff --git a/tests/test_project.py b/tests/test_project.py index 63e0d3fb..0994b2c7 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -547,6 +547,33 @@ def test_dict_round_trip(self): assert project_dict[key] == new_project_dict[key] assert project_materials_dict == new_project_materials_dict + def test_dict_round_trip_experiment_without_xe(self): + # When - an experiment whose x-uncertainty was explicitly cleared + global_object.map._clear() + project = Project() + project.models = ModelCollection(Model(name='First'), Model(name='Second')) + fpath = os.path.join(PATH_STATIC, 'example.ort') + project.load_experiment_for_model_at_index(fpath, 1) + project.experiments[1].xe = None + project_dict = project.as_dict() + + # Expect - name and model are recorded regardless of xe + assert project_dict['experiments_names'][1] == 'Example data file from refnx docs' + assert project_dict['experiments_models'][1] == project.models[1].name + assert len(project_dict['experiments'][1]) == 3 + + # Then - the project loads back with the experiment attached to the same model + global_object.map._clear() + new_project = Project() + new_project.from_dict(project_dict) + + assert list(new_project.experiments.keys()) == [1] + assert new_project.experiments[1].name == 'Example data file from refnx docs' + assert new_project.experiments[1].model == new_project.models[1] + assert_allclose(new_project.experiments[1].x, project.experiments[1].x) + assert_allclose(new_project.experiments[1].y, project.experiments[1].y) + assert_allclose(new_project.experiments[1].ye, project.experiments[1].ye) + def test_save_as_json(self, tmp_path): # When global_object.map._clear() From 53be08b65777410d81abdc041a789027a11dc001 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 14 Sep 2026 12:00:07 +0200 Subject: [PATCH 33/38] Added exception when file exists and overwrite=false (#411) --- src/easyreflectometry/project.py | 32 ++++++++++++++++++++++---------- tests/test_project.py | 26 +++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/easyreflectometry/project.py b/src/easyreflectometry/project.py index f65b9f14..2e629b61 100644 --- a/src/easyreflectometry/project.py +++ b/src/easyreflectometry/project.py @@ -1755,19 +1755,31 @@ def remove_model_at_index(self, index: int) -> None: self._current_assembly_index = 0 self._current_layer_index = 0 - def add_material(self, material: MaterialCollection) -> None: - """Add material.""" + def add_material(self, material: Material) -> None: + """Add a material to the project material collection. + + :param material: the material to add. + :raises ValueError: if the material is already in the collection. The collection is + left untouched in that case, so a caller cannot mistake a refused add for a + successful one. + """ if material in self._materials: - print(f'WARNING: Material {material} is already in material collection') - else: - self._materials.append(material) + raise ValueError(f'Material {material.name} is already in the material collection.') + self._materials.append(material) def remove_material(self, index: int) -> None: - """Remove material.""" - if self._materials[index] in self._get_materials_in_models(): - print(f'ERROR: Material {self._materials[index]} is used in models') - else: - self._materials.pop(index) + """Remove the material at *index* from the project material collection. + + :param index: position of the material in the collection. + :raises IndexError: if there is no material at *index*. + :raises ValueError: if the material is used by one of the models. Removing it would + leave the model referring to a material the project no longer holds, so the + collection is left untouched. + """ + material = self._materials[index] + if material in self._get_materials_in_models(): + raise ValueError(f'Material {material.name} is used in models and cannot be removed.') + self._materials.pop(index) def _default_info(self): """Default info.""" diff --git a/tests/test_project.py b/tests/test_project.py index 0994b2c7..cda8d9ec 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -348,12 +348,32 @@ def test_remove_material_in_model(self): models = ModelCollection(model) project.models = models - # Then - project.remove_material(0) + # Then Expect + with pytest.raises(ValueError, match='is used in models'): + project.remove_material(0) - # Expect assert len(project._materials) == 2 + def test_add_material_already_present(self): + # When + project = Project() + material = Material() + project.add_material(material) + + # Then Expect + with pytest.raises(ValueError, match='already in the material collection'): + project.add_material(material) + + assert len(project._materials) == 1 + + def test_remove_material_out_of_range(self): + # When + project = Project() + + # Then Expect + with pytest.raises(IndexError): + project.remove_material(0) + def test_default_info(self): # When project = Project() From ad7d255f26b2821ded9b9a9542bcdcf7e0fcaaaa Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Tue, 15 Sep 2026 13:47:45 +0200 Subject: [PATCH 34/38] fix the multi-column load #376 (#413) * fix the multi-column load #376 --- src/easyreflectometry/data/measurement.py | 37 ++++++--- tests/_static/ref_five_col.txt | 4 + tests/test_data.py | 97 ++++++++++++++++++++++- 3 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 tests/_static/ref_five_col.txt diff --git a/src/easyreflectometry/data/measurement.py b/src/easyreflectometry/data/measurement.py index bd3c7432..c71cd4be 100644 --- a/src/easyreflectometry/data/measurement.py +++ b/src/easyreflectometry/data/measurement.py @@ -23,6 +23,11 @@ def load(fname: Union[TextIO, str]) -> sc.DataGroup: to parse raises instead of being silently re-read as plain text (which would drop the entire header, including polarization). + For a plain text file the columns are read by position in the ORSO order + Qz, R, sR, sQz -- further numeric columns are ignored -- and the error + columns must hold **standard deviations**, not variances. See + :func:`_load_txt`. + Parameters ---------- fname : Union[TextIO, str] @@ -118,6 +123,18 @@ def extract_orso_title(data_group: sc.DataGroup, data_name: str) -> str | None: def _load_txt(fname: Union[TextIO, str]) -> sc.DataGroup: """Load data from a simple txt file. + Columns are read by position, following the ORSO order: Qz, R, sR, sQz. + Any further **numeric** columns (e.g. wavelength) are ignored -- the whole + file is still parsed as numbers before the leading columns are taken, so a + trailing text column, or rows of differing width, remain an error. + + The error columns are taken to be **standard deviations** (sigma), matching + the ORSO default, and are squared to obtain the stored variances; a file + carrying variances instead would be mis-scaled. Plain text carries no + convention marker, so this is a requirement on the caller and not something + the loader can check -- unlike an ORSO file, which declares ``value_is`` and + whose FWHM errors are converted to sigma on load. + Parameters ---------- fname : Union[TextIO, str] @@ -138,23 +155,19 @@ def _load_txt(fname: Union[TextIO, str]) -> sc.DataGroup: basename = os.path.splitext(os.path.basename(fname))[0] try: - # First load only the data to check column count - data = np.loadtxt(fname, delimiter=delimiter, comments='#') - if data.ndim == 1: - # Handle single row case - num_columns = len(data) - else: - num_columns = data.shape[1] + # ndmin=2 keeps a single-row file two-dimensional, so columns are indexable + data = np.loadtxt(fname, delimiter=delimiter, comments='#', ndmin=2) + num_columns = data.shape[1] # Verify minimum column requirement if num_columns < 3: raise ValueError(f'File must contain at least 3 columns (found {num_columns})') - # Now unpack the data based on column count + # Take the leading columns by position; any extra columns are ignored if num_columns >= 4: - x, y, e, xe = np.loadtxt(fname, delimiter=delimiter, comments='#', unpack=True) + x, y, e, xe = data[:, :4].T else: # 3 columns - x, y, e = np.loadtxt(fname, delimiter=delimiter, comments='#', unpack=True) + x, y, e = data[:, :3].T xe = np.zeros_like(x) except (ValueError, IOError) as error: @@ -186,13 +199,13 @@ def merge_datagroups(*data_groups: sc.DataGroup) -> sc.DataGroup: if key not in merged_data: merged_data[key] = value else: - merged_data[key] = sc.concatenate([merged_data[key], value]) + merged_data[key] = sc.concat([merged_data[key], value], dim=merged_data[key].dims[0]) for key, value in group['coords'].items(): if key not in merged_coords: merged_coords[key] = value else: - merged_coords[key] = sc.concatenate([merged_coords[key], value]) + merged_coords[key] = sc.concat([merged_coords[key], value], dim=merged_coords[key].dims[0]) if 'attrs' not in group: continue diff --git a/tests/_static/ref_five_col.txt b/tests/_static/ref_five_col.txt new file mode 100644 index 00000000..5ecdcdca --- /dev/null +++ b/tests/_static/ref_five_col.txt @@ -0,0 +1,4 @@ +# # Qz RQz sR sQ lambda +1.03563296e-02 3.88100068e+00 4.33909068e+00 5.17816478e-05 5.00000000e+00 +1.06717294e-02 1.16430511e+01 8.89252719e+00 5.33586471e-05 5.50000000e+00 +1.09871292e-02 7.76200136e+00 6.13138301e+00 5.49356464e-05 6.00000000e+00 diff --git a/tests/test_data.py b/tests/test_data.py index d08402f2..eecace58 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -3,10 +3,12 @@ import os +import tempfile import unittest import numpy as np import pytest +from numpy.testing import assert_allclose from numpy.testing import assert_almost_equal from orsopy.fileio import Header from orsopy.fileio import load_orso @@ -41,8 +43,53 @@ def test_load_with_txt(self): coords_name = 'Qz_test_example1' assert_almost_equal(er_data['data'][data_name].values, n_data[:, 1]) assert_almost_equal(er_data['coords'][coords_name].values, n_data[:, 0]) - assert_almost_equal(er_data['data'][data_name].variances, np.square(n_data[:, 2])) - assert_almost_equal(er_data['coords'][coords_name].variances, np.square(n_data[:, 3])) + # Relative tolerance: the resolution variances are ~1e-9, well inside the default + # absolute tolerance of assert_almost_equal, which would accept zeros here. + assert_allclose(er_data['data'][data_name].variances, np.square(n_data[:, 2]), rtol=1e-12, atol=0) + assert_allclose(er_data['coords'][coords_name].variances, np.square(n_data[:, 3]), rtol=1e-12, atol=0) + + def test_load_with_txt_extra_columns(self): + """Columns beyond the leading Qz, R, sR, sQz are ignored, not an error.""" + fpath = os.path.join(PATH_STATIC, 'ref_five_col.txt') + er_data = load(fpath) + n_data = np.loadtxt(fpath) + data_name = 'R_ref_five_col' + coords_name = 'Qz_ref_five_col' + assert_almost_equal(er_data['data'][data_name].values, n_data[:, 1]) + assert_almost_equal(er_data['coords'][coords_name].values, n_data[:, 0]) + # Relative tolerance: the resolution variances are ~1e-9, well inside the default + # absolute tolerance of assert_almost_equal, which would accept zeros here. + assert_allclose(er_data['data'][data_name].variances, np.square(n_data[:, 2]), rtol=1e-12, atol=0) + assert_allclose(er_data['coords'][coords_name].variances, np.square(n_data[:, 3]), rtol=1e-12, atol=0) + + def test_load_with_txt_extra_columns_single_row(self): + """A single-row file stays two-dimensional, so the leading columns are still indexable.""" + row = np.array([[1.03563296e-02, 3.88100068e00, 4.33909068e00, 5.17816478e-05, 5.0]]) + with tempfile.TemporaryDirectory() as directory: + fpath = os.path.join(directory, 'single_row.txt') + np.savetxt(fpath, row) + er_data = load(fpath) + + assert_allclose(er_data['data']['R_single_row'].values, row[:, 1], rtol=1e-12, atol=0) + assert_allclose(er_data['coords']['Qz_single_row'].values, row[:, 0], rtol=1e-12, atol=0) + assert_allclose(er_data['data']['R_single_row'].variances, np.square(row[:, 2]), rtol=1e-12, atol=0) + assert_allclose(er_data['coords']['Qz_single_row'].variances, np.square(row[:, 3]), rtol=1e-12, atol=0) + + def test_load_with_txt_extra_columns_comma_delimited(self): + """Extra columns are ignored for a comma-delimited file too, not only whitespace.""" + rows = np.array([ + [1.03563296e-02, 3.88100068e00, 4.33909068e00, 5.17816478e-05, 5.0], + [1.06717294e-02, 1.16430511e01, 8.89252719e00, 5.33586471e-05, 5.5], + ]) + with tempfile.TemporaryDirectory() as directory: + fpath = os.path.join(directory, 'comma_five.txt') + np.savetxt(fpath, rows, delimiter=',') + er_data = load(fpath) + + assert_allclose(er_data['data']['R_comma_five'].values, rows[:, 1], rtol=1e-12, atol=0) + assert_allclose(er_data['coords']['Qz_comma_five'].values, rows[:, 0], rtol=1e-12, atol=0) + assert_allclose(er_data['data']['R_comma_five'].variances, np.square(rows[:, 2]), rtol=1e-12, atol=0) + assert_allclose(er_data['coords']['Qz_comma_five'].variances, np.square(rows[:, 3]), rtol=1e-12, atol=0) def test_load_with_txt_commas(self): fpath = os.path.join(PATH_STATIC, 'ref_concat_1.txt') @@ -106,8 +153,10 @@ def test_txt(self): coords_name = 'Qz_test_example1' assert_almost_equal(er_data['data'][data_name].values, n_data[:, 1]) assert_almost_equal(er_data['coords'][coords_name].values, n_data[:, 0]) - assert_almost_equal(er_data['data'][data_name].variances, np.square(n_data[:, 2])) - assert_almost_equal(er_data['coords'][coords_name].variances, np.square(n_data[:, 3])) + # Relative tolerance: the resolution variances are ~1e-9, well inside the default + # absolute tolerance of assert_almost_equal, which would accept zeros here. + assert_allclose(er_data['data'][data_name].variances, np.square(n_data[:, 2]), rtol=1e-12, atol=0) + assert_allclose(er_data['coords'][coords_name].variances, np.square(n_data[:, 3]), rtol=1e-12, atol=0) def test_load_as_dataset_orso(self): fpath = os.path.join(PATH_STATIC, 'test_example1.ort') @@ -199,6 +248,46 @@ def test_merge_datagroups_multiple_groups(self): assert set(merged['data'].keys()) == all_data_keys assert set(merged['coords'].keys()) == all_coords_keys + def test_merge_datagroups_shared_key_concatenates(self): + """Groups sharing a key are concatenated, in argument order, keeping uncertainties. + + The two groups hold different data and differ in length, so a reversed + concatenation, a dropped group, or lost variances all fail here; merging a + file with itself would detect none of them. + """ + first = np.array([ + [1.0e-02, 9.0e-01, 1.0e-02, 5.0e-05], + [2.0e-02, 8.0e-01, 2.0e-02, 6.0e-05], + [3.0e-02, 7.0e-01, 3.0e-02, 7.0e-05], + ]) + second = np.array([[4.0e-02, 6.0e-01, 4.0e-02, 8.0e-05]]) + # Same basename in two directories, so the two groups share their data and + # coordinate keys -- which is what sends the merge down the concatenating branch. + with tempfile.TemporaryDirectory() as directory: + first_path = os.path.join(directory, 'a', 'shared.txt') + second_path = os.path.join(directory, 'b', 'shared.txt') + for path, rows in ((first_path, first), (second_path, second)): + os.makedirs(os.path.dirname(path)) + np.savetxt(path, rows) + group_one = load(first_path) + group_two = load(second_path) + + merged = merge_datagroups(group_one, group_two) + + data_name = 'R_shared' + coords_name = 'Qz_shared' + expected = np.concatenate([first, second]) + merged_data = merged['data'][data_name] + merged_coords = merged['coords'][coords_name] + + assert merged_data.dims == group_one['data'][data_name].dims + assert merged_coords.unit == group_one['coords'][coords_name].unit + assert len(merged_data.values) == len(first) + len(second) + assert_allclose(merged_data.values, expected[:, 1], rtol=1e-12, atol=0) + assert_allclose(merged_coords.values, expected[:, 0], rtol=1e-12, atol=0) + assert_allclose(merged_data.variances, np.square(expected[:, 2]), rtol=1e-12, atol=0) + assert_allclose(merged_coords.variances, np.square(expected[:, 3]), rtol=1e-12, atol=0) + def test_merge_datagroups_with_attrs(self): fpath = os.path.join(PATH_STATIC, 'test_example1.ort') data_group = load(fpath) From 198faaeab9b794516021169118e704f511fa684f Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 18 Sep 2026 08:34:48 +0200 Subject: [PATCH 35/38] preparations for the release --- CHANGELOG.md | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6936ee5f..cb6304ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# Version 1.8.0 (18 Sept 2026) ## Project persistence diff --git a/pyproject.toml b/pyproject.toml index cc0fb6d4..e733cd50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'easyscience @ git+https://github.com/easyscience/easyscience.git@develop', + 'easyscience<=1.8', 'scipp', 'refnx', 'refl1d>=1.0.0', From cb495ff92e781ae0bfc62d713a25a327319dc839 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 18 Sep 2026 09:47:51 +0200 Subject: [PATCH 36/38] pixi update --- pixi.lock | 281 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 222 insertions(+), 59 deletions(-) diff --git a/pixi.lock b/pixi.lock index e69d3faf..a8b70079 100644 --- a/pixi.lock +++ b/pixi.lock @@ -203,7 +203,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -227,6 +226,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -247,6 +247,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz @@ -259,6 +260,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -339,6 +341,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda @@ -514,7 +517,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -541,6 +543,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -558,6 +561,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl @@ -569,6 +573,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl @@ -649,6 +654,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda @@ -815,7 +821,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -840,6 +845,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -863,6 +869,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl @@ -874,6 +881,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl @@ -954,6 +962,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl py-311-env: channels: - url: https://conda.anaconda.org/nodefaults/ @@ -1138,7 +1147,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/06/8a/5e156e31ba656ce93c1cc895dd8f051ec351cb382940dca655aaec475005/python_bidi-0.6.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1155,6 +1163,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl @@ -1163,6 +1172,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -1181,6 +1191,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl @@ -1272,6 +1283,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl p1: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -1443,7 +1455,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/c7/deb8c5e604404dbf10a3808a858946ca3547692ff6316b698945bb72177e/python_socketio-5.16.1-py3-none-any.whl @@ -1460,6 +1471,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl @@ -1467,6 +1479,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -1487,6 +1500,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl @@ -1577,6 +1591,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -1738,7 +1753,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -1754,6 +1768,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl @@ -1761,6 +1776,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -1779,6 +1795,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl @@ -1875,6 +1892,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl py-313-env: channels: - url: https://conda.anaconda.org/nodefaults/ @@ -2061,7 +2079,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.1-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2085,6 +2102,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -2105,6 +2123,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz @@ -2117,6 +2136,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -2197,6 +2217,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda @@ -2372,7 +2393,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h4818236_10.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2399,6 +2419,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -2416,6 +2437,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl @@ -2427,6 +2449,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl @@ -2507,6 +2530,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda @@ -2673,7 +2697,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h507cc87_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/7c/fa07d3da2b6253eb8474be16eab2eadf670460e364ccc895ca7ff388ee30/oscrypto-1.3.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl @@ -2698,6 +2721,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -2721,6 +2745,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl @@ -2732,6 +2757,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl @@ -2812,6 +2838,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl user: channels: - url: https://conda.anaconda.org/nodefaults/ @@ -8315,7 +8342,7 @@ packages: requires_dist: - asteval - bumps - - easyscience @ git+https://github.com/easyscience/easyscience.git@develop + - easyscience<=1.8 - orsopy>=1.2 - plotly - pooch @@ -8360,55 +8387,6 @@ packages: - versioningit ; extra == 'dev' - h5py ; extra == 'orb' requires_python: '>=3.11' -- pypi: git+https://github.com/easyscience/easyscience.git?rev=develop#10e056c27dba89e10c9adc15a2565dfb092256ae - name: easyscience - version: 2.5.1+dev2 - requires_dist: - - asteval - - bumps>=1.0.4 - - dfo-ls - - lmfit - - numpy - - scipp - - build ; extra == 'dev' - - copier ; extra == 'dev' - - docstripy ; extra == 'dev' - - format-docstring ; extra == 'dev' - - gitpython ; extra == 'dev' - - interrogate ; extra == 'dev' - - ipykernel ; extra == 'dev' - - ipympl ; extra == 'dev' - - ipython ; extra == 'dev' - - ipywidgets ; extra == 'dev' - - jinja2 ; extra == 'dev' - - jupyterlab ; extra == 'dev' - - jupyterquiz ; extra == 'dev' - - jupytext ; extra == 'dev' - - matplotlib ; 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' - - pooch ; extra == 'dev' - - pre-commit ; extra == 'dev' - - pydoclint ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-cov ; extra == 'dev' - - pytest-xdist ; extra == 'dev' - - pyyaml ; extra == 'dev' - - radon ; extra == 'dev' - - ruff ; extra == 'dev' - - spdx-headers ; extra == 'dev' - - validate-pyproject[all] ; extra == 'dev' - - versioningit ; extra == 'dev' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl name: backrefs version: '7.0' @@ -9129,6 +9107,68 @@ packages: version: 7.4.3 sha256: e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131 requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl + name: pint + version: 0.25.3 + sha256: 27eb25143bd5de9fcc4d5a4b484f16faf6b4615aa93ece6b3373a8c1a3c1b97d + requires_dist: + - flexcache>=0.3 + - flexparser>=0.4 + - platformdirs>=2.1.0 + - typing-extensions>=4.0.0 + - babel<=2.8 ; extra == 'all' + - dask<2025.3.0 ; extra == 'all' + - matplotlib ; extra == 'all' + - numpy>=1.23 ; extra == 'all' + - pint-pandas>=0.3 ; extra == 'all' + - scipy ; extra == 'all' + - uncertainties>=3.1.6 ; extra == 'all' + - xarray ; extra == 'all' + - babel<=2.8 ; extra == 'babel' + - pytest ; extra == 'codspeed' + - pytest-benchmark ; extra == 'codspeed' + - pytest-codspeed ; extra == 'codspeed' + - pytest-cov ; extra == 'codspeed' + - pytest-mpl ; extra == 'codspeed' + - pytest-subtests ; extra == 'codspeed' + - dask<2025.3.0 ; extra == 'dask' + - babel ; extra == 'docs' + - commonmark==0.8.1 ; extra == 'docs' + - currencyconverter ; extra == 'docs' + - docutils ; extra == 'docs' + - graphviz ; extra == 'docs' + - ipykernel ; extra == 'docs' + - ipython<=8.12 ; extra == 'docs' + - jupyter-client ; extra == 'docs' + - nbsphinx ; extra == 'docs' + - pooch ; extra == 'docs' + - pygments>=2.4 ; extra == 'docs' + - recommonmark==0.5.0 ; extra == 'docs' + - sciform ; extra == 'docs' + - scipy ; extra == 'docs' + - serialize ; extra == 'docs' + - sparse ; extra == 'docs' + - sphinx-book-theme>=1.1.0 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-design ; extra == 'docs' + - sphinx>=6,<8.2 ; extra == 'docs' + - matplotlib ; extra == 'matplotlib' + - numpy>=1.23 ; extra == 'numpy' + - pint-pandas>=0.3 ; extra == 'pandas' + - scipy ; extra == 'scipy' + - pytest ; extra == 'test' + - pytest-benchmark ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest ; extra == 'test-all' + - pytest-benchmark ; extra == 'test-all' + - pytest-cov ; extra == 'test-all' + - pytest-mpl ; extra == 'test-all' + - pytest-subtests ; extra == 'test-all' + - pytest-mpl ; extra == 'test-mpl' + - uncertainties>=3.1.6 ; extra == 'uncertainties' + - xarray ; extra == 'xarray' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl name: reportlab version: 4.5.0 @@ -9338,6 +9378,17 @@ packages: sha256: 32806726b5310524a2b5bfee320737f7feef635cafc945c991062806daa9e43a requires_dist: - ipython ; extra == 'ipython' +- pypi: https://files.pythonhosted.org/packages/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-py3-none-any.whl + name: flexcache + version: '0.3' + sha256: d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32 + requires_dist: + - typing-extensions + - pytest ; extra == 'test' + - pytest-mpl ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-subtests ; extra == 'test' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl name: nbqa version: 1.9.1 @@ -10144,6 +10195,35 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl + name: easyscience + version: 1.3.0 + sha256: 82a9a1f9c55b53d03314ed558f08b72837ede930d7952a0b60b867a604b81bf3 + requires_dist: + - asteval + - bumps + - dfo-ls + - lmfit + - numpy + - pint + - scipp + - uncertainties + - xarray + - build ; extra == 'dev' + - codecov ; extra == 'dev' + - flake8 ; extra == 'dev' + - matplotlib ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; 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' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl name: matplotlib version: 3.10.9 @@ -10732,6 +10812,78 @@ packages: - genshi ; extra == 'genshi' - lxml ; platform_python_implementation == 'CPython' and extra == 'lxml' requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' +- pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl + name: pint + version: 0.26.1 + sha256: e982b129415c09c63308f314ae44697d83e5c96f253bcc0d5b833fc3329eb6d4 + requires_dist: + - flexcache>=0.3 + - flexparser>=0.4 + - platformdirs>=2.1.0 + - typing-extensions>=4.0.0 + - babel<=2.8 ; extra == 'all' + - dask<2025.3.0 ; extra == 'all' + - matplotlib ; extra == 'all' + - numpy>=2.0.0 ; extra == 'all' + - optype[numpy] ; extra == 'all' + - pint-pandas>=0.3 ; extra == 'all' + - scipy ; extra == 'all' + - uncertainties>=3.1.6 ; extra == 'all' + - xarray ; extra == 'all' + - babel<=2.8 ; extra == 'babel' + - pytest ; extra == 'codspeed' + - pytest-benchmark ; extra == 'codspeed' + - pytest-codspeed ; extra == 'codspeed' + - pytest-cov ; extra == 'codspeed' + - pytest-mpl ; extra == 'codspeed' + - pytest-subtests ; extra == 'codspeed' + - dask<2025.3.0 ; extra == 'dask' + - babel ; extra == 'docs' + - commonmark==0.8.1 ; extra == 'docs' + - currencyconverter ; extra == 'docs' + - dask[complete]<2025.3.0 ; extra == 'docs' + - docutils ; extra == 'docs' + - graphviz ; extra == 'docs' + - ipykernel ; extra == 'docs' + - ipython<=8.12 ; extra == 'docs' + - jupyter-client ; extra == 'docs' + - jupyterlite-pyodide-kernel>=0.4 ; extra == 'docs' + - jupyterlite-sphinx>=0.20 ; extra == 'docs' + - matplotlib ; extra == 'docs' + - nbsphinx ; extra == 'docs' + - numba>=0.59 ; extra == 'docs' + - numpy<2.5 ; extra == 'docs' + - optype[numpy] ; extra == 'docs' + - pooch ; extra == 'docs' + - pygments>=2.4 ; extra == 'docs' + - recommonmark==0.5.0 ; extra == 'docs' + - sciform ; extra == 'docs' + - scipy ; extra == 'docs' + - serialize ; extra == 'docs' + - sparse ; extra == 'docs' + - sphinx-book-theme>=1.1.0 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-design ; extra == 'docs' + - sphinx>=6,<8.2 ; extra == 'docs' + - xarray ; extra == 'docs' + - matplotlib ; extra == 'matplotlib' + - numpy>=2.0.0 ; extra == 'numpy' + - optype[numpy] ; extra == 'optype' + - pint-pandas>=0.3 ; extra == 'pandas' + - scipy ; extra == 'scipy' + - pytest ; extra == 'test' + - pytest-benchmark ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-subtests ; extra == 'test' + - pytest ; extra == 'test-all' + - pytest-benchmark ; extra == 'test-all' + - pytest-cov ; extra == 'test-all' + - pytest-mpl ; extra == 'test-all' + - pytest-subtests ; extra == 'test-all' + - pytest-mpl ; extra == 'test-mpl' + - uncertainties>=3.1.6 ; extra == 'uncertainties' + - xarray ; extra == 'xarray' + requires_python: '>=3.12' - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl name: svglib version: 1.6.0 @@ -13567,6 +13719,17 @@ packages: - numpy>=1.22 - numpy>=1.22,<2.5 requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl + name: flexparser + version: '0.4' + sha256: 3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846 + requires_dist: + - typing-extensions + - pytest ; extra == 'test' + - pytest-mpl ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-subtests ; extra == 'test' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl name: watchdog version: 6.0.0 From b126b025c305a2594f5ebe914fe1d60e29267a86 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 18 Sep 2026 09:59:08 +0200 Subject: [PATCH 37/38] updated EasyScience dep --- pixi.lock | 282 +++++++++++-------------------------------------- pyproject.toml | 2 +- 2 files changed, 61 insertions(+), 223 deletions(-) diff --git a/pixi.lock b/pixi.lock index a8b70079..01b3ef82 100644 --- a/pixi.lock +++ b/pixi.lock @@ -226,7 +226,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -247,7 +246,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz @@ -260,7 +258,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -325,6 +322,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -341,7 +339,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda @@ -543,7 +540,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -561,7 +557,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl @@ -573,7 +568,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl @@ -643,6 +637,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -654,7 +649,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda @@ -845,7 +839,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -869,7 +862,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl @@ -881,7 +873,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl @@ -949,6 +940,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -962,7 +954,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl py-311-env: channels: - url: https://conda.anaconda.org/nodefaults/ @@ -1163,7 +1154,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl @@ -1172,7 +1162,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -1191,7 +1180,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl @@ -1270,6 +1258,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl @@ -1283,7 +1272,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl p1: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -1471,7 +1459,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/14/4b/d3c79495dee4831b8bebca2790e72cb90f0c5849c940570a7c7e5b70b952/chardet-7.4.3-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl @@ -1479,7 +1466,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -1500,7 +1486,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl @@ -1580,6 +1565,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -1591,7 +1577,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.13.0-pyhcf101f3_0.conda @@ -1768,7 +1753,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/13/95/cf3f7fe4910cf0365fa8ea0c731f4b8a624d97cd76ea777913ac8d0868e2/mkdocs_jupyter-0.26.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl @@ -1776,7 +1760,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -1795,7 +1778,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl @@ -1879,6 +1861,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e6/0d/8882a4c7a5ebe59a46b709e82411d9c730d67250d41a2e11bc4bcd4d431d/scipp-26.3.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl @@ -1892,7 +1875,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl py-313-env: channels: - url: https://conda.anaconda.org/nodefaults/ @@ -2102,7 +2084,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -2123,7 +2104,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz @@ -2136,7 +2116,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl @@ -2201,6 +2180,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -2217,7 +2197,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl p1: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda @@ -2419,7 +2398,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -2437,7 +2415,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/3e/17/1f31d8562e6f970d64911f1abc330d233bc0c0601411cf7e19c1292be6da/spdx_headers-1.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/5f/25bdec773905bff0ff6cf35ca73b17bd05593b4f87bd8c5fa43705f7167d/chardet-7.4.3-cp313-cp313-macosx_11_0_arm64.whl @@ -2449,7 +2426,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/64/a8/c09fbe44b12fa919c5bfe0afb71e60d1231a7dc93405e54c30496c57c9d3/arviz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-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/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl @@ -2519,6 +2495,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-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/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -2530,7 +2507,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda @@ -2721,7 +2697,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.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/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-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/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl @@ -2745,7 +2720,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/44/0d/f494a2ec62ab56ffd44da4f236db374421fe0cd1e39d9ebc9785751eb432/periodictable-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4a/1a/bea014b55c45c98c63dffd34d2dd01eed0a73ce5b175bd7b1ba798d13558/pyhanko-0.35.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl @@ -2757,7 +2731,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/69/d1/705e6c19b437a4105bf3b9ae7945fcfc3ad2abb73d14bae0a3f2d58b305b/arviz_base-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6a/00/d26b61e82163a59334e9f2ae31ba95ac01922da3792f090e11762d4422e8/rlpycairo-0.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/7d/77752d819ede528c664ecf4de974b17b049e054a14537b74b53c8568014f/arabic_reshaper-3.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6e/94/be70f8ee9c45f2f62b39a1f0e9303bc20e138a8f3b8e50ffd89498e177e1/mkdocstrings-1.0.4-py3-none-any.whl @@ -2825,6 +2798,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/dc/83/6d810a8a9ebc9c307989b418840c20e46907c74d707beb67ab566773e6fc/xarray-2026.4.0-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/e4/bc/daa30c02069eeac5b9198985ba42f5d65ca71bed6705b18329e51d352b7c/plopp-26.4.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/be/b257e12f9710819fde40adc972578bee6b72c5992da1bc8369bef2597756/nbmake-1.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f4/99/722e90c586455733fa860d092722ef074f3a65449fe9787b7b7e2dafcd1f/pyhanko_certvalidator-0.31.1-py3-none-any.whl @@ -2838,7 +2812,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/d9/8f612389178e1e1800aaed85537b024ebe28b9a82fff6a015825e51a8877/refl1d-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/20/2870f2e63da2b74eda896053809065769b256646bb3f4b74e06d013ad590/uritools-6.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl user: channels: - url: https://conda.anaconda.org/nodefaults/ @@ -8342,7 +8315,7 @@ packages: requires_dist: - asteval - bumps - - easyscience<=1.8 + - easyscience>=2.5.1,<3 - orsopy>=1.2 - plotly - pooch @@ -9107,68 +9080,6 @@ packages: version: 7.4.3 sha256: e1b98790c284ff813f18f7cf7de5f05ea2435a080030c7f1a8318f3a4f80b131 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1b/dd/a9fe6a0a09512da23951c68bf36466aeecd89def3183dc095edbc807ddc5/pint-0.25.3-py3-none-any.whl - name: pint - version: 0.25.3 - sha256: 27eb25143bd5de9fcc4d5a4b484f16faf6b4615aa93ece6b3373a8c1a3c1b97d - requires_dist: - - flexcache>=0.3 - - flexparser>=0.4 - - platformdirs>=2.1.0 - - typing-extensions>=4.0.0 - - babel<=2.8 ; extra == 'all' - - dask<2025.3.0 ; extra == 'all' - - matplotlib ; extra == 'all' - - numpy>=1.23 ; extra == 'all' - - pint-pandas>=0.3 ; extra == 'all' - - scipy ; extra == 'all' - - uncertainties>=3.1.6 ; extra == 'all' - - xarray ; extra == 'all' - - babel<=2.8 ; extra == 'babel' - - pytest ; extra == 'codspeed' - - pytest-benchmark ; extra == 'codspeed' - - pytest-codspeed ; extra == 'codspeed' - - pytest-cov ; extra == 'codspeed' - - pytest-mpl ; extra == 'codspeed' - - pytest-subtests ; extra == 'codspeed' - - dask<2025.3.0 ; extra == 'dask' - - babel ; extra == 'docs' - - commonmark==0.8.1 ; extra == 'docs' - - currencyconverter ; extra == 'docs' - - docutils ; extra == 'docs' - - graphviz ; extra == 'docs' - - ipykernel ; extra == 'docs' - - ipython<=8.12 ; extra == 'docs' - - jupyter-client ; extra == 'docs' - - nbsphinx ; extra == 'docs' - - pooch ; extra == 'docs' - - pygments>=2.4 ; extra == 'docs' - - recommonmark==0.5.0 ; extra == 'docs' - - sciform ; extra == 'docs' - - scipy ; extra == 'docs' - - serialize ; extra == 'docs' - - sparse ; extra == 'docs' - - sphinx-book-theme>=1.1.0 ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-design ; extra == 'docs' - - sphinx>=6,<8.2 ; extra == 'docs' - - matplotlib ; extra == 'matplotlib' - - numpy>=1.23 ; extra == 'numpy' - - pint-pandas>=0.3 ; extra == 'pandas' - - scipy ; extra == 'scipy' - - pytest ; extra == 'test' - - pytest-benchmark ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-subtests ; extra == 'test' - - pytest ; extra == 'test-all' - - pytest-benchmark ; extra == 'test-all' - - pytest-cov ; extra == 'test-all' - - pytest-mpl ; extra == 'test-all' - - pytest-subtests ; extra == 'test-all' - - pytest-mpl ; extra == 'test-mpl' - - uncertainties>=3.1.6 ; extra == 'uncertainties' - - xarray ; extra == 'xarray' - requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/1c/13/bc43591a54dd38ac5c19e7e849f0311d879737a0d07e032e5be79849a5fb/reportlab-4.5.0-py3-none-any.whl name: reportlab version: 4.5.0 @@ -9378,17 +9289,6 @@ packages: sha256: 32806726b5310524a2b5bfee320737f7feef635cafc945c991062806daa9e43a requires_dist: - ipython ; extra == 'ipython' -- pypi: https://files.pythonhosted.org/packages/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-py3-none-any.whl - name: flexcache - version: '0.3' - sha256: d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32 - requires_dist: - - typing-extensions - - pytest ; extra == 'test' - - pytest-mpl ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-subtests ; extra == 'test' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/28/88/4789719fbbe166d12d345b3ac66b96105f10001b16e00a9765ba29261a21/nbqa-1.9.1-py3-none-any.whl name: nbqa version: 1.9.1 @@ -10195,35 +10095,6 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/4b/3d/b318067c20ab284c5fc0cc3dade88dd8311434d2f910ec0916e83c6a0626/easyscience-1.3.0-py3-none-any.whl - name: easyscience - version: 1.3.0 - sha256: 82a9a1f9c55b53d03314ed558f08b72837ede930d7952a0b60b867a604b81bf3 - requires_dist: - - asteval - - bumps - - dfo-ls - - lmfit - - numpy - - pint - - scipp - - uncertainties - - xarray - - build ; extra == 'dev' - - codecov ; extra == 'dev' - - flake8 ; extra == 'dev' - - matplotlib ; extra == 'dev' - - pytest ; extra == 'dev' - - pytest-cov ; 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' - requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl name: matplotlib version: 3.10.9 @@ -10812,78 +10683,6 @@ packages: - genshi ; extra == 'genshi' - lxml ; platform_python_implementation == 'CPython' and extra == 'lxml' requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' -- pypi: https://files.pythonhosted.org/packages/6d/5c/9507ea3c732f8a259f45ddf190a741e9bf7d3fe229b5d473917ee6efbf1e/pint-0.26.1-py3-none-any.whl - name: pint - version: 0.26.1 - sha256: e982b129415c09c63308f314ae44697d83e5c96f253bcc0d5b833fc3329eb6d4 - requires_dist: - - flexcache>=0.3 - - flexparser>=0.4 - - platformdirs>=2.1.0 - - typing-extensions>=4.0.0 - - babel<=2.8 ; extra == 'all' - - dask<2025.3.0 ; extra == 'all' - - matplotlib ; extra == 'all' - - numpy>=2.0.0 ; extra == 'all' - - optype[numpy] ; extra == 'all' - - pint-pandas>=0.3 ; extra == 'all' - - scipy ; extra == 'all' - - uncertainties>=3.1.6 ; extra == 'all' - - xarray ; extra == 'all' - - babel<=2.8 ; extra == 'babel' - - pytest ; extra == 'codspeed' - - pytest-benchmark ; extra == 'codspeed' - - pytest-codspeed ; extra == 'codspeed' - - pytest-cov ; extra == 'codspeed' - - pytest-mpl ; extra == 'codspeed' - - pytest-subtests ; extra == 'codspeed' - - dask<2025.3.0 ; extra == 'dask' - - babel ; extra == 'docs' - - commonmark==0.8.1 ; extra == 'docs' - - currencyconverter ; extra == 'docs' - - dask[complete]<2025.3.0 ; extra == 'docs' - - docutils ; extra == 'docs' - - graphviz ; extra == 'docs' - - ipykernel ; extra == 'docs' - - ipython<=8.12 ; extra == 'docs' - - jupyter-client ; extra == 'docs' - - jupyterlite-pyodide-kernel>=0.4 ; extra == 'docs' - - jupyterlite-sphinx>=0.20 ; extra == 'docs' - - matplotlib ; extra == 'docs' - - nbsphinx ; extra == 'docs' - - numba>=0.59 ; extra == 'docs' - - numpy<2.5 ; extra == 'docs' - - optype[numpy] ; extra == 'docs' - - pooch ; extra == 'docs' - - pygments>=2.4 ; extra == 'docs' - - recommonmark==0.5.0 ; extra == 'docs' - - sciform ; extra == 'docs' - - scipy ; extra == 'docs' - - serialize ; extra == 'docs' - - sparse ; extra == 'docs' - - sphinx-book-theme>=1.1.0 ; extra == 'docs' - - sphinx-copybutton ; extra == 'docs' - - sphinx-design ; extra == 'docs' - - sphinx>=6,<8.2 ; extra == 'docs' - - xarray ; extra == 'docs' - - matplotlib ; extra == 'matplotlib' - - numpy>=2.0.0 ; extra == 'numpy' - - optype[numpy] ; extra == 'optype' - - pint-pandas>=0.3 ; extra == 'pandas' - - scipy ; extra == 'scipy' - - pytest ; extra == 'test' - - pytest-benchmark ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-subtests ; extra == 'test' - - pytest ; extra == 'test-all' - - pytest-benchmark ; extra == 'test-all' - - pytest-cov ; extra == 'test-all' - - pytest-mpl ; extra == 'test-all' - - pytest-subtests ; extra == 'test-all' - - pytest-mpl ; extra == 'test-mpl' - - uncertainties>=3.1.6 ; extra == 'uncertainties' - - xarray ; extra == 'xarray' - requires_python: '>=3.12' - pypi: https://files.pythonhosted.org/packages/6d/93/01273a1b8d8454d45f2e18b3d6098c7be13a0864a55fbd0ebda7815c201a/svglib-1.6.0-py3-none-any.whl name: svglib version: 1.6.0 @@ -13340,6 +13139,56 @@ packages: - nodejs ; extra == 'all' - pythreejs ; extra == 'all' requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/e6/90/90a65e6ae1b6e66183b48874d32509fc306c994beecc7924a6fa3d9f8955/easyscience-2.5.1-py3-none-any.whl + name: easyscience + version: 2.5.1 + sha256: e44c3efb10f8e040ba88523c6ba58e131e3174721f24801d036fdc5a24e622f2 + requires_dist: + - asteval + - bumps>=1.0.4 + - dfo-ls + - lmfit + - numpy + - scipp + - build ; extra == 'dev' + - copier ; extra == 'dev' + - docstripy ; extra == 'dev' + - format-docstring ; extra == 'dev' + - gitpython ; extra == 'dev' + - interrogate ; extra == 'dev' + - ipykernel ; extra == 'dev' + - ipympl ; extra == 'dev' + - ipython ; extra == 'dev' + - ipywidgets ; extra == 'dev' + - jinja2 ; extra == 'dev' + - jupyterlab ; extra == 'dev' + - jupyterquiz ; extra == 'dev' + - jupytext ; extra == 'dev' + - matplotlib ; 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' + - pooch ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pydoclint ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pyyaml ; extra == 'dev' + - radon ; extra == 'dev' + - ruff ; extra == 'dev' + - spdx-headers ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' + - versioningit ; extra == 'dev' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl name: cycler version: 0.12.1 @@ -13719,17 +13568,6 @@ packages: - numpy>=1.22 - numpy>=1.22,<2.5 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl - name: flexparser - version: '0.4' - sha256: 3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846 - requires_dist: - - typing-extensions - - pytest ; extra == 'test' - - pytest-mpl ; extra == 'test' - - pytest-cov ; extra == 'test' - - pytest-subtests ; extra == 'test' - requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl name: watchdog version: 6.0.0 diff --git a/pyproject.toml b/pyproject.toml index e733cd50..7f908bab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'easyscience<=1.8', + 'easyscience>=2.5.1,<3', 'scipp', 'refnx', 'refl1d>=1.0.0', From e7e36936ef3da61407d1e7a898040e88f78d208c Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 18 Sep 2026 11:23:45 +0200 Subject: [PATCH 38/38] pre-release doc fixes --- AUTHORS.rst | 3 +- CHANGELOG.md | 84 +++++++++++++++++++++++ docs/docs/installation-and-setup/index.md | 32 ++++++--- docs/docs/user-guide/index.md | 9 +-- docs/overrides/main.html | 11 ++- 5 files changed, 122 insertions(+), 17 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 87f153c4..f7c87537 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -8,4 +8,5 @@ Contributors * Andrew R. McCluskey * Andrew Sazonov * Simon Ward -* Andreas Pedersen \ No newline at end of file +* Andreas Pedersen +* Piotr Rozyczko diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6304ff..1946c35a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,25 @@ experiment with `xe` set to `None` saved without them and the project then failed to load. +## Data loading + +- Plain text files with more than four columns now load (issue #376). + The loader unpacked every column into `Qz, R, sR, sQz`, so a file + carrying a fifth column (wavelength, for instance) failed with + `ValueError: Failed to load data`. Columns are now read by position in + the ORSO order and further **numeric** columns are ignored. A trailing + text column, or rows of differing width, are still an error. A + single-row file loads as well. +- The error columns of a plain text file are documented as **standard + deviations**, matching the ORSO default; they are squared to obtain + the stored variances. Plain text carries no convention marker, so a + file holding variances is mis-scaled and the loader cannot detect it. + This has always been the behaviour; it is now stated in the `load` + docstring. +- `merge_datagroups` uses `scipp.concat` instead of `scipp.concatenate`, + which current scipp releases no longer provide. Merging data groups + that share a key failed with an `AttributeError`. + ## Models - `ModelCollection(interface=...)` with no models now builds its default @@ -40,6 +59,62 @@ so constructing a collection with a real calculator and no models failed. +## Materials + +- New `MaterialDensity.sld_coupled`. A `MaterialDensity` derives `sld` + and `isld` from its chemical formula and mass density; this property + switches that coupling per material. Set to `False`, `sld` and `isld` + become independent parameters that can be set and fitted directly, + keeping their current values, while `density` and the formula stop + affecting the reflectivity. Setting it back to `True` recomputes the + SLDs from the current formula and density and discards the manual + values. The state is serialized, including the manual SLDs of a + decoupled material; project files from before this release load as + coupled. +- Assigning `MaterialDensity.chemical_structure` now updates the + molecular weight along with the scattering lengths. Only the + scattering lengths were updated before, so after a formula change the + derived SLD mixed the new scattering length with the old molecular + weight. SLDs of materials whose formula was changed after construction + should be recomputed. +- Behaviour change: `MaterialDensity.molecular_weight` is a read-only + `DescriptorNumber` and no longer a `Parameter`. It is a constant of + the formula and only `density / molecular_weight` enters the SLD, so + freeing it alongside the density made a fit degenerate. +- Behaviour change: assigning an invalid formula to + `MaterialDensity.chemical_structure` raises `ValueError` and leaves + the material unchanged. An empty string, or one without any element + symbol, used to be accepted as an empty formula with zero scattering + length. An unknown element already raised, but only after the formula + string had been stored, leaving the material half-updated. +- `MaterialCollection.duplicate_material` keeps the type of the material + it copies. It always built a plain `Material`, so duplicating a + `MaterialDensity`, `MaterialMixture` or `MaterialSolvated` lost the + subclass. The duplicate is also bound to the collection's calculator + interface now. + +## Calculators + +- Removed the BornAgain calculator, the + `easyreflectometry.calculators.bornagain` package. Its registration + had long been commented out, so `CalculatorFactory` never offered it + and no supported workflow reached it. The available engines are refnx + and Refl1D. Code that imported from `calculators.bornagain` directly + will fail with `ModuleNotFoundError`. + +## Summary report + +- The parameter table leaves the uncertainty cell empty for a parameter + that has not been fitted. It used to print `0.0`, which read as a + perfectly determined value. + +## Dependencies + +- `easyscience` is now required as `>=2.5.1,<3`. Release 1.7.0 declared + it without bounds, which let a resolver pick the pre-2.0 series; that + series lacks the `easyscience.variable` module this library imports + and fails on `import easyreflectometry`. + ## Parameter constraints - New equality-constraint helpers `constrain`, `constrain_equal`, @@ -319,6 +394,15 @@ returned. - New API reference pages for constraints, inequality constraints, Bayesian analysis, calculators, parameter limits, `LayerMagnetism`, ORSO, summary and plotting. +- The **Open in Google Colab** button on tutorial pages works again. Its + link assumed a flat `tutorials//` layout and had pointed at a + missing file since the tutorials were grouped into category folders. +- The installation guide no longer tells users to set up a + `python -m easyreflectometry` task; the library has no command-line + interface and the command failed. It now shows how to check the + installed version and how to download the tutorial notebooks. +- The user guide no longer lists BornAgain as a planned calculation + engine. # Version 1.7.0 (1 Aug 2026) diff --git a/docs/docs/installation-and-setup/index.md b/docs/docs/installation-and-setup/index.md index 4baef822..c0d39547 100644 --- a/docs/docs/installation-and-setup/index.md +++ b/docs/docs/installation-and-setup/index.md @@ -70,11 +70,15 @@ using **Pixi**. ```txt pixi add --pypi easyreflectometry ``` -- Add a Pixi task to run EasyReflectometry commands easily: +- Check the installation by printing the installed version: ```txt - pixi task add easyreflectometry "python -m easyreflectometry" + pixi run python -c "import easyreflectometry; print(easyreflectometry.__version__)" ``` +EasyReflectometry is a Python library and has no command-line interface. +It is used from Python scripts or Jupyter notebooks, as shown in the +[:material-school: Tutorials](../tutorials/index.md). + #### Updating Pixi and EasyReflectometry - To update all packages in the Pixi environment, including @@ -168,10 +172,10 @@ can be installed using `pip`. To do so, use the following command: pip install easyreflectometry ``` -To install a specific version of EasyReflectometry, e.g., 1.0.3: +To install a specific version of EasyReflectometry, e.g., 1.8.0: ```txt -pip install 'easyreflectometry==1.0.3' +pip install 'easyreflectometry==1.8.0' ``` To upgrade to the latest version: @@ -224,9 +228,15 @@ tutorials interactively in two different ways: locally or online via Google Colab. If you decide to run the tutorials locally, you need to download them -first. This can be done individually via the :material-download: -**Download Notebook** button available on each tutorial page, or all at -once using the command line, as shown below. +first. Each tutorial page has a :material-download: **Download +Notebook** button. Save the notebooks you want into a `tutorials/` +directory inside your project. The notebooks either download their +example data when they run or generate it themselves, so no data files +need to be saved alongside them. + +To get all tutorials at once, download the repository as a +[ZIP archive](https://github.com/easyscience/reflectometry-lib/archive/refs/heads/master.zip) +and copy its `docs/docs/tutorials/` folder. ### Run Tutorials Locally with Pixi recommended { #running-with-pixi data-toc-label="Run Tutorials Locally with Pixi" } @@ -237,8 +247,8 @@ once using the command line, as shown below. ```txt pixi add --pypi jupyterlab ipython pixi-kernel ``` -- Download all the EasyReflectometry tutorials to the `tutorials/` - directory. +- Download the EasyReflectometry tutorials to the `tutorials/` + directory, as described above. - Start the JupyterLab server in the `tutorials/` directory to access the notebooks: ```txt @@ -258,8 +268,8 @@ once using the command line, as shown below. ```txt python -m ipykernel install --user --name=venv --display-name "EasyReflectometry Python kernel" ``` -- Download all the EasyReflectometry tutorials to the `tutorials/` - directory. +- Download the EasyReflectometry tutorials to the `tutorials/` + directory, as described above. - Start the Jupyter Notebook server in the `tutorials/` directory to access the notebooks: ```txt diff --git a/docs/docs/user-guide/index.md b/docs/docs/user-guide/index.md index fbdec8b4..e0c95f27 100644 --- a/docs/docs/user-guide/index.md +++ b/docs/docs/user-guide/index.md @@ -26,14 +26,15 @@ curve from our inputted sample parameters. We rely on third party software to provide the necessary calculators. Different calculators might have different capabilities and limitations. -Currently, EasyReflectometry can offer two different calculation -engines: +EasyReflectometry offers two calculation engines: - [**refnx**](https://refnx.readthedocs.io/) - [**Refl1D**](https://refl1d.readthedocs.io/en/latest/) -And we are working to add more, in particular -[**BornAgain**](https://www.bornagainproject.org) and +Refl1D is the engine to choose for magnetic samples and polarized data; +refnx does not model magnetism. + +We are working to add more, in particular [**GenX**](https://aglavic.github.io/genx/doc/). ### Model diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 2e146827..fe7c19d5 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -8,12 +8,21 @@ {% set tutorial_name = parts[-2] %} {% set filename = parts[-1] %} + {# Directory of this page on gh-pages. Tutorials are grouped into + category folders (tutorials/fitting/simple_fitting/), so take the + path from the page URL instead of assuming tutorials//. #} + {% if page.url.endswith('/') %} + {% set page_dir = page.url %} + {% else %} + {% set page_dir = page.url.rsplit('/', 1)[0] ~ "/" ~ tutorial_name ~ "/" %} + {% endif %} + {# Colab url #} {% set base_colab_url = "https://colab.research.google.com/github/" %} {% set colab_url = base_colab_url ~ config.extra.vars.github_repository ~ "/blob/gh-pages/" ~ config.extra.vars.docs_version ~ - "/tutorials/" ~ tutorial_name ~ "/" ~ filename + "/" ~ page_dir ~ filename %} {# Download link: relative to the current page #}