From c4c1b0274d045e16d2b742cd184b2c82e105632c Mon Sep 17 00:00:00 2001 From: rozyczko Date: Wed, 6 May 2026 14:03:13 +0200 Subject: [PATCH 01/15] initial implementation --- .../fitting/minimizers/minimizer_bumps.py | 84 +++++ src/easyscience/fitting/multi_fitter.py | 107 +++++++ .../integration/fitting/test_multi_fitter.py | 300 ++++++++++++++++++ 3 files changed, 491 insertions(+) diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 13678854..0ead63b5 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -334,6 +334,90 @@ def _make_func(x, y, weights): return _outer(self) + 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, + seed: int | None = None, + sampler_kwargs: dict | None = None, + ) -> dict: + """Run Bayesian MCMC sampling using the BUMPS DREAM sampler. + + Builds a BUMPS :class:`~bumps.names.FitProblem` from the current + model and runs the DREAM sampler. This is the public minimizer-level + entry point for Bayesian sampling; the higher-level + :meth:`easyscience.fitting.multi_fitter.MultiFitter.sample` delegates + to this method after flattening multi-dataset arrays. + + :param x: Flattened independent variable array. + :type x: np.ndarray + :param y: Flattened dependent variable array. + :type y: np.ndarray + :param weights: Flattened weight array. + :type weights: np.ndarray + :param samples: Number of retained DREAM samples requested from BUMPS. + :type samples: int + :param burn: Burn-in steps. + :type burn: int + :param thin: Thinning interval. + :type thin: int + :param chains: User-friendly alias for BUMPS DREAM population count. + :type chains: int | None + :param population: BUMPS DREAM population count (``pop``) for advanced users. + :type population: int | None + :param seed: Best-effort random seed passed to ``numpy.random.seed``. + BUMPS DREAM may use additional internal RNG state that is not + controlled by this seed, so exact reproducibility is not guaranteed. + :type seed: int | None + :param sampler_kwargs: Additional keyword arguments forwarded to + :func:`bumps.fitters.fit`. + :type sampler_kwargs: dict | None + :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, + and ``'logp'``. + :rtype: dict + """ + from bumps.fitters import fit as bumps_fit + from bumps.names import FitProblem + + # Build the BUMPS Curve model using the minimizer's existing machinery + model_func = self._make_model() + x_flat = np.linspace(0, y.size - 1, y.size) + curve = model_func(x_flat, y, weights) + problem = FitProblem(curve) + + # Best-effort seed: sets numpy's global RNG state just before DREAM starts. + # BUMPS DREAM may have its own internal RNG paths that are not fully + # controlled by this, so exact reproducibility is not guaranteed. + if seed is not None: + np.random.seed(seed) + + # Run DREAM sampler + dream_kwargs: dict = {'samples': samples, 'burn': burn, 'thin': thin} + if chains is not None or population is not None: + pop = chains if chains is not None else population + dream_kwargs['pop'] = pop + if sampler_kwargs: + dream_kwargs.update(sampler_kwargs) + result = bumps_fit(problem, method='dream', **dream_kwargs) + + # Extract posterior + draws = result.state.draw().points + param_names = [p.name[len(MINIMIZER_PARAMETER_PREFIX) :] for p in problem._parameters] + logp = getattr(result.state, 'logp', None) + + return { + 'draws': draws, + 'param_names': param_names, + 'state': result.state, + 'logp': logp, + } + def _set_parameter_fit_result( self, fit_result, diff --git a/src/easyscience/fitting/multi_fitter.py b/src/easyscience/fitting/multi_fitter.py index f66cd173..bc846e7a 100644 --- a/src/easyscience/fitting/multi_fitter.py +++ b/src/easyscience/fitting/multi_fitter.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause from typing import Callable +from typing import Dict from typing import List from typing import Optional @@ -149,3 +150,109 @@ def _post_compute_reshaping( fit_results_list.append(current_results) sp = ep return fit_results_list + + def sample( + self, + x: List[np.ndarray], + y: List[np.ndarray], + weights: List[np.ndarray], + samples: int = 10000, + burn: int = 1000, + thin: int = 10, + chains: int | None = None, + population: int | None = None, + seed: int | None = None, + vectorized: bool = False, + sampler_kwargs: dict | None = None, + ) -> Dict: + """Run Bayesian MCMC sampling using the BUMPS DREAM sampler. + + Requires that the current minimizer is a BUMPS instance (i.e. the + minimizer was switched to ``AvailableMinimizers.Bumps`` or equivalent). + + :param x: List of independent variable arrays (one per dataset). + :type x: List[np.ndarray] + :param y: List of dependent variable arrays (one per dataset). + :type y: List[np.ndarray] + :param weights: List of weight arrays (one per dataset). + :type weights: List[np.ndarray] + :param samples: Number of retained DREAM samples requested from BUMPS. + :type samples: int + :param burn: Burn-in steps. + :type burn: int + :param thin: Thinning interval. + :type thin: int + :param chains: User-friendly alias for BUMPS DREAM population count. + :type chains: int | None + :param population: BUMPS DREAM population count (``pop``) for advanced users. + :type population: int | None + :param seed: Best-effort random seed. BUMPS DREAM may use additional + internal RNG state that is not controlled by this seed, so exact + reproducibility is not guaranteed. + :type seed: int | None + :param vectorized: Whether the fit function expects vectorized + (multidimensional) input. Defaults to ``False``. + :type vectorized: bool + :param sampler_kwargs: Additional keyword arguments forwarded to the + BUMPS DREAM sampler via :func:`bumps.fitters.fit`. + :type sampler_kwargs: dict | None + :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, + and ``'logp'``. + :rtype: dict + :raises RuntimeError: If the current minimizer is not a BUMPS instance. + """ + # --- Alias resolution --- + if chains is not None and population is not None: + if chains != population: + raise ValueError( + f'Conflicting population arguments: chains={chains}, population={population}. ' + 'Only provide one.' + ) + pop = chains + elif chains is not None: + pop = chains + elif population is not None: + pop = population + else: + pop = None + + # Flatten multi-dataset arrays + _, x_new, y_new, w_new, _dims = self._precompute_reshaping( + x, y, weights, vectorized=vectorized + ) + self._dependent_dims = _dims + + # Wrap fit functions for multi-dataset flattening, mirroring the + # ``Fitter.fit`` lifecycle: use the property setter so the minimizer + # is re-created with the wrapped fit function. + original_fit_func = self.fit_function + fit_fun_wrap = self._fit_function_wrapper(x_new, flatten=True) + self.fit_function = fit_fun_wrap + + try: + minimizer = self.minimizer + + # Verify it's a BUMPS minimizer (sampling only works with BUMPS/DREAM) + if not (hasattr(minimizer, 'package') and minimizer.package == 'bumps'): + raise RuntimeError( + 'Bayesian sampling requires a BUMPS minimizer. ' + 'Use ``fitter.switch_minimizer(AvailableMinimizers.Bumps)`` first.' + ) + + # Delegate to the BUMPS minimizer's public sample method + result = minimizer.sample( + x=x_new, + y=y_new, + weights=w_new, + samples=samples, + burn=burn, + thin=thin, + chains=None, # alias already resolved into `pop` + population=pop, + seed=seed, + sampler_kwargs=sampler_kwargs, + ) + finally: + self.fit_function = original_fit_func + + return result diff --git a/tests/integration/fitting/test_multi_fitter.py b/tests/integration/fitting/test_multi_fitter.py index 7e2d02d1..4ceb3739 100644 --- a/tests/integration/fitting/test_multi_fitter.py +++ b/tests/integration/fitting/test_multi_fitter.py @@ -286,3 +286,303 @@ def test_multi_fit_1D_2D(fit_engine): assert result.success assert np.all(result.x == X[idx]) assert np.all(result.y_obs == Y[idx]) + + +# --------------------------------------------------------------------------- +# Tests for MultiFitter.sample (Bayesian MCMC via BUMPS DREAM) +# --------------------------------------------------------------------------- + + +class TestSampleRequiresBumps: + def test_raises_runtime_error_when_not_bumps(self): + """sample() must raise RuntimeError if the minimizer is not a BUMPS instance.""" + sp = AbsSin(0.354, 3.05) + f = MultiFitter([sp], [sp]) + + x = np.linspace(0, 5, 50) + y = np.sin(x) + weights = np.ones_like(x) + + with pytest.raises(RuntimeError, match='Bayesian sampling requires a BUMPS minimizer'): + f.sample(x=[x], y=[y], weights=[weights], samples=10, burn=5, thin=1) + + +class TestSampleBasic: + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_returns_expected_keys_and_shapes(self): + """sample() with BUMPS should return draws, param_names, state, logp.""" + ref_sin = AbsSin(0.2, np.pi) + sp = AbsSin(0.354, 3.05) + + x = np.linspace(0, 5, 50) + y = ref_sin(x) + weights = np.ones_like(x) + + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + result = f.sample(x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2) + + assert isinstance(result, dict) + assert 'draws' in result + assert 'param_names' in result + assert 'state' in result + assert 'logp' in result + + # draws shape: (retained_samples, n_params) + assert result['draws'].ndim == 2 + assert result['draws'].shape[1] == len(result['param_names']) + + # param_names should match the model's fit parameters + expected_pars = {p.unique_name for p in sp.get_fit_parameters()} + assert set(result['param_names']) == expected_pars + + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_multi_dataset_returns_consistent_param_names(self): + """sample() with multiple datasets should have correct param_names across all.""" + ref_sin_1 = AbsSin(0.2, np.pi) + sp_sin_1 = AbsSin(0.354, 3.05) + sp_line = Line(0.43, 6.1) + + # Link a parameter across models + sp_line.m.make_dependent_on( + dependency_expression='sp_sin1', dependency_map={'sp_sin1': sp_sin_1.offset} + ) + + x1 = np.linspace(0, 5, 50) + y1 = ref_sin_1(x1) + x2 = np.copy(x1) + y2 = Line(1, 4.6)(x2) + weights = np.ones_like(x1) + + sp_sin_1.offset.fixed = False + sp_sin_1.phase.fixed = False + sp_line.c.fixed = False + + f = MultiFitter([sp_sin_1, sp_line], [sp_sin_1, sp_line]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + result = f.sample( + x=[x1, x2], y=[y1, y2], weights=[weights, weights], samples=100, burn=20, thin=2 + ) + + # All parameters across both models should appear + all_params = {p.unique_name for p in sp_sin_1.get_fit_parameters()} + all_params |= {p.unique_name for p in sp_line.get_fit_parameters()} + assert set(result['param_names']) == all_params + + +class TestSampleAliasResolution: + def test_conflicting_chains_and_population_raises(self): + """Passing both chains and population with different values must raise.""" + sp = AbsSin(0.354, 3.05) + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + x = np.linspace(0, 5, 50) + y = np.sin(x) + weights = np.ones_like(x) + + with pytest.raises(ValueError, match='Conflicting population arguments'): + f.sample( + x=[x], y=[y], weights=[weights], samples=10, burn=5, thin=1, chains=3, population=5 + ) + + def test_chains_and_population_equal_is_ok(self): + """Passing chains == population should succeed (no conflict).""" + sp = AbsSin(0.354, 3.05) + sp.offset.fixed = False + sp.phase.fixed = False + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + x = np.linspace(0, 5, 50) + y = np.sin(x) + weights = np.ones_like(x) + + # Should not raise ValueError — chains and population are equal. + # DREAM needs a sufficient population; 5 is a safe minimum. + result = f.sample( + x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, chains=5, population=5 + ) + assert 'draws' in result + + +class TestSampleSeedReproducibility: + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_seed_produces_valid_draws(self): + """Running sample() with a seed must produce valid draws.""" + ref_sin = AbsSin(0.2, np.pi) + sp = AbsSin(0.354, 3.05) + + x = np.linspace(0, 5, 50) + y = ref_sin(x) + weights = np.ones_like(x) + + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + result = f.sample(x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, seed=42) + + assert result['draws'].ndim == 2 + assert result['draws'].shape[0] > 0 + assert result['draws'].shape[1] == len(result['param_names']) + # logp should be present (may be None if not computed) + assert 'logp' in result + + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_different_seeds_both_produce_valid_draws(self): + """Running sample() with different seeds should each produce valid draws.""" + ref_sin = AbsSin(0.2, np.pi) + sp = AbsSin(0.354, 3.05) + + x = np.linspace(0, 5, 50) + y = ref_sin(x) + weights = np.ones_like(x) + + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + result1 = f.sample(x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, seed=42) + result2 = f.sample( + x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, seed=12345 + ) + + # Both must produce valid draws + assert result1['draws'].shape[0] > 0 + assert result2['draws'].shape[0] > 0 + assert result1['draws'].ndim == 2 + assert result2['draws'].ndim == 2 + + +class TestSampleVectorized: + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_vectorized_2d_input_produces_valid_draws(self): + """sample() with vectorized=True and 2D input should produce valid draws.""" + sp = AbsSin2D(0.1, 1.75) + + x = np.linspace(0, 5, 50) + X, Y = np.meshgrid(x, x) + x2D = np.stack((X, Y), axis=2) + y2D = np.abs(np.sin(X)) * np.abs(np.sin(Y)) + weights = np.ones_like(y2D) + + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + result = f.sample( + x=[x2D], y=[y2D], weights=[weights], samples=100, burn=20, thin=2, vectorized=True + ) + + assert result['draws'].ndim == 2 + assert result['draws'].shape[0] > 0 + assert result['draws'].shape[1] == len(result['param_names']) + + +class TestSampleStateRestoration: + def test_fit_function_restored_after_runtime_error(self): + """fit_function must be restored to its original value even when sample() raises.""" + sp = AbsSin(0.354, 3.05) + f = MultiFitter([sp], [sp]) + + x = np.linspace(0, 5, 50) + y = np.sin(x) + weights = np.ones_like(x) + + original_func = f.fit_function + + with pytest.raises(RuntimeError): + f.sample(x=[x], y=[y], weights=[weights], samples=10, burn=5, thin=1) + + assert f.fit_function is original_func + + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_fit_function_restored_after_successful_sample(self): + """fit_function must be restored to its original value after a successful sample().""" + ref_sin = AbsSin(0.2, np.pi) + sp = AbsSin(0.354, 3.05) + + x = np.linspace(0, 5, 50) + y = ref_sin(x) + weights = np.ones_like(x) + + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + original_func = f.fit_function + f.sample(x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2) + assert f.fit_function is original_func + + +class TestSampleSamplerKwargs: + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_sampler_kwargs_forwarded(self): + """sampler_kwargs dict is forwarded to the BUMPS DREAM sampler.""" + ref_sin = AbsSin(0.2, np.pi) + sp = AbsSin(0.354, 3.05) + + x = np.linspace(0, 5, 50) + y = ref_sin(x) + weights = np.ones_like(x) + + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + # Pass extra kwargs — should not raise + result = f.sample( + x=[x], + y=[y], + weights=[weights], + samples=100, + burn=20, + thin=2, + sampler_kwargs={'init': 'random'}, + ) + + assert result['draws'].ndim == 2 + assert result['draws'].shape[0] > 0 From 96be6d0232740c8ce20fe7de4f9b739ef5d6751f Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Sat, 9 May 2026 22:20:04 +0200 Subject: [PATCH 02/15] show max iteration, so users know how long to wait. Added cancellation of bayesian --- .../fitting/minimizers/minimizer_bumps.py | 122 ++++++++++++++---- src/easyscience/fitting/multi_fitter.py | 9 +- 2 files changed, 103 insertions(+), 28 deletions(-) diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 0ead63b5..a2003d82 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -80,6 +80,7 @@ def fit( tolerance: float | None = None, max_evaluations: int | None = None, progress_callback: Callable[[dict], bool | None] | None = None, + abort_test: Callable[[], bool] | None = None, minimizer_kwargs: dict | None = None, engine_kwargs: dict | None = None, **kwargs, @@ -186,6 +187,7 @@ def fit( fitclass=fitclass, problem=problem, monitors=monitors, + abort_test=abort_test or (lambda: False), **minimizer_kwargs, **kwargs, ) @@ -346,6 +348,8 @@ def sample( population: int | None = None, seed: int | None = None, sampler_kwargs: dict | None = None, + progress_callback: Callable[[dict], bool | None] | None = None, + abort_test: Callable[[], bool] | None = None, ) -> dict: """Run Bayesian MCMC sampling using the BUMPS DREAM sampler. @@ -356,33 +360,23 @@ def sample( to this method after flattening multi-dataset arrays. :param x: Flattened independent variable array. - :type x: np.ndarray :param y: Flattened dependent variable array. - :type y: np.ndarray :param weights: Flattened weight array. - :type weights: np.ndarray :param samples: Number of retained DREAM samples requested from BUMPS. - :type samples: int :param burn: Burn-in steps. - :type burn: int :param thin: Thinning interval. - :type thin: int :param chains: User-friendly alias for BUMPS DREAM population count. - :type chains: int | None - :param population: BUMPS DREAM population count (``pop``) for advanced users. - :type population: int | None - :param seed: Best-effort random seed passed to ``numpy.random.seed``. - BUMPS DREAM may use additional internal RNG state that is not - controlled by this seed, so exact reproducibility is not guaranteed. - :type seed: int | None + :param population: BUMPS DREAM population count for advanced users. + :param seed: Best-effort random seed. :param sampler_kwargs: Additional keyword arguments forwarded to :func:`bumps.fitters.fit`. - :type sampler_kwargs: dict | None - :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, + :param progress_callback: Optional callback for progress updates during + sampling. The payload dict includes ``iteration`` (DREAM generation + number) and ``sampling: True``. + :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'`", and ``'logp'``. - :rtype: dict """ - from bumps.fitters import fit as bumps_fit + from bumps.fitters import DreamFit from bumps.names import FitProblem # Build the BUMPS Curve model using the minimizer's existing machinery @@ -392,32 +386,106 @@ def sample( problem = FitProblem(curve) # Best-effort seed: sets numpy's global RNG state just before DREAM starts. - # BUMPS DREAM may have its own internal RNG paths that are not fully - # controlled by this, so exact reproducibility is not guaranteed. if seed is not None: np.random.seed(seed) - # Run DREAM sampler + # Resolve population parameter + if chains is not None and population is not None: + if chains != population: + raise ValueError( + f'Conflicting population arguments: chains={chains}, population={population}. ' + 'Only provide one.' + ) + pop = chains + elif chains is not None: + pop = chains + elif population is not None: + pop = population + else: + pop = None + + # Build DREAM kwargs dream_kwargs: dict = {'samples': samples, 'burn': burn, 'thin': thin} - if chains is not None or population is not None: - pop = chains if chains is not None else population + if pop is not None: dream_kwargs['pop'] = pop if sampler_kwargs: dream_kwargs.update(sampler_kwargs) - result = bumps_fit(problem, method='dream', **dream_kwargs) - # Extract posterior - draws = result.state.draw().points + # Build monitors (same pattern as classical Bumps.fit()) + monitors = [] + if progress_callback is not None: + if not callable(progress_callback): + raise ValueError('progress_callback must be callable') + # Compute total DREAM steps for progress display (burn + sampling generations) + pop_val = pop if pop else 10 + _total_steps = burn + (samples + pop_val - 1) // pop_val + monitors.append( + BumpsProgressMonitor( + problem, + progress_callback, + lambda problem, iteration, point, nllf: { + **self._build_sample_progress_payload(problem, iteration, point, nllf), + 'total_steps': _total_steps, + }, + ) + ) + + driver = FitDriver( + fitclass=DreamFit, + problem=problem, + monitors=monitors, + abort_test=abort_test or (lambda: False), + **dream_kwargs, + ) + driver.clip() + + from easyscience import global_object + + stack_status = global_object.stack.enabled + global_object.stack.enabled = False + + try: + x_opt, fx = driver.fit() + result_state = getattr(driver.fitter, 'state', None) + if result_state is None: + raise FitError('Sampling aborted by user') + except Exception: + self._restore_parameter_values() + raise + finally: + global_object.stack.enabled = stack_status + + draws = result_state.draw().points param_names = [p.name[len(MINIMIZER_PARAMETER_PREFIX) :] for p in problem._parameters] - logp = getattr(result.state, 'logp', None) + logp = getattr(result_state, 'logp', None) return { 'draws': draws, 'param_names': param_names, - 'state': result.state, + 'state': result_state, 'logp': logp, } + def _build_sample_progress_payload( + self, problem, iteration: int, point: np.ndarray, nllf: float + ) -> dict: + """Build a progress payload for Bayesian DREAM sampling steps. + + Called by :class:`BumpsProgressMonitor` at each DREAM generation. + The payload includes ``sampling: True`` so downstream consumers can + distinguish sampling progress from classical fitting progress. + """ + parameter_values = self._current_parameter_snapshot(problem, point) + return { + 'iteration': iteration, + 'chi2': float(problem.chisq(nllf=nllf, norm=False)), + 'reduced_chi2': float(problem.chisq(nllf=nllf, norm=True)), + 'parameter_values': parameter_values, + 'refresh_plots': False, + 'finished': False, + 'sampling': True, + } + def _set_parameter_fit_result( self, fit_result, diff --git a/src/easyscience/fitting/multi_fitter.py b/src/easyscience/fitting/multi_fitter.py index bc846e7a..c8d7c871 100644 --- a/src/easyscience/fitting/multi_fitter.py +++ b/src/easyscience/fitting/multi_fitter.py @@ -164,6 +164,8 @@ def sample( seed: int | None = None, vectorized: bool = False, sampler_kwargs: dict | None = None, + progress_callback: Callable[[dict], bool | None] | None = None, + abort_test: Callable[[], bool] | None = None, ) -> Dict: """Run Bayesian MCMC sampling using the BUMPS DREAM sampler. @@ -196,7 +198,10 @@ def sample( :param sampler_kwargs: Additional keyword arguments forwarded to the BUMPS DREAM sampler via :func:`bumps.fitters.fit`. :type sampler_kwargs: dict | None - :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, + :param progress_callback: Optional callback for progress updates during + sampling. The payload dict includes ``iteration`` (DREAM generation + number) and ``sampling: True``. + :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'`", and ``'logp'``. :rtype: dict :raises RuntimeError: If the current minimizer is not a BUMPS instance. @@ -251,6 +256,8 @@ def sample( population=pop, seed=seed, sampler_kwargs=sampler_kwargs, + progress_callback=progress_callback, + abort_test=abort_test, ) finally: self.fit_function = original_fit_func From 181d43dff869462e2f421d17913233c5ba066187 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Sun, 10 May 2026 08:51:03 +0200 Subject: [PATCH 03/15] use absolute paths for wheel --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05998190..2bd3fab7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -217,10 +217,10 @@ jobs: exit 1 fi - whl_url="file://$(python -c 'import os,sys; print(os.path.abspath(sys.argv[1]))' "${whl_path[0]}")" + whl_path=$(python -c 'import os,sys; print(os.path.relpath(os.path.abspath(sys.argv[1]), os.getcwd()))' "${whl_path[0]}") - echo "Adding easyscience from: $whl_url" - pixi add --pypi "easyscience[dev] @ ${whl_url}" + echo "Adding easyscience from local wheel: $whl_path" + pixi add --pypi "easyscience[dev] @ ${whl_path}" echo "Exiting pixi project directory" cd .. From 06cefc7fd1fdcfe06a29092736724210b4c0a677 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Sun, 10 May 2026 21:56:57 +0200 Subject: [PATCH 04/15] additional tests for bayesian --- .../minimizers/test_minimizer_bumps.py | 359 ++++++++++++++++++ tests/unit/fitting/test_multi_fitter.py | 255 +++++++++++++ 2 files changed, 614 insertions(+) diff --git a/tests/unit/fitting/minimizers/test_minimizer_bumps.py b/tests/unit/fitting/minimizers/test_minimizer_bumps.py index 6b587dd7..93ee68b6 100644 --- a/tests/unit/fitting/minimizers/test_minimizer_bumps.py +++ b/tests/unit/fitting/minimizers/test_minimizer_bumps.py @@ -672,3 +672,362 @@ def test_gen_fit_results_uses_nit_for_budget_check(self, minimizer: Bumps, monke domain_fit_results.message == 'Fit stopped: reached maximum optimizer steps (3); objective evaluated 2 times' ) + + +# =================================================================== +# Bumps.sample() — Bayesian DREAM sampling +# =================================================================== + + +class TestBumpsSample: + """Tests for the ``Bumps.sample()`` method and its helpers.""" + + # Sentinel value to signal "set fitter.state = None" in _setup_driver_mock + ABORT = object() + + @pytest.fixture + def minimizer(self) -> Bumps: + return Bumps( + obj='obj', + fit_function='fit_function', + minimizer_enum=MagicMock(package='bumps', method='amoeba'), + ) + + @pytest.fixture(autouse=True) + def _mock_bumps_internals(self, monkeypatch): + """Prevent sample() from constructing real BUMPS objects. + + ``sample()`` imports ``DreamFit`` and ``FitProblem`` from the real + ``bumps`` package internally, which would try to build real model + objects. We redirect those to mocks and also mock ``FitDriver`` + (which *is* a module-level import) so the whole flow stays under + test control. + + Also mock ``_make_model`` on the class so that the ``minimizer`` + fixture (which uses ``obj='obj'``) doesn't fail inside ``sample()``. + """ + import bumps.fitters + import bumps.names + + monkeypatch.setattr(bumps.fitters, 'DreamFit', MagicMock()) + monkeypatch.setattr(bumps.names, 'FitProblem', MagicMock(return_value=MagicMock())) + monkeypatch.setattr( + Bumps, '_make_model', MagicMock(return_value=MagicMock(return_value=MagicMock())) + ) + + def _setup_driver_mock( + self, monkeypatch, fitter_state_value=None, fit_result=None, fit_side_effect=None + ): + """Helper to create a mocked FitDriver with configurable behavior. + + :param fitter_state_value: If ``None``, ``driver.fitter.state`` will be + a regular MagicMock (non-None). Pass ``ABORT`` to set it to ``None`` + and simulate user abort. + """ + from easyscience import global_object + + global_object.stack.enabled = False + + mock_driver = MagicMock() + mock_driver.clip = MagicMock() + + if fit_side_effect is not None: + mock_driver.fit.side_effect = fit_side_effect + else: + mock_driver.fit.return_value = fit_result or (np.array([1.0]), 0.0) + + mock_driver.stderr = MagicMock(return_value=np.array([0.1])) + + if fitter_state_value is TestBumpsSample.ABORT: + mock_driver.fitter.state = None + else: + mock_state = MagicMock() + mock_state.draw.return_value.points = np.array([[1.0]]) + mock_state.logp = None + mock_driver.fitter.state = mock_state + + mock_FitDriver = MagicMock(return_value=mock_driver) + monkeypatch.setattr( + easyscience.fitting.minimizers.minimizer_bumps, 'FitDriver', mock_FitDriver + ) + return mock_FitDriver, mock_driver + + def test_sample_basic(self, minimizer: Bumps, monkeypatch) -> None: + """Verify that sample() returns a dict with expected keys.""" + mock_FitDriver, _ = self._setup_driver_mock(monkeypatch) + minimizer._make_model = MagicMock(return_value=MagicMock(return_value=MagicMock())) + + result = minimizer.sample( + x=np.array([1.0, 2.0]), + y=np.array([0.1, 0.2]), + weights=np.array([1.0, 1.0]), + samples=100, + burn=20, + thin=2, + population=5, + ) + + assert isinstance(result, dict) + assert 'draws' in result + assert 'param_names' in result + assert 'state' in result + assert 'logp' in result + mock_FitDriver.assert_called_once() + + def test_sample_with_progress_callback(self, minimizer: Bumps, monkeypatch) -> None: + """Verify progress callback is wired up as a monitor.""" + mock_FitDriver, _ = self._setup_driver_mock(monkeypatch) + minimizer._make_model = MagicMock(return_value=MagicMock(return_value=MagicMock())) + progress_callback = MagicMock() + + result = minimizer.sample( + x=np.array([1.0]), + y=np.array([0.1]), + weights=np.array([1.0]), + samples=10, + burn=5, + thin=1, + progress_callback=progress_callback, + ) + + assert result is not None + call_kwargs = mock_FitDriver.call_args.kwargs + assert 'monitors' in call_kwargs + assert len(call_kwargs['monitors']) == 1 + assert isinstance(call_kwargs['monitors'][0], BumpsProgressMonitor) + + def test_sample_aborted_by_user_raises_fit_error(self, minimizer: Bumps, monkeypatch) -> None: + """Verify that sampling abortion raises FitError.""" + self._setup_driver_mock(monkeypatch, fitter_state_value=TestBumpsSample.ABORT) + minimizer._make_model = MagicMock(return_value=MagicMock(return_value=MagicMock())) + + with pytest.raises(FitError, match='Sampling aborted by user'): + minimizer.sample(x=np.array([1.0]), y=np.array([0.1]), weights=np.array([1.0])) + + def test_sample_driver_exception_restores_parameters( + self, minimizer: Bumps, monkeypatch + ) -> None: + """Verify that a driver exception during sampling restores parameter values.""" + self._setup_driver_mock(monkeypatch, fit_side_effect=RuntimeError('driver failed')) + minimizer._make_model = MagicMock(return_value=MagicMock(return_value=MagicMock())) + minimizer._restore_parameter_values = MagicMock() + + with pytest.raises(RuntimeError, match='driver failed'): + minimizer.sample(x=np.array([1.0]), y=np.array([0.1]), weights=np.array([1.0])) + + minimizer._restore_parameter_values.assert_called_once() + + def test_sample_conflicting_population_raises(self, minimizer: Bumps) -> None: + with pytest.raises(ValueError, match='Conflicting population'): + minimizer.sample( + x=np.array([1.0]), + y=np.array([0.1]), + weights=np.array([1.0]), + chains=5, + population=10, + samples=10, + burn=0, + thin=1, + ) + + def test_sample_rejects_non_callable_callback(self, minimizer: Bumps, monkeypatch) -> None: + import bumps.names + + monkeypatch.setattr(bumps.names, 'FitProblem', MagicMock(return_value=MagicMock())) + minimizer._make_model = MagicMock(return_value=MagicMock(return_value=MagicMock())) + + with pytest.raises(ValueError, match='progress_callback must be callable'): + minimizer.sample( + x=np.array([1.0]), + y=np.array([0.1]), + weights=np.array([1.0]), + samples=10, + burn=5, + thin=1, + progress_callback='not-callable', + ) + + +# =================================================================== +# _build_sample_progress_payload +# =================================================================== + + +class TestBuildSampleProgressPayload: + @pytest.fixture + def minimizer(self) -> Bumps: + return Bumps( + obj='obj', + fit_function='fit_function', + minimizer_enum=MagicMock(package='bumps', method='amoeba'), + ) + + def test_payload_structure_and_sampling_flag(self, minimizer: Bumps) -> None: + b = minimizer + + mock_problem = MagicMock() + mock_problem.chisq.side_effect = [25.0, 12.5] + mock_problem.labels.return_value = ['palpha'] + mock_problem.getp.return_value = np.array([1.0]) + b._cached_pars = {'alpha': MagicMock(value=1.0)} + + payload = b._build_sample_progress_payload(mock_problem, 7, np.array([1.0]), 12.5) + + assert payload['iteration'] == 7 + assert payload['chi2'] == 25.0 + assert payload['reduced_chi2'] == 12.5 + assert payload['parameter_values'] == {'alpha': 1.0} + assert payload['sampling'] is True + assert payload['finished'] is False + assert payload['refresh_plots'] is False + + def test_payload_keys(self, minimizer: Bumps) -> None: + b = minimizer + mock_problem = MagicMock() + mock_problem.chisq.side_effect = [10.0, 5.0] + mock_problem.labels.return_value = ['pa'] + mock_problem.getp.return_value = np.array([5.0]) + b._cached_pars = {'a': MagicMock(value=5.0)} + + payload = b._build_sample_progress_payload(mock_problem, 1, np.array([5.0]), nllf=5.0) + + expected_keys = { + 'iteration', + 'chi2', + 'reduced_chi2', + 'parameter_values', + 'refresh_plots', + 'finished', + 'sampling', + } + assert set(payload.keys()) == expected_keys + + +# =================================================================== +# _set_parameter_fit_result with stack_status=True +# =================================================================== + + +class TestSetParameterFitResultWithStack: + @pytest.fixture + def minimizer(self) -> Bumps: + return Bumps( + obj='obj', + fit_function='fit_function', + minimizer_enum=MagicMock(package='bumps', method='amoeba'), + ) + + def test_stack_status_true_calls_begin_end_macro(self, minimizer: Bumps) -> None: + from easyscience import global_object + + global_object.stack.enabled = False + + minimizer._cached_pars = {'a': MagicMock(), 'b': MagicMock()} + minimizer._cached_pars['a'].value = 'old_a' + minimizer._cached_pars['b'].value = 'old_b' + minimizer._restore_parameter_values = MagicMock() + + mock_fit_result = MagicMock() + mock_fit_result.x = np.array([1.0, 2.0]) + mock_fit_result.dx = np.array([0.1, 0.2]) + + mock_par_a = MagicMock() + mock_par_a.name = 'pa' + mock_par_b = MagicMock() + mock_par_b.name = 'pb' + par_list = [mock_par_a, mock_par_b] + + minimizer._set_parameter_fit_result(mock_fit_result, True, par_list) + + assert minimizer._cached_pars['a'].value == 1.0 + assert minimizer._cached_pars['a'].error == 0.1 + assert minimizer._cached_pars['b'].value == 2.0 + assert minimizer._cached_pars['b'].error == 0.2 + minimizer._restore_parameter_values.assert_called_once() + + +# =================================================================== +# convert_to_par_object +# =================================================================== + + +class TestConvertToParObject: + def test_convert_parameter_object(self) -> None: + from easyscience.variable import Parameter + + param = Parameter('thickness', 42.0, min=0.0, max=100.0) + param.fixed = False + + result = Bumps.convert_to_par_object(param) + + # convert_to_par_object uses obj.unique_name which is auto-assigned + assert result.name.startswith('p') + assert result.value == 42.0 + assert result.bounds == (0.0, 100.0) + assert result.fixed is False + + def test_convert_fixed_parameter(self) -> None: + from easyscience.variable import Parameter + + param = Parameter('roughness', 5.0, min=0.0, max=20.0) + param.fixed = True + + result = Bumps.convert_to_par_object(param) + + assert result.name.startswith('p') + assert result.fixed is True + + +# =================================================================== +# fit() with abort_test +# =================================================================== + + +class TestFitWithAbortTest: + @pytest.fixture + def minimizer(self) -> Bumps: + return Bumps( + obj='obj', + fit_function='fit_function', + minimizer_enum=MagicMock(package='bumps', method='amoeba'), + ) + + def test_abort_test_passed_to_fit_driver(self, minimizer: Bumps, monkeypatch) -> None: + from easyscience import global_object + + global_object.stack.enabled = False + + mock_driver = MagicMock() + mock_driver.clip = MagicMock() + mock_driver.fit = MagicMock(return_value=(np.array([42.0]), 0.0)) + mock_driver.stderr = MagicMock(return_value=np.array([0.1])) + mock_driver.monitor_runner.history.step = [0] + mock_FitDriver = MagicMock(return_value=mock_driver) + monkeypatch.setattr( + easyscience.fitting.minimizers.minimizer_bumps, 'FitDriver', mock_FitDriver + ) + + mock_problem = MagicMock() + mock_problem._parameters = [] + monkeypatch.setattr( + easyscience.fitting.minimizers.minimizer_bumps, + 'FitProblem', + MagicMock(return_value=mock_problem), + ) + + minimizer._make_model = MagicMock(return_value=MagicMock(return_value=MagicMock())) + minimizer._gen_fit_results = MagicMock(return_value='result') + minimizer._resolve_fitclass = MagicMock(return_value=MagicMock(id='amoeba')) + minimizer._set_parameter_fit_result = MagicMock() + minimizer._cached_pars = {'a': MagicMock(value=1.0)} + minimizer._cached_pars_vals = {'a': (1.0, 0.0)} + + abort_test = MagicMock(return_value=False) + + minimizer.fit( + x=np.array([1.0]), y=np.array([2.0]), weights=np.array([1.0]), abort_test=abort_test + ) + + call_kwargs = mock_FitDriver.call_args.kwargs + assert callable(call_kwargs['abort_test']) + assert call_kwargs['abort_test'] is not (lambda: False) diff --git a/tests/unit/fitting/test_multi_fitter.py b/tests/unit/fitting/test_multi_fitter.py index a54a927b..f8d8495e 100644 --- a/tests/unit/fitting/test_multi_fitter.py +++ b/tests/unit/fitting/test_multi_fitter.py @@ -3,6 +3,7 @@ from unittest.mock import MagicMock +import numpy as np import pytest from easyscience import ObjBase @@ -61,3 +62,257 @@ def test_fit_progress_callback(self, multi_fitter: MultiFitter): max_evaluations=None, progress_callback=progress_callback, ) + + +# =================================================================== +# MultiFitter.sample() — Bayesian DREAM sampling +# =================================================================== + + +class TestMultiFitterSample: + @pytest.fixture + def multi_fitter(self, monkeypatch): + monkeypatch.setattr(Fitter, '_update_minimizer', MagicMock()) + fit_object_1 = Line(1.0, 0.5) + fit_object_2 = Line(2.0, 1.5) + return MultiFitter([fit_object_1, fit_object_2], [fit_object_1, fit_object_2]) + + def test_sample_basic(self, multi_fitter: MultiFitter): + """Verify sample() calls the minimizer's sample() and returns its result.""" + import numpy as np + + multi_fitter._precompute_reshaping = MagicMock( + return_value=('x_fit', 'x_new', 'y_new', 'weights', 'dims') + ) + multi_fitter._fit_function_wrapper = MagicMock(return_value='wrapped_fit_function') + multi_fitter._minimizer = MagicMock() + multi_fitter._minimizer.package = 'bumps' + expected_result = { + 'draws': np.array([[1.0]]), + 'param_names': ['a'], + 'state': 'stub', + 'logp': None, + } + multi_fitter._minimizer.sample = MagicMock(return_value=expected_result) + + result = multi_fitter.sample( + x=[np.array([1.0]), np.array([2.0])], + y=[np.array([0.1]), np.array([0.2])], + weights=[np.array([1.0]), np.array([1.0])], + samples=100, + burn=20, + thin=2, + population=5, + ) + + assert result == expected_result + multi_fitter._minimizer.sample.assert_called_once() + call_kwargs = multi_fitter._minimizer.sample.call_args.kwargs + assert call_kwargs['samples'] == 100 + assert call_kwargs['burn'] == 20 + assert call_kwargs['thin'] == 2 + assert call_kwargs['population'] == 5 + assert call_kwargs['progress_callback'] is None + + def test_sample_raises_if_not_bumps(self, multi_fitter: MultiFitter): + """sample() should raise RuntimeError if minimizer is not BUMPS.""" + multi_fitter._precompute_reshaping = MagicMock( + return_value=('x_fit', 'x_new', 'y_new', 'weights', 'dims') + ) + multi_fitter._fit_function_wrapper = MagicMock(return_value='wrapped_fit_function') + multi_fitter._minimizer = MagicMock() + multi_fitter._minimizer.package = 'lmfit' # Not bumps + + with pytest.raises(RuntimeError, match='Bayesian sampling requires a BUMPS minimizer'): + multi_fitter.sample( + x=[np.array([1.0])], + y=[np.array([0.1])], + weights=[np.array([1.0])], + ) + + def test_sample_with_progress_callback(self, multi_fitter: MultiFitter): + """Progress callback should be forwarded to minimizer.sample().""" + multi_fitter._precompute_reshaping = MagicMock( + return_value=('x_fit', 'x_new', 'y_new', 'weights', 'dims') + ) + multi_fitter._fit_function_wrapper = MagicMock(return_value='wrapped_fit_function') + multi_fitter._minimizer = MagicMock() + multi_fitter._minimizer.package = 'bumps' + multi_fitter._minimizer.sample = MagicMock( + return_value={'draws': [], 'param_names': [], 'state': None, 'logp': None} + ) + + progress_callback = MagicMock() + + multi_fitter.sample( + x=[np.array([1.0])], + y=[np.array([0.1])], + weights=[np.array([1.0])], + progress_callback=progress_callback, + ) + + kwargs = multi_fitter._minimizer.sample.call_args.kwargs + assert kwargs['progress_callback'] is progress_callback + + def test_sample_population_alias(self, multi_fitter: MultiFitter): + """chains parameter is aliased to population.""" + multi_fitter._precompute_reshaping = MagicMock( + return_value=('x_fit', 'x_new', 'y_new', 'weights', 'dims') + ) + multi_fitter._fit_function_wrapper = MagicMock(return_value='wrapped_fit_function') + multi_fitter._minimizer = MagicMock() + multi_fitter._minimizer.package = 'bumps' + multi_fitter._minimizer.sample = MagicMock( + return_value={'draws': [], 'param_names': [], 'state': None, 'logp': None} + ) + + multi_fitter.sample( + x=[np.array([1.0])], + y=[np.array([0.1])], + weights=[np.array([1.0])], + chains=7, # Should be forwarded as population=7 + ) + + kwargs = multi_fitter._minimizer.sample.call_args.kwargs + assert kwargs['population'] == 7 + assert kwargs['chains'] is None + + def test_sample_conflicting_population_raises(self, multi_fitter: MultiFitter): + multi_fitter._precompute_reshaping = MagicMock( + return_value=('x_fit', 'x_new', 'y_new', 'weights', 'dims') + ) + multi_fitter._fit_function_wrapper = MagicMock(return_value='wrapped_fit_function') + multi_fitter._minimizer = MagicMock() + multi_fitter._minimizer.package = 'bumps' + + with pytest.raises(ValueError, match='Conflicting population'): + multi_fitter.sample( + x=[np.array([1.0])], + y=[np.array([0.1])], + weights=[np.array([1.0])], + chains=5, + population=10, + ) + + def test_sample_restores_original_fit_function(self, multi_fitter: MultiFitter): + """After sample() completes (even on error) the original fit_function is restored.""" + original_ff = multi_fitter.fit_function + multi_fitter._precompute_reshaping = MagicMock( + return_value=('x_fit', 'x_new', 'y_new', 'weights', 'dims') + ) + multi_fitter._fit_function_wrapper = MagicMock(return_value='wrapped_fit_function') + multi_fitter._minimizer = MagicMock() + multi_fitter._minimizer.package = 'bumps' + multi_fitter._minimizer.sample = MagicMock(side_effect=RuntimeError('boom')) + + with pytest.raises(RuntimeError): + multi_fitter.sample( + x=[np.array([1.0])], + y=[np.array([0.1])], + weights=[np.array([1.0])], + ) + + assert multi_fitter.fit_function is original_ff + + +# =================================================================== +# MultiFitter._post_compute_reshaping +# =================================================================== + + +class TestPostComputeReshaping: + def test_splits_single_result_into_two(self): + """Verify _post_compute_reshaping splits combined result by dataset.""" + import numpy as np + + from easyscience.fitting.minimizers import FitResults + + fit_objects = [Line(1.0, 0.5), Line(2.0, 1.5)] + mf = MultiFitter(fit_objects, fit_objects) + + # Simulate a combined result + combined = FitResults() + combined.success = True + combined.x = np.array([0, 1, 2]) + combined.y_obs = np.array([0.5, 1.0, 1.5]) + combined.y_calc = np.array([0.51, 0.99, 1.51]) + combined.y_err = np.array([0.01, 0.02, 0.03]) + combined.p = {'pm': 1.0, 'pc': 0.5} + combined.p0 = {'pm': 0.0, 'pc': 0.0} + combined.n_evaluations = 100 + combined.iterations = 50 + combined.message = 'success' + combined.minimizer_engine = 'bumps' + combined.engine_result = 'engine_result' + + # Set dependent_dims as _precompute_reshaping would + mf._dependent_dims = [(2,), (1,)] + + x_input = [np.array([0.0, 1.0]), np.array([2.0])] + y_input = [np.array([0.5, 1.0]), np.array([1.5])] + + results = mf._post_compute_reshaping(combined, x_input, y_input) + + assert len(results) == 2 + assert results[0].success is True + assert results[1].success is True + assert len(results[0].y_obs) == 2 + assert len(results[1].y_obs) == 1 + assert results[0].total_results is combined + assert results[1].total_results is combined + assert np.allclose(results[0].y_calc, [0.51, 0.99]) + + def test_handles_single_dataset(self): + import numpy as np + + from easyscience.fitting.minimizers import FitResults + + fit_objects = [Line(1.0, 0.5)] + mf = MultiFitter(fit_objects, fit_objects) + + combined = FitResults() + combined.success = True + combined.y_obs = np.array([1.0, 2.0, 3.0]) + combined.y_calc = np.array([1.1, 2.1, 3.1]) + combined.y_err = np.array([0.1, 0.1, 0.1]) + combined.p = {'pm': 1.0} + combined.p0 = {'pm': 0.0} + combined.n_evaluations = 50 + combined.iterations = 25 + combined.message = 'ok' + combined.minimizer_engine = 'bumps' + combined.engine_result = 'er' + + mf._dependent_dims = [(3,)] + x_input = [np.array([0.0, 1.0, 2.0])] + y_input = [np.array([1.0, 2.0, 3.0])] + + results = mf._post_compute_reshaping(combined, x_input, y_input) + + assert len(results) == 1 + assert np.allclose(results[0].y_calc, [1.1, 2.1, 3.1]) + + +# =================================================================== +# MultiFitter._precompute_reshaping with weights=None +# =================================================================== + + +class TestPrecomputeReshaping: + def test_weights_all_none_returns_none(self): + """When all weights are None, _precompute_reshaping should return None for weights.""" + import numpy as np + + fit_objects = [Line(1.0, 0.5), Line(2.0, 1.5)] + mf = MultiFitter(fit_objects, fit_objects) + + x = [np.array([1.0, 2.0]), np.array([3.0])] + y = [np.array([1.5, 2.5]), np.array([4.5])] + weights = [None, None] + + x_fit, x_new, y_new, w_new, dims = MultiFitter._precompute_reshaping( + x, y, weights, vectorized=False + ) + + assert w_new is None + assert len(dims) == 2 From efd7ff176ab33f84d8a20843dfe18663560e39b8 Mon Sep 17 00:00:00 2001 From: rozyczko Date: Tue, 12 May 2026 13:51:12 +0200 Subject: [PATCH 05/15] proper docstrings --- .../fitting/minimizers/minimizer_bumps.py | 75 +++++++++++++----- src/easyscience/fitting/multi_fitter.py | 79 +++++++++++-------- 2 files changed, 102 insertions(+), 52 deletions(-) diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 3479ff92..174b19f7 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -117,6 +117,9 @@ def fit( Optional callback for progress updates. The payload field ``iteration`` carries the BUMPS optimizer step index. By default, None. + abort_test : Callable[[], bool] | None, default=None + Optional callback that returns ``True`` to signal that sampling should be aborted. + Called periodically during the DREAM sampling loop. minimizer_kwargs : dict | None, default=None Additional keyword arguments passed to the BUMPS minimizer. By default, None. @@ -393,28 +396,58 @@ def sample( ) -> dict: """Run Bayesian MCMC sampling using the BUMPS DREAM sampler. - Builds a BUMPS :class:`~bumps.names.FitProblem` from the current - model and runs the DREAM sampler. This is the public minimizer-level - entry point for Bayesian sampling; the higher-level - :meth:`easyscience.fitting.multi_fitter.MultiFitter.sample` delegates - to this method after flattening multi-dataset arrays. - - :param x: Flattened independent variable array. - :param y: Flattened dependent variable array. - :param weights: Flattened weight array. - :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 for advanced users. - :param seed: Best-effort random seed. - :param sampler_kwargs: Additional keyword arguments forwarded to - :func:`bumps.fitters.fit`. - :param progress_callback: Optional callback for progress updates during - sampling. The payload dict includes ``iteration`` (DREAM generation - number) and ``sampling: True``. - :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'`", + Builds a BUMPS `FitProblem` from the current model and runs the DREAM + sampler. This is the public minimizer-level entry point for Bayesian + sampling; the higher-level `MultiFitter.sample` delegates to this + method after flattening multi-dataset arrays. + + Parameters + ---------- + x : np.ndarray + Flattened independent variable array. + y : np.ndarray + Flattened dependent variable array. + weights : np.ndarray + Flattened weight array. + samples : int, default=10000 + Number of retained DREAM samples requested from BUMPS. + burn : int, default=2000 + Burn-in steps. + thin : int, default=10 + Thinning interval. + chains : int | None, default=None + User-friendly alias for BUMPS DREAM population count. + population : int | None, default=None + BUMPS DREAM population count for advanced users. + seed : int | None, default=None + Best-effort random seed. + sampler_kwargs : dict | None, default=None + Additional keyword arguments forwarded to `bumps.fitters.fit`. + progress_callback : Callable[[dict], bool | None] | None, default=None + Optional callback for progress updates during sampling. The + payload dict includes ``iteration`` (DREAM generation number) and + ``sampling: True``. + abort_test : Callable[[], bool] | None, default=None + Optional callback that returns ``True`` to signal that sampling + should be aborted. Called periodically during the DREAM sampling + loop. + + Returns + ------- + dict + Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, and ``'logp'``. + + Raises + ------ + ValueError + If both ``chains`` and ``population`` are provided with different + values, or if ``progress_callback`` is not callable. + FitError + If DREAM sampling was aborted by the user (via ``abort_test``). + Exception + Re-raised from DREAM fitting if any unexpected error occurs + (parameter values are restored beforehand). """ from bumps.fitters import DreamFit from bumps.names import FitProblem diff --git a/src/easyscience/fitting/multi_fitter.py b/src/easyscience/fitting/multi_fitter.py index fd6655be..13a79350 100644 --- a/src/easyscience/fitting/multi_fitter.py +++ b/src/easyscience/fitting/multi_fitter.py @@ -211,39 +211,56 @@ def sample( Requires that the current minimizer is a BUMPS instance (i.e. the minimizer was switched to ``AvailableMinimizers.Bumps`` or equivalent). - :param x: List of independent variable arrays (one per dataset). - :type x: List[np.ndarray] - :param y: List of dependent variable arrays (one per dataset). - :type y: List[np.ndarray] - :param weights: List of weight arrays (one per dataset). - :type weights: List[np.ndarray] - :param samples: Number of retained DREAM samples requested from BUMPS. - :type samples: int - :param burn: Burn-in steps. - :type burn: int - :param thin: Thinning interval. - :type thin: int - :param chains: User-friendly alias for BUMPS DREAM population count. - :type chains: int | None - :param population: BUMPS DREAM population count (``pop``) for advanced users. - :type population: int | None - :param seed: Best-effort random seed. BUMPS DREAM may use additional - internal RNG state that is not controlled by this seed, so exact + Parameters + ---------- + x : List[np.ndarray] + List of independent variable arrays (one per dataset). + y : List[np.ndarray] + List of dependent variable arrays (one per dataset). + weights : List[np.ndarray] + List of weight arrays (one per dataset). + samples : int, default=10000 + Number of retained DREAM samples requested from BUMPS. + burn : int, default=1000 + Burn-in steps. + thin : int, default=10 + Thinning interval. + chains : int | None, default=None + User-friendly alias for BUMPS DREAM population count. + population : int | None, default=None + BUMPS DREAM population count (``pop``) for advanced users. + seed : int | None, default=None + Best-effort random seed. BUMPS DREAM may use additional internal + RNG state that is not controlled by this seed, so exact reproducibility is not guaranteed. - :type seed: int | None - :param vectorized: Whether the fit function expects vectorized - (multidimensional) input. Defaults to ``False``. - :type vectorized: bool - :param sampler_kwargs: Additional keyword arguments forwarded to the - BUMPS DREAM sampler via :func:`bumps.fitters.fit`. - :type sampler_kwargs: dict | None - :param progress_callback: Optional callback for progress updates during - sampling. The payload dict includes ``iteration`` (DREAM generation - number) and ``sampling: True``. - :return: Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'`", + vectorized : bool, default=False + Whether the fit function expects vectorized (multidimensional) + input. + sampler_kwargs : dict | None, default=None + Additional keyword arguments forwarded to the BUMPS DREAM sampler + via `bumps.fitters.fit`. + progress_callback : Callable[[dict], bool | None] | None, default=None + Optional callback for progress updates during sampling. The + payload dict includes ``iteration`` (DREAM generation number) and + ``sampling: True``. + abort_test : Callable[[], bool] | None, default=None + Optional callback that returns ``True`` to signal that sampling + should be aborted. Called periodically during the DREAM sampling + loop. + + Returns + ------- + Dict + Dictionary with keys ``'draws'``, ``'param_names'``, ``'state'``, and ``'logp'``. - :rtype: dict - :raises RuntimeError: If the current minimizer is not a BUMPS instance. + + Raises + ------ + RuntimeError + If the current minimizer is not a BUMPS instance. + ValueError + If both ``chains`` and ``population`` are provided with different + values. """ # --- Alias resolution --- if chains is not None and population is not None: From 2913ca64c2194e22d25dd207932c993422f5b608 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Tue, 12 May 2026 17:11:08 +0200 Subject: [PATCH 06/15] we need `legacy` folder in the wheel --- pixi.lock | 13 +++---------- pyproject.toml | 5 ++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/pixi.lock b/pixi.lock index 83a140f2..d22ec2c2 100644 --- a/pixi.lock +++ b/pixi.lock @@ -6,8 +6,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -811,8 +809,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -1606,8 +1602,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -2411,8 +2405,6 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple - options: - pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -4174,8 +4166,8 @@ packages: requires_python: '>=3.11' - pypi: ./ name: easyscience - version: 2.3.1+devdirty16 - sha256: cfdcbbdca748c028ca05d142a93d60388cabb47e3afe6995d9bdb029a82ed28b + version: 2.3.1+devdirty11 + sha256: 9046eb543a77a4603204f7e67b55f0f0e82840101820ef1051cbfc23766ed4f0 requires_dist: - asteval - bumps @@ -4222,6 +4214,7 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.11' + editable: true - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 md5: 8e662bd460bda79b1ea39194e3c4c9ab diff --git a/pyproject.toml b/pyproject.toml index bdce82da..53acf732 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,7 @@ requires = ['hatchling', 'versioningit'] [tool.hatch.build.targets.wheel] packages = ['src/easyscience'] -exclude = ['src/easyscience/legacy'] +# exclude = ['src/easyscience/legacy'] [tool.hatch.metadata] allow-direct-references = true @@ -211,8 +211,7 @@ select = [ # 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 + # 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/ ] From 8aa06c1645ff3d21346fe3387889879c100ea05d Mon Sep 17 00:00:00 2001 From: rozyczko Date: Wed, 13 May 2026 11:59:14 +0200 Subject: [PATCH 07/15] PR review issues addressed --- pixi.lock | 698 +++++++++++++----- .../fitting/minimizers/minimizer_bumps.py | 27 +- src/easyscience/fitting/multi_fitter.py | 4 +- 3 files changed, 542 insertions(+), 187 deletions(-) diff --git a/pixi.lock b/pixi.lock index d22ec2c2..f84d1522 100644 --- a/pixi.lock +++ b/pixi.lock @@ -6,6 +6,8 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -162,7 +164,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -197,14 +199,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.2-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -221,7 +223,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -229,7 +231,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-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/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/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-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 @@ -238,7 +240,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-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/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -250,13 +252,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -266,14 +268,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -431,7 +433,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -466,14 +468,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -490,7 +492,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -498,7 +500,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-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/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/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-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 @@ -507,7 +509,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-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/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -519,13 +521,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -534,14 +536,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -686,7 +688,7 @@ environments: - 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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.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/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-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/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 @@ -721,14 +723,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.2-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -745,7 +747,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -753,7 +755,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.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/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl @@ -762,7 +764,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -774,13 +776,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -790,14 +792,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -809,6 +811,8 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -963,7 +967,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.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/fd/35/202235eb5c3c14c212462cd91d61b7386bf8fc44bc7a77f4742d2a69174b/coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -998,14 +1002,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.2-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -1022,7 +1026,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -1030,7 +1034,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.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/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/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.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/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl @@ -1039,7 +1043,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-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/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -1051,13 +1055,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1067,14 +1071,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -1228,7 +1232,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/7f/8d/46692d24b3f395d4cbf17bfcc57136b4f2f9c0c0df864b0bddfc1d71a014/coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1263,14 +1267,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.2-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -1287,7 +1291,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -1295,7 +1299,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-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/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/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-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 @@ -1304,7 +1308,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-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/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -1316,13 +1320,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1331,14 +1335,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -1479,7 +1483,7 @@ environments: - 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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f0/f0/a71ddbd874431e7a7cd96071f0c331cfbbad07704833c765d24ffbab8a67/coverage-7.14.0-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/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 @@ -1514,14 +1518,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.2-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -1538,7 +1542,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -1546,7 +1550,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-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/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/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-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 @@ -1555,7 +1559,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -1567,13 +1571,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1583,14 +1587,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -1602,6 +1606,8 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -1758,7 +1764,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1793,14 +1799,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.2-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -1817,7 +1823,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -1825,7 +1831,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-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/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/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-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 @@ -1834,7 +1840,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-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/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -1846,13 +1852,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -1862,14 +1868,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -2027,7 +2033,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -2062,14 +2068,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -2086,7 +2092,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -2094,7 +2100,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-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/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/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-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 @@ -2103,7 +2109,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-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/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -2115,13 +2121,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -2130,14 +2136,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -2282,7 +2288,7 @@ environments: - 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/98/ba/fc20faf5b2bb04615fa906f8daecfe896e6f7a56b34debd93c4a4d63e6e5/copier-9.15.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/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-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/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 @@ -2317,14 +2323,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl - 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/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.2-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/38/7e/7b91c89a4cf0f543a83be978657afb20c86af6d725253e319589dcc4ce52/lmfit-1.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/f0/834e479e47e499b6478e807fb57b31cc2db696c4db30557bb6f5aea4a90b/mando-0.7.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.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/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/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.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/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 @@ -2341,7 +2347,7 @@ environments: - 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/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.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 @@ -2349,7 +2355,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.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/3a/eb/fea4d1d51c49832120f7f285d07306db3960f423a2612c6057caf3e8196f/pip-26.1.1-py3-none-any.whl @@ -2358,7 +2364,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/79/ad/45312df6b63ba64ea35b8d8f5f0c577aac16e6b416eafe8e1cb34e03f9a7/plumbum-1.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-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/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl @@ -2370,13 +2376,13 @@ environments: - 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/30/d4/24d543ab8b8158b7f5a97113c831205f5c900c92c8762b1e7f44b7ea0405/python_discovery-1.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/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/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/f7/d00d9b4a0313a6be3a3e0818e6375e15da6d7076f4ae47d1324e7ca986a1/radon-6.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/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 @@ -2386,14 +2392,14 @@ 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/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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.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/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-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 @@ -2405,6 +2411,8 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -3884,45 +3892,45 @@ packages: - questionary>=1.8.1 - typing-extensions>=4.0.0,<5.0.0 ; python_full_version < '3.11' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl +- pypi: https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.5 - sha256: 941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3 + version: 7.14.0 + sha256: 9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca 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 +- pypi: https://files.pythonhosted.org/packages/7f/8d/46692d24b3f395d4cbf17bfcc57136b4f2f9c0c0df864b0bddfc1d71a014/coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl name: coverage - version: 7.13.5 - sha256: 145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587 + version: 7.14.0 + sha256: a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4 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 +- pypi: https://files.pythonhosted.org/packages/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-win_amd64.whl name: coverage - version: 7.13.5 - sha256: 631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b + version: 7.14.0 + sha256: 0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e 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 +- pypi: https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl name: coverage - version: 7.13.5 - sha256: ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b + version: 7.14.0 + sha256: 23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66 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 +- pypi: https://files.pythonhosted.org/packages/f0/f0/a71ddbd874431e7a7cd96071f0c331cfbbad07704833c765d24ffbab8a67/coverage-7.14.0-cp311-cp311-win_amd64.whl name: coverage - version: 7.13.5 - sha256: 78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3 + version: 7.14.0 + sha256: bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1 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 +- pypi: https://files.pythonhosted.org/packages/fd/35/202235eb5c3c14c212462cd91d61b7386bf8fc44bc7a77f4742d2a69174b/coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.5 - sha256: 258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9 + version: 7.14.0 + sha256: 9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' @@ -4166,8 +4174,8 @@ packages: requires_python: '>=3.11' - pypi: ./ name: easyscience - version: 2.3.1+devdirty11 - sha256: 9046eb543a77a4603204f7e67b55f0f0e82840101820ef1051cbfc23766ed4f0 + version: 2.3.1+devdirty12 + sha256: ec0f736717da6cf2e6807feed4c5d7f304603284cb2d06fea08361420615c957 requires_dist: - asteval - bumps @@ -4214,7 +4222,6 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.11' - editable: true - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 md5: 8e662bd460bda79b1ea39194e3c4c9ab @@ -5262,10 +5269,10 @@ packages: 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 +- pypi: https://files.pythonhosted.org/packages/4c/65/b4b86e5fa07543bfbbcdc6c9f7f9f561e66a5f3539992e3009973f2b1314/jupytext-1.19.2-py3-none-any.whl name: jupytext - version: 1.19.1 - sha256: d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9 + version: 1.19.2 + sha256: 8a31e896c7e9215841783aade24336e945543057e1c2d7f00b22f9e870348688 requires_dist: - markdown-it-py>=1.0 - mdit-py-plugins @@ -6028,10 +6035,10 @@ packages: - 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 +- 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.1.0 - sha256: d4939a62a2dd0cd9cb80a191a711ba1d39bac8ed5ef9e9966895b0171c01c46d + version: 4.2.0 + sha256: 9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a requires_dist: - mdurl~=0.1 - psutil ; extra == 'benchmarking' @@ -6286,10 +6293,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl name: mdit-py-plugins - version: 0.5.0 - sha256: 07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f + version: 0.6.1 + sha256: 214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d requires_dist: - markdown-it-py>=2.0.0,<5.0.0 - pre-commit ; extra == 'code-style' @@ -6299,6 +6306,7 @@ packages: - pytest ; extra == 'testing' - pytest-cov ; extra == 'testing' - pytest-regressions ; extra == 'testing' + - pytest-timeout ; 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 @@ -6666,6 +6674,28 @@ packages: - sqlparse ; extra == 'sql' - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.0-py3-none-any.whl + name: narwhals + version: 2.21.0 + sha256: 1e6617d0fca68ae1fda29e5397c4eaacd3ffc9fffe6bcd6ded0c690475e853be + 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 @@ -7144,10 +7174,10 @@ packages: - 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 +- 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: 61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76 + sha256: 07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991 requires_dist: - numpy>=1.26.0 ; python_full_version < '3.14' - numpy>=2.3.3 ; python_full_version >= '3.14' @@ -7234,10 +7264,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl name: pandas - version: 3.0.2 - sha256: ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df + version: 3.0.3 + sha256: a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44 requires_dist: - numpy>=1.26.0 ; python_full_version < '3.14' - numpy>=2.3.3 ; python_full_version >= '3.14' @@ -7285,7 +7315,7 @@ packages: - pyqt5>=5.15.9 ; extra == 'clipboard' - qtpy>=2.4.2 ; extra == 'clipboard' - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' + - pytz>=2020.1 ; extra == 'timezone' - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - beautifulsoup4>=4.12.3 ; extra == 'all' @@ -7311,7 +7341,7 @@ packages: - 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' + - pytz>=2020.1 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - qtpy>=2.4.2 ; extra == 'all' - scipy>=1.14.1 ; extra == 'all' @@ -7324,10 +7354,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl name: pandas - version: 3.0.2 - sha256: 07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991 + version: 3.0.3 + sha256: 8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27 requires_dist: - numpy>=1.26.0 ; python_full_version < '3.14' - numpy>=2.3.3 ; python_full_version >= '3.14' @@ -7375,7 +7405,7 @@ packages: - pyqt5>=5.15.9 ; extra == 'clipboard' - qtpy>=2.4.2 ; extra == 'clipboard' - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' + - pytz>=2020.1 ; extra == 'timezone' - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - beautifulsoup4>=4.12.3 ; extra == 'all' @@ -7401,7 +7431,7 @@ packages: - 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' + - pytz>=2020.1 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - qtpy>=2.4.2 ; extra == 'all' - scipy>=1.14.1 ; extra == 'all' @@ -7414,10 +7444,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl name: pandas - version: 3.0.2 - sha256: dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c + version: 3.0.3 + sha256: 4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639 requires_dist: - numpy>=1.26.0 ; python_full_version < '3.14' - numpy>=2.3.3 ; python_full_version >= '3.14' @@ -7465,7 +7495,7 @@ packages: - pyqt5>=5.15.9 ; extra == 'clipboard' - qtpy>=2.4.2 ; extra == 'clipboard' - zstandard>=0.23.0 ; extra == 'compression' - - pytz>=2024.2 ; extra == 'timezone' + - pytz>=2020.1 ; extra == 'timezone' - adbc-driver-postgresql>=1.2.0 ; extra == 'all' - adbc-driver-sqlite>=1.2.0 ; extra == 'all' - beautifulsoup4>=4.12.3 ; extra == 'all' @@ -7491,7 +7521,277 @@ packages: - 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' + - pytz>=2020.1 ; 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/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl + name: pandas + version: 3.0.3 + sha256: 39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7 + 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>=2020.1 ; 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>=2020.1 ; 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/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 3.0.3 + sha256: a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a + 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>=2020.1 ; 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>=2020.1 ; 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/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl + name: pandas + version: 3.0.3 + sha256: 6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea + 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>=2020.1 ; 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>=2020.1 ; extra == 'all' - pyxlsb>=1.0.10 ; extra == 'all' - qtpy>=2.4.2 ; extra == 'all' - scipy>=1.14.1 ; extra == 'all' @@ -7914,36 +8214,51 @@ packages: - pkg:pypi/prompt-toolkit?source=hash-mapping size: 273927 timestamp: 1756321848365 -- 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' +- pypi: https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl + name: propcache + version: 0.5.2 + sha256: c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl + name: propcache + version: 0.5.2 + sha256: dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: propcache + version: 0.5.2 + sha256: 5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl + name: propcache + version: 0.5.2 + sha256: 44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: propcache + version: 0.5.2 + sha256: 4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl + name: propcache + version: 0.5.2 + sha256: fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098 + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py311haee01d2_0.conda sha256: 8d9325af538a8f56013e42bbb91a4dc6935aece34476e20bafacf6007b571e86 md5: 2ed8f6fe8b51d8e19f7621941f7bb95f @@ -8436,10 +8751,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl name: python-discovery - version: 1.3.0 - sha256: 441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f + version: 1.3.1 + sha256: ed188687ebb3b82c01a17cd5ac62fc94d9f6487a7f1a0f9dfe89753fec91039c requires_dist: - filelock>=3.15.4 - platformdirs>=4.3.6,<5 @@ -8447,6 +8762,8 @@ packages: - sphinx-autodoc-typehints>=3.6.3 ; extra == 'docs' - sphinx>=9.1 ; extra == 'docs' - sphinxcontrib-mermaid>=2 ; extra == 'docs' + - sphinxcontrib-towncrier>=0.4 ; extra == 'docs' + - towncrier>=25.8 ; extra == 'docs' - covdefaults>=2.3 ; extra == 'testing' - coverage>=7.5.4 ; extra == 'testing' - pytest-mock>=3.14 ; extra == 'testing' @@ -8881,6 +9198,18 @@ packages: - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' - chardet>=3.0.2,<8 ; extra == 'use-chardet-on-py3' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl + name: requests + version: 2.34.0 + sha256: 917520a21b767485ce7c588f4ebb917c436b24a31231b44228715eaeb5a52c60 + requires_dist: + - charset-normalizer>=2,<4 + - idna>=2.5,<4 + - urllib3>=1.26,<3 + - certifi>=2023.5.7 + - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' + - chardet>=3.0.2,<8 ; extra == 'use-chardet-on-py3' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/returns-0.27.0-pyhc364b38_0.conda sha256: 3b45efeae771f1a20307b36ecdb3a8911a89c05382836b50c62b0a99d8d3dfd8 md5: da94ff04d97ec5efc42cbe5da3c43a84 @@ -9843,10 +10172,10 @@ packages: - pkg:pypi/traitlets?source=compressed-mapping size: 115165 timestamp: 1778074251714 -- 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/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl name: trove-classifiers - version: 2026.4.28.13 - sha256: 8f4b1eb4e16296b57d612965444f87a83861cc989a0451ac97fe4265ddef03b8 + version: 2026.5.7.17 + sha256: 5ec0800de5e2ddbd7c663cb4c0c15328f132dc168813897c18866c5c7b93db33 - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c md5: edd329d7d3a4ab45dcf905899a7a6115 @@ -9941,6 +10270,17 @@ packages: - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' - backports-zstd>=1.0.0 ; python_full_version < '3.14' and extra == 'zstd' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl + name: urllib3 + version: 2.7.0 + sha256: 9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 + requires_dist: + - brotli>=1.2.0 ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi>=1.2.0.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' + - h2>=4,<5 ; extra == 'h2' + - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' + - backports-zstd>=1.0.0 ; python_full_version < '3.14' and extra == 'zstd' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl name: validate-pyproject version: '0.25' @@ -10008,10 +10348,10 @@ packages: - 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 +- pypi: https://files.pythonhosted.org/packages/20/5b/885f479093f6627669d39b57bc3d4e674da532e1a4b247d473a61d8d2118/virtualenv-21.3.2-py3-none-any.whl name: virtualenv - version: 21.3.1 - sha256: d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35 + version: 21.3.2 + sha256: c58ea748fa50bb2a4367da5ba3d30b02458ed40b4ea888faad94021f3309f764 requires_dist: - distlib>=0.3.7,<1 - filelock>=3.24.2,<4 ; python_full_version >= '3.10' diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 174b19f7..03c2fc9e 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -118,8 +118,9 @@ def fit( ``iteration`` carries the BUMPS optimizer step index. By default, None. abort_test : Callable[[], bool] | None, default=None - Optional callback that returns ``True`` to signal that sampling should be aborted. - Called periodically during the DREAM sampling loop. + Optional callback that returns ``True`` to signal that the fit + should be aborted. Called periodically during the + BUMPS optimizer iteration loop. minimizer_kwargs : dict | None, default=None Additional keyword arguments passed to the BUMPS minimizer. By default, None. @@ -441,8 +442,9 @@ def sample( Raises ------ ValueError - If both ``chains`` and ``population`` are provided with different - values, or if ``progress_callback`` is not callable. + If the input shapes or weights are invalid, if both ``chains`` + and ``population`` are provided with different values, or if + ``progress_callback`` is not callable. FitError If DREAM sampling was aborted by the user (via ``abort_test``). Exception @@ -452,10 +454,23 @@ def sample( from bumps.fitters import DreamFit from bumps.names import FitProblem + x, y, weights = np.asarray(x), np.asarray(y), np.asarray(weights) + + if y.shape != x.shape: + raise ValueError('x and y must have the same shape.') + + if weights.shape != x.shape: + raise ValueError('Weights must have the same shape as x and y.') + + if not np.isfinite(weights).all(): + raise ValueError('Weights cannot be NaN or infinite.') + + if (weights <= 0).any(): + raise ValueError('Weights must be strictly positive and non-zero.') + # Build the BUMPS Curve model using the minimizer's existing machinery model_func = self._make_model() - x_flat = np.linspace(0, y.size - 1, y.size) - curve = model_func(x_flat, y, weights) + curve = model_func(x, y, weights) problem = FitProblem(curve) # Best-effort seed: sets numpy's global RNG state just before DREAM starts. diff --git a/src/easyscience/fitting/multi_fitter.py b/src/easyscience/fitting/multi_fitter.py index 13a79350..f40411d7 100644 --- a/src/easyscience/fitting/multi_fitter.py +++ b/src/easyscience/fitting/multi_fitter.py @@ -278,7 +278,7 @@ def sample( pop = None # Flatten multi-dataset arrays - _, x_new, y_new, w_new, _dims = self._precompute_reshaping( + x_fit, x_new, y_new, w_new, _dims = self._precompute_reshaping( x, y, weights, vectorized=vectorized ) self._dependent_dims = _dims @@ -302,7 +302,7 @@ def sample( # Delegate to the BUMPS minimizer's public sample method result = minimizer.sample( - x=x_new, + x=x_fit, y=y_new, weights=w_new, samples=samples, From 131f7c6ea04b49f88517e196c378a30e2348b1e1 Mon Sep 17 00:00:00 2001 From: rozyczko Date: Mon, 18 May 2026 10:58:56 +0200 Subject: [PATCH 08/15] msg -> reason for exceptions --- tests/integration/fitting/test_fitter.py | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/integration/fitting/test_fitter.py b/tests/integration/fitting/test_fitter.py index 9f97b54a..0fb9b327 100644 --- a/tests/integration/fitting/test_fitter.py +++ b/tests/integration/fitting/test_fitter.py @@ -125,7 +125,7 @@ def test_basic_fit(fit_engine: AvailableMinimizers): try: f.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') result = f.fit(x=x, y=y, weights=weights) @@ -173,7 +173,7 @@ def test_fit_result(fit_engine): try: f.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') result = f.fit(x, y, weights=weights) check_fit_results(result, sp_sin, ref_sin, x, sp_ref1=sp_ref1, sp_ref2=sp_ref2) @@ -205,7 +205,7 @@ def test_basic_max_evaluations(fit_engine): try: f.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') f.max_evaluations = 3 result = f.fit(x=x, y=y, weights=weights) # Result should not be the same as the reference @@ -240,7 +240,7 @@ def test_max_evaluations_populates_fit_result_fields(fit_engine): try: f.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') f.max_evaluations = 3 result = f.fit(x=x, y=y, weights=weights) @@ -268,7 +268,7 @@ def test_bumps_max_evaluations_counts_objective_calls() -> None: try: f.switch_minimizer(AvailableMinimizers.Bumps) except AttributeError: - pytest.skip(msg=f'{AvailableMinimizers.Bumps} is not installed') + pytest.skip(reason=f'{AvailableMinimizers.Bumps} is not installed') f.max_evaluations = 3 result = f.fit(x=x, y=y, weights=weights) @@ -306,7 +306,7 @@ def test_basic_tolerance(fit_engine, tolerance): try: f.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') f.tolerance = tolerance result = f.fit(x=x, y=y, weights=weights) # Result should not be the same as the reference @@ -377,7 +377,7 @@ def test_dependent_parameter(fit_engine): try: f.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') result = f.fit(x, y, weights=weights) check_fit_results(result, sp_sin, ref_sin, x) @@ -405,12 +405,12 @@ def test_2D_vectorized(fit_engine): try: ff.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') try: result = ff.fit(x=XY, y=mm(XY), weights=weights, vectorized=True) except FitError as e: if 'Unable to allocate' in str(e): - pytest.skip(msg='MemoryError - Matrix too large') + pytest.skip(reason='MemoryError - Matrix too large') else: raise e assert result.n_pars == len(m2.get_fit_parameters()) @@ -444,12 +444,12 @@ def test_2D_non_vectorized(fit_engine): try: ff.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') try: result = ff.fit(x=XY, y=mm(XY.reshape(-1, 2)), weights=weights, vectorized=False) except FitError as e: if 'Unable to allocate' in str(e): - pytest.skip(msg='MemoryError - Matrix too large') + pytest.skip(reason='MemoryError - Matrix too large') else: raise e assert result.n_pars == len(m2.get_fit_parameters()) @@ -492,7 +492,7 @@ def test_fixed_parameter_does_not_change(fit_engine): try: f.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') result = f.fit(x=x, y=y, weights=weights) @@ -567,7 +567,7 @@ def run_fit(weights): try: f.switch_minimizer(fit_engine) except AttributeError: - pytest.skip(msg=f'{fit_engine} is not installed') + pytest.skip(reason=f'{fit_engine} is not installed') f.fit(x=x, y=y, weights=weights) return model.offset.value, model.phase.value From 916b1d3622ad06c0259ecfda2803bd17d8ad899c Mon Sep 17 00:00:00 2001 From: rozyczko Date: Mon, 18 May 2026 11:45:41 +0200 Subject: [PATCH 09/15] PR fixes --- .../fitting/minimizers/minimizer_bumps.py | 71 ++++++++++++------- src/easyscience/fitting/multi_fitter.py | 25 ++----- .../minimizers/test_minimizer_bumps.py | 55 ++++++++++++++ 3 files changed, 109 insertions(+), 42 deletions(-) diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 03c2fc9e..bdc1e7cb 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -262,6 +262,43 @@ def _resolve_fitclass(method: str): return fitclass raise FitError(f'Unknown BUMPS fitting method: {method}') + @staticmethod + def _resolve_population_alias(chains: int | None, population: int | None) -> int | None: + """Resolve the DREAM population count from the ``chains`` alias. + + Both ``chains`` (user-friendly name) and ``population`` (BUMPS + native name) refer to the same DREAM ``pop`` parameter. This + helper enforces that at most one is provided and returns the + resolved value. + + Parameters + ---------- + chains : int | None + User-friendly alias for the DREAM population count. + population : int | None + BUMPS-native DREAM population count. + + Returns + ------- + int | None + The resolved population count, or ``None`` if neither was + provided. + + Raises + ------ + ValueError + If both ``chains`` and ``population`` are provided with + different values. + """ + if chains is not None and population is not None: + if chains != population: + raise ValueError( + f'Conflicting population arguments: chains={chains}, ' + f'population={population}. Only provide one.' + ) + return chains + return chains if chains is not None else population + def _build_progress_payload( self, problem, iteration: int, point: np.ndarray, nllf: float ) -> dict: @@ -421,7 +458,12 @@ def sample( population : int | None, default=None BUMPS DREAM population count for advanced users. seed : int | None, default=None - Best-effort random seed. + Best-effort random seed. Calls ``numpy.random.seed(seed)`` + before DREAM starts, which affects the *global* NumPy RNG + state and may interact with other code in the process. + BUMPS DREAM uses additional internal RNG state that is + **not** controlled by this seed, so exact reproducibility + across runs is **not** guaranteed. sampler_kwargs : dict | None, default=None Additional keyword arguments forwarded to `bumps.fitters.fit`. progress_callback : Callable[[dict], bool | None] | None, default=None @@ -478,19 +520,7 @@ def sample( np.random.seed(seed) # Resolve population parameter - if chains is not None and population is not None: - if chains != population: - raise ValueError( - f'Conflicting population arguments: chains={chains}, population={population}. ' - 'Only provide one.' - ) - pop = chains - elif chains is not None: - pop = chains - elif population is not None: - pop = population - else: - pop = None + pop = self._resolve_population_alias(chains, population) # Build DREAM kwargs dream_kwargs: dict = {'samples': samples, 'burn': burn, 'thin': thin} @@ -563,16 +593,9 @@ def _build_sample_progress_payload( The payload includes ``sampling: True`` so downstream consumers can distinguish sampling progress from classical fitting progress. """ - parameter_values = self._current_parameter_snapshot(problem, point) - return { - 'iteration': iteration, - 'chi2': float(problem.chisq(nllf=nllf, norm=False)), - 'reduced_chi2': float(problem.chisq(nllf=nllf, norm=True)), - 'parameter_values': parameter_values, - 'refresh_plots': False, - 'finished': False, - 'sampling': True, - } + payload = self._build_progress_payload(problem, iteration, point, nllf) + payload['sampling'] = True + return payload def _set_parameter_fit_result( self, diff --git a/src/easyscience/fitting/multi_fitter.py b/src/easyscience/fitting/multi_fitter.py index f40411d7..a7731972 100644 --- a/src/easyscience/fitting/multi_fitter.py +++ b/src/easyscience/fitting/multi_fitter.py @@ -196,7 +196,7 @@ def sample( y: List[np.ndarray], weights: List[np.ndarray], samples: int = 10000, - burn: int = 1000, + burn: int = 2000, thin: int = 10, chains: int | None = None, population: int | None = None, @@ -221,7 +221,7 @@ def sample( List of weight arrays (one per dataset). samples : int, default=10000 Number of retained DREAM samples requested from BUMPS. - burn : int, default=1000 + burn : int, default=2000 Burn-in steps. thin : int, default=10 Thinning interval. @@ -258,24 +258,13 @@ def sample( ------ RuntimeError If the current minimizer is not a BUMPS instance. - ValueError - If both ``chains`` and ``population`` are provided with different - values. """ # --- Alias resolution --- - if chains is not None and population is not None: - if chains != population: - raise ValueError( - f'Conflicting population arguments: chains={chains}, population={population}. ' - 'Only provide one.' - ) - pop = chains - elif chains is not None: - pop = chains - elif population is not None: - pop = population - else: - pop = None + # Delegate to the BUMPS minimizer's static helper so the logic + # stays in one place. + from easyscience.fitting.minimizers.minimizer_bumps import Bumps + + pop = Bumps._resolve_population_alias(chains, population) # Flatten multi-dataset arrays x_fit, x_new, y_new, w_new, _dims = self._precompute_reshaping( diff --git a/tests/unit/fitting/minimizers/test_minimizer_bumps.py b/tests/unit/fitting/minimizers/test_minimizer_bumps.py index 93ee68b6..28a55fe7 100644 --- a/tests/unit/fitting/minimizers/test_minimizer_bumps.py +++ b/tests/unit/fitting/minimizers/test_minimizer_bumps.py @@ -848,6 +848,38 @@ def test_sample_rejects_non_callable_callback(self, minimizer: Bumps, monkeypatc ) +# =================================================================== +# _resolve_population_alias (static helper) +# =================================================================== + + +class TestResolvePopulationAlias: + """Tests for ``Bumps._resolve_population_alias``.""" + + def test_both_none_returns_none(self) -> None: + assert Bumps._resolve_population_alias(None, None) is None + + def test_chains_only_returns_chains(self) -> None: + assert Bumps._resolve_population_alias(5, None) == 5 + + def test_population_only_returns_population(self) -> None: + assert Bumps._resolve_population_alias(None, 7) == 7 + + def test_both_equal_returns_value(self) -> None: + assert Bumps._resolve_population_alias(5, 5) == 5 + + def test_both_different_raises(self) -> None: + with pytest.raises(ValueError, match='Conflicting population'): + Bumps._resolve_population_alias(3, 10) + + def test_chains_zero_is_valid(self) -> None: + """Zero is a valid (though unusual) population value.""" + assert Bumps._resolve_population_alias(0, None) == 0 + + def test_population_zero_is_valid(self) -> None: + assert Bumps._resolve_population_alias(None, 0) == 0 + + # =================================================================== # _build_sample_progress_payload # =================================================================== @@ -902,6 +934,29 @@ def test_payload_keys(self, minimizer: Bumps) -> None: } assert set(payload.keys()) == expected_keys + def test_delegates_to_build_progress_payload(self, minimizer: Bumps) -> None: + """_build_sample_progress_payload calls _build_progress_payload and adds sampling.""" + mock_problem = MagicMock() + + # Patch _build_progress_payload to track calls + base_payload = { + 'iteration': 3, + 'chi2': 42.0, + 'reduced_chi2': 21.0, + 'parameter_values': {'x': 7.0}, + 'refresh_plots': False, + 'finished': False, + } + with patch.object( + minimizer, '_build_progress_payload', return_value=base_payload + ) as mock_bpp: + result = minimizer._build_sample_progress_payload( + mock_problem, 3, np.array([7.0]), 21.0 + ) + + mock_bpp.assert_called_once_with(mock_problem, 3, np.array([7.0]), 21.0) + assert result == {**base_payload, 'sampling': True} + # =================================================================== # _set_parameter_fit_result with stack_status=True From d6adf067594eba98d7c6d0e3afe5d755380bd63d Mon Sep 17 00:00:00 2001 From: rozyczko Date: Tue, 19 May 2026 14:06:32 +0200 Subject: [PATCH 10/15] added multiprocessing to Bayesian sampling --- pyproject.toml | 13 +- .../fitting/minimizers/minimizer_bumps.py | 235 +++++++++++++++++- src/easyscience/fitting/multi_fitter.py | 7 + .../integration/fitting/test_multi_fitter.py | 124 +++++++++ tools/benchmarks/sampling_mpi.py | 119 +++++++++ 5 files changed, 491 insertions(+), 7 deletions(-) create mode 100644 tools/benchmarks/sampling_mpi.py diff --git a/pyproject.toml b/pyproject.toml index e2c8c24c..1dba9e62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,12 +24,13 @@ classifiers = [ ] requires-python = '>=3.11' dependencies = [ - 'asteval', # Safely evaluate Python expressions from strings - 'lmfit', # Non-linear least squares fitting - 'bumps', # Bayesian uncertainty estimation - 'dfo-ls', # Derivative-free optimization - 'numpy', # Numerical computing - 'scipp', # Handling and analysis of scientific data + 'asteval', # Safely evaluate Python expressions from strings + 'lmfit', # Non-linear least squares fitting + 'bumps', # Bayesian uncertainty estimation + 'cloudpickle', # Serialize fitting closures for multiprocessing + 'dfo-ls', # Derivative-free optimization + 'numpy', # Numerical computing + 'scipp', # Handling and analysis of scientific data ] [project.optional-dependencies] diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index bdc1e7cb..640d6099 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -2,7 +2,11 @@ # SPDX-License-Identifier: BSD-3-Clause import copy +import copyreg +import multiprocessing as mp +import pickle import warnings +import weakref from typing import Any from typing import Callable from typing import List @@ -32,6 +36,210 @@ FIT_AVAILABLE_IDS_FILTERED.remove('pt') +_WORKER_PROBLEM = None + +_SCIPP_VARIABLE_KEY = '__easyscience_scipp_variable__' + + +def _serialize_worker_value(value: Any) -> Any: + try: + import scipp as sc + except ImportError: + sc = None + + if sc is not None and isinstance(value, sc.Variable): + return { + _SCIPP_VARIABLE_KEY: True, + 'value': value.value, + 'variance': value.variance, + 'unit': str(value.unit), + } + if isinstance(value, (weakref.ReferenceType, weakref.KeyedRef)): + return None + if isinstance(value, dict): + return {key: _serialize_worker_value(item) for key, item in value.items()} + if isinstance(value, list): + return [_serialize_worker_value(item) for item in value] + if isinstance(value, tuple): + return tuple(_serialize_worker_value(item) for item in value) + if isinstance(value, set): + return {_serialize_worker_value(item) for item in value} + return value + + +def _deserialize_worker_value(value: Any) -> Any: + if isinstance(value, dict): + if value.get(_SCIPP_VARIABLE_KEY): + import scipp as sc + + return sc.scalar( + value['value'], + unit=value['unit'], + variance=value['variance'], + ) + return {key: _deserialize_worker_value(item) for key, item in value.items()} + if isinstance(value, list): + return [_deserialize_worker_value(item) for item in value] + if isinstance(value, tuple): + return tuple(_deserialize_worker_value(item) for item in value) + if isinstance(value, set): + return {_deserialize_worker_value(item) for item in value} + return value + + +def _collect_object_state(obj: object) -> dict: + state = {} + if hasattr(obj, '__dict__'): + state['__dict__'] = { + key: _serialize_worker_value(value) + for key, value in obj.__dict__.items() + if key != '__old_class__' + } + + slots = {} + for cls in type(obj).mro(): + cls_slots = getattr(cls, '__slots__', ()) + if isinstance(cls_slots, str): + cls_slots = (cls_slots,) + for slot in cls_slots: + if slot in ('__dict__', '__weakref__', '_global_object'): + continue + if hasattr(obj, slot): + slots[slot] = _serialize_worker_value(getattr(obj, slot)) + state['__slots__'] = slots + return state + + +def _restore_object_state(cls: type, state: dict) -> object: + obj = cls.__new__(cls) + if hasattr(obj, '__dict__'): + obj.__dict__.update(_deserialize_worker_value(state.get('__dict__', {}))) + for slot, value in _deserialize_worker_value(state.get('__slots__', {})).items(): + object.__setattr__(obj, slot, value) + + for key, value in getattr(obj, '_kwargs', {}).items(): + object.__setattr__(obj, key, value) + + from easyscience import global_object + + if hasattr(obj, '_global_object') or any( + '_global_object' in getattr(base, '__slots__', ()) for base in cls.mro() + ): + object.__setattr__(obj, '_global_object', global_object) + return obj + + +def _reduce_object_state(obj: object) -> tuple: + cls = getattr(obj, '__old_class__', obj.__class__) + return _restore_object_state, (cls, _collect_object_state(obj)) + + +def _restore_none() -> None: + return None + + +def _reduce_weakref(obj: weakref.ReferenceType) -> tuple: + return _restore_none, () + + +def _problem_serializer(): + try: + import cloudpickle + except ImportError: + return pickle + return cloudpickle + + +def _init_bumps_worker(problem_bytes: bytes) -> None: + global _WORKER_PROBLEM + _WORKER_PROBLEM = pickle.loads(problem_bytes) + + from easyscience import global_object + + global_object.stack.enabled = False + + +def _evaluate_bumps_point(point: np.ndarray) -> float: + if _WORKER_PROBLEM is None: + raise RuntimeError('BUMPS worker problem was not initialized') + return float(_WORKER_PROBLEM.nllf(point)) + + +class BumpsPoolMapper: + """Multiprocessing mapper for BUMPS DREAM population evaluation.""" + + def __init__(self, problem: FitProblem, n_workers: int): + self._pool = None + self.n_workers = n_workers + serializer = _problem_serializer() + try: + from easyscience.base_classes.based_base import BasedBase + from easyscience.variable.descriptor_base import DescriptorBase + + original_reduce = BasedBase.__reduce__ + original_descriptor_reduce = getattr(DescriptorBase, '__reduce__', None) + original_weakref_reduce = copyreg.dispatch_table.get(weakref.ReferenceType) + original_keyedref_reduce = copyreg.dispatch_table.get(weakref.KeyedRef) + BasedBase.__reduce__ = _reduce_object_state + DescriptorBase.__reduce__ = _reduce_object_state + copyreg.pickle(weakref.ReferenceType, _reduce_weakref) + copyreg.pickle(weakref.KeyedRef, _reduce_weakref) + try: + problem_bytes = serializer.dumps(problem) + finally: + BasedBase.__reduce__ = original_reduce + if original_descriptor_reduce is None: + delattr(DescriptorBase, '__reduce__') + else: + DescriptorBase.__reduce__ = original_descriptor_reduce + if original_weakref_reduce is None: + copyreg.dispatch_table.pop(weakref.ReferenceType, None) + else: + copyreg.pickle(weakref.ReferenceType, original_weakref_reduce) + if original_keyedref_reduce is None: + copyreg.dispatch_table.pop(weakref.KeyedRef, None) + else: + copyreg.pickle(weakref.KeyedRef, original_keyedref_reduce) + except Exception as exc: + raise FitError( + 'BUMPS multiprocessing requires the FitProblem and fit function to be ' + 'serializable. Install cloudpickle for closure support, or use ' + 'n_workers=1 for sequential sampling.' + ) from exc + + context = mp.get_context('spawn') + self._pool = context.Pool( + processes=n_workers, + initializer=_init_bumps_worker, + initargs=(problem_bytes,), + ) + + def __call__(self, population: np.ndarray) -> list[float]: + # BUMPS may pass either a single point (1D) or a population (2D). + # Always reshape to 2D so list() produces one element per chain member. + pop = np.atleast_2d(np.asarray(population)) + n_points = pop.shape[0] + results = self._pool.map(_evaluate_bumps_point, list(pop), chunksize=1) + + # Safety check: BUMPS DREAM state corruption can occur if the + # mapper returns a different number of values than expected. + if len(results) != n_points: + raise RuntimeError( + f'Mapper returned {len(results)} results for {n_points} population points' + ) + return results + + def close(self) -> None: + self.terminate() + + def terminate(self) -> None: + if self._pool is None: + return + self._pool.terminate() + self._pool.join() + self._pool = None + + class Bumps(MinimizerBase): """ This is a wrapper to Bumps: https://bumps.readthedocs.io/ It allows @@ -431,6 +639,7 @@ def sample( sampler_kwargs: dict | None = None, progress_callback: Callable[[dict], bool | None] | None = None, abort_test: Callable[[], bool] | None = None, + n_workers: int | None = None, ) -> dict: """Run Bayesian MCMC sampling using the BUMPS DREAM sampler. @@ -474,6 +683,11 @@ def sample( Optional callback that returns ``True`` to signal that sampling should be aborted. Called periodically during the DREAM sampling loop. + n_workers : int | None, default=None + Number of worker processes used to evaluate the DREAM population. + Values of ``None`` and ``1`` use BUMPS' sequential mapper. Values + greater than ``1`` require the BUMPS problem and fit function to be + pickleable. Returns ------- @@ -488,7 +702,9 @@ def sample( and ``population`` are provided with different values, or if ``progress_callback`` is not callable. FitError - If DREAM sampling was aborted by the user (via ``abort_test``). + If DREAM sampling was aborted by the user (via ``abort_test``), or + if multiprocessing was requested for a problem that cannot be + serialized for worker processes. Exception Re-raised from DREAM fitting if any unexpected error occurs (parameter values are restored beforehand). @@ -529,6 +745,17 @@ def sample( if sampler_kwargs: dream_kwargs.update(sampler_kwargs) + resolved_pop = int(dream_kwargs.get('pop', 10)) + if resolved_pop <= 0: + raise ValueError('DREAM population must be a positive integer.') + + mapper = None + if n_workers is not None: + if n_workers < 1: + raise ValueError('n_workers must be at least 1.') + if n_workers > 1: + mapper = BumpsPoolMapper(problem, n_workers=min(n_workers, resolved_pop)) + # Build monitors (same pattern as classical Bumps.fit()) monitors = [] if progress_callback is not None: @@ -553,6 +780,7 @@ def sample( problem=problem, monitors=monitors, abort_test=abort_test or (lambda: False), + mapper=mapper, **dream_kwargs, ) driver.clip() @@ -568,9 +796,14 @@ def sample( if result_state is None: raise FitError('Sampling aborted by user') except Exception: + if mapper is not None: + mapper.terminate() + mapper = None self._restore_parameter_values() raise finally: + if mapper is not None: + mapper.close() global_object.stack.enabled = stack_status draws = result_state.draw().points diff --git a/src/easyscience/fitting/multi_fitter.py b/src/easyscience/fitting/multi_fitter.py index a7731972..963b2b63 100644 --- a/src/easyscience/fitting/multi_fitter.py +++ b/src/easyscience/fitting/multi_fitter.py @@ -203,6 +203,7 @@ def sample( seed: int | None = None, vectorized: bool = False, sampler_kwargs: dict | None = None, + n_workers: int | None = None, progress_callback: Callable[[dict], bool | None] | None = None, abort_test: Callable[[], bool] | None = None, ) -> Dict: @@ -239,6 +240,11 @@ def sample( sampler_kwargs : dict | None, default=None Additional keyword arguments forwarded to the BUMPS DREAM sampler via `bumps.fitters.fit`. + n_workers : int | None, default=None + Number of worker processes used to evaluate the DREAM population. + Values of ``None`` and ``1`` use BUMPS' sequential mapper. Values + greater than ``1`` require the BUMPS problem and fit function to be + pickleable. progress_callback : Callable[[dict], bool | None] | None, default=None Optional callback for progress updates during sampling. The payload dict includes ``iteration`` (DREAM generation number) and @@ -301,6 +307,7 @@ def sample( population=pop, seed=seed, sampler_kwargs=sampler_kwargs, + n_workers=n_workers, progress_callback=progress_callback, abort_test=abort_test, ) diff --git a/tests/integration/fitting/test_multi_fitter.py b/tests/integration/fitting/test_multi_fitter.py index 4ceb3739..6b3e2cec 100644 --- a/tests/integration/fitting/test_multi_fitter.py +++ b/tests/integration/fitting/test_multi_fitter.py @@ -1,6 +1,11 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +import subprocess +import sys +import textwrap +from pathlib import Path + import numpy as np import pytest @@ -586,3 +591,122 @@ def test_sampler_kwargs_forwarded(self): assert result['draws'].ndim == 2 assert result['draws'].shape[0] > 0 + + +class TestSampleMultiprocessing: + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_n_workers_one_uses_sequential_mapper(self): + """n_workers=1 should behave like the default sequential DREAM mapper.""" + ref_sin = AbsSin(0.2, np.pi) + sp = AbsSin(0.354, 3.05) + + x = np.linspace(0, 5, 50) + y = ref_sin(x) + weights = np.ones_like(x) + + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + result = f.sample( + x=[x], y=[y], weights=[weights], samples=50, burn=10, thin=2, n_workers=2 + ) + + assert result['draws'].ndim == 2 + assert result['draws'].shape[0] > 0 + assert result['draws'].shape[1] == len(result['param_names']) + + def test_n_workers_must_be_positive(self): + """n_workers must be positive when explicitly provided.""" + sp = AbsSin(0.354, 3.05) + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + try: + f.switch_minimizer('Bumps') + except AttributeError: + pytest.skip('BUMPS is not installed') + + x = np.linspace(0, 5, 50) + y = np.sin(x) + weights = np.ones_like(x) + + with pytest.raises(ValueError, match='n_workers must be at least 1'): + f.sample(x=[x], y=[y], weights=[weights], samples=10, burn=5, thin=1, n_workers=0) + + @pytest.mark.filterwarnings('ignore::UserWarning') + def test_n_workers_two_produces_valid_draws(self, tmp_path): + """n_workers>1 should evaluate DREAM populations through process workers.""" + repo_root = str(Path(__file__).resolve().parents[3]) + script = tmp_path / 'run_bumps_multiprocessing_sample.py' + script.write_text( + textwrap.dedent( + f""" + import multiprocessing as mp + import sys + + sys.path.insert(0, {repo_root!r}) + sys.path.insert(0, {repo_root + '/src'!r}) + + import numpy as np + + from tests.integration.fitting.test_multi_fitter import AbsSin + from easyscience.fitting.multi_fitter import MultiFitter + + + def main(): + ref_sin = AbsSin(0.2, np.pi) + sp = AbsSin(0.354, 3.05) + + x = np.linspace(0, 5, 40) + y = ref_sin(x) + weights = np.ones_like(x) + + sp.offset.fixed = False + sp.phase.fixed = False + + f = MultiFitter([sp], [sp]) + f.switch_minimizer('Bumps') + result = f.sample( + x=[x], + y=[y], + weights=[weights], + samples=50, + burn=10, + thin=2, + population=5, + n_workers=2, + ) + + assert result['draws'].ndim == 2 + assert result['draws'].shape[0] > 0 + assert result['draws'].shape[1] == len(result['param_names']) + + + if __name__ == '__main__': + mp.freeze_support() + main() + """ + ), + encoding='utf-8', + ) + + try: + completed = subprocess.run( + [sys.executable, str(script)], + cwd=repo_root, + capture_output=True, + text=True, + timeout=60, + check=False, + ) + except subprocess.TimeoutExpired: + pytest.fail('n_workers=2 sampling subprocess timed out after 60 seconds') + + assert completed.returncode == 0, completed.stdout + completed.stderr diff --git a/tools/benchmarks/sampling_mpi.py b/tools/benchmarks/sampling_mpi.py new file mode 100644 index 00000000..ccfe284e --- /dev/null +++ b/tools/benchmarks/sampling_mpi.py @@ -0,0 +1,119 @@ +# SPDX-FileCopyrightText: 2026 EasyScience contributors +# SPDX-License-Identifier: BSD-3-Clause +"""Quick benchmark: Bayesian DREAM sampling with multiprocessing. + +Runs the same small sampling problem sequentially and with process workers, +printing wall-clock times so you can judge the speedup. +""" + +import time +import warnings + +import numpy as np + +from easyscience import ObjBase +from easyscience import Parameter +from easyscience.fitting.multi_fitter import MultiFitter + +# -- simple test model -------------------------------------------------------- + +# Simulate an expensive model by adding a configurable CPU burn per evaluation. +# Set to 0.0 for the trivial model; try 0.02–0.1 to see multiprocessing speedup. +_MODEL_DELAY = 0.09 # seconds of CPU work per model call + + +class Line(ObjBase): + m: Parameter + c: Parameter + + def __init__(self, m_val: float, c_val: float): + super().__init__( + 'line', + m=Parameter('m', m_val), + c=Parameter('c', c_val), + ) + + def __call__(self, x: np.ndarray) -> np.ndarray: + if _MODEL_DELAY > 0: + # burn CPU to simulate a real physics model + t0 = time.perf_counter() + while time.perf_counter() - t0 < _MODEL_DELAY: + _ = np.sum(np.sin(x) ** 2 + np.cos(x) ** 2) + return self.m.value * x + self.c.value + +# -- helpers ------------------------------------------------------------------ + +def run_sample(n_workers: int | None, **sample_kwargs) -> tuple[dict, float]: + """Run one DREAM sampling call and return (result_dict, wall_seconds).""" + x = np.linspace(0, 10, 60) + y_true = 2.5 * x + 1.3 + rng = np.random.default_rng(42) + y = y_true + rng.normal(0, 0.3, size=x.shape) + weights = np.full_like(x, 1.0 / 0.3) + + model = Line(2.0, 1.0) + model.m.fixed = False + model.c.fixed = False + + fitter = MultiFitter([model], [model]) + fitter.switch_minimizer('Bumps') + + t0 = time.perf_counter() + result = fitter.sample( + x=[x], + y=[y], + weights=[weights], + n_workers=n_workers, + **sample_kwargs, + ) + elapsed = time.perf_counter() - t0 + return result, elapsed + +def summarise(label: str, result: dict, elapsed: float) -> None: + draws = result['draws'] + print(f' {label:>12s} {elapsed:6.2f} s ' + f'draws shape {draws.shape} ' + f'params: {result["param_names"]}') + +# -- main --------------------------------------------------------------------- + +def main() -> None: + sample_kwargs = dict(samples=200, burn=50, thin=2, population=5, seed=123) + + print('Bayesian multiprocessing quick test') + print('-----------------------------------') + print(f' config: {sample_kwargs}') + print() + + # 1. sequential (default) + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + res_seq, t_seq = run_sample(n_workers=None, **sample_kwargs) + summarise('sequential', res_seq, t_seq) + + # 2. n_workers=1 (same as sequential, but explicit) + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + res_w1, t_w1 = run_sample(n_workers=1, **sample_kwargs) + summarise('n_workers=1', res_w1, t_w1) + + # 3. n_workers=2 (actual multiprocessing) + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + res_w2, t_w2 = run_sample(n_workers=2, **sample_kwargs) + summarise('n_workers=2', res_w2, t_w2) + + # 4. n_workers=4 + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + res_w4, t_w4 = run_sample(n_workers=4, **sample_kwargs) + summarise('n_workers=4', res_w4, t_w4) + + print() + for label, t_val in [('n_workers=2', t_w2), ('n_workers=4', t_w4)]: + ratio = t_seq / t_val + tag = f'{ratio:.1f}× speedup' if ratio > 1 else f'{1/ratio:.1f}× slower' + print(f' {label:>12s} {tag} (seq {t_seq:.2f}s → {t_val:.2f}s)') + +if __name__ == '__main__': + main() From 79caafb13958c83e445c2a9f98a29f5792585edb Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Thu, 21 May 2026 18:52:55 +0200 Subject: [PATCH 11/15] PR review adderssed --- pixi.lock | 1 + .../fitting/minimizers/minimizer_bumps.py | 66 ++-- .../integration/fitting/test_multi_fitter.py | 2 +- .../minimizers/test_minimizer_bumps.py | 354 ++++++++++++++++++ 4 files changed, 385 insertions(+), 38 deletions(-) diff --git a/pixi.lock b/pixi.lock index 144dca8c..d6ab5106 100644 --- a/pixi.lock +++ b/pixi.lock @@ -6493,6 +6493,7 @@ packages: requires_dist: - asteval - bumps + - cloudpickle - dfo-ls - lmfit - numpy diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 640d6099..da9f727b 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause import copy -import copyreg +import io import multiprocessing as mp import pickle import warnings @@ -142,12 +142,29 @@ def _reduce_weakref(obj: weakref.ReferenceType) -> tuple: return _restore_none, () -def _problem_serializer(): - try: - import cloudpickle - except ImportError: - return pickle - return cloudpickle +def _problem_pickler_class(): + """Build a Pickler subclass that handles BUMPS problem reduction locally. + + Uses ``reducer_override`` (instance-scoped) instead of mutating + ``__reduce__`` on shared classes or ``copyreg.dispatch_table`` — those + globals would race with any concurrent pickle on another thread. + """ + from cloudpickle import CloudPickler + + from easyscience.base_classes.based_base import BasedBase + from easyscience.variable.descriptor_base import DescriptorBase + + _parent_reducer = CloudPickler.reducer_override + + class _BumpsProblemPickler(CloudPickler): + def reducer_override(self, obj): + if isinstance(obj, (weakref.ReferenceType, weakref.KeyedRef)): + return _restore_none, () + if isinstance(obj, (BasedBase, DescriptorBase)): + return _reduce_object_state(obj) + return _parent_reducer(self, obj) + + return _BumpsProblemPickler def _init_bumps_worker(problem_bytes: bytes) -> None: @@ -171,40 +188,15 @@ class BumpsPoolMapper: def __init__(self, problem: FitProblem, n_workers: int): self._pool = None self.n_workers = n_workers - serializer = _problem_serializer() try: - from easyscience.base_classes.based_base import BasedBase - from easyscience.variable.descriptor_base import DescriptorBase - - original_reduce = BasedBase.__reduce__ - original_descriptor_reduce = getattr(DescriptorBase, '__reduce__', None) - original_weakref_reduce = copyreg.dispatch_table.get(weakref.ReferenceType) - original_keyedref_reduce = copyreg.dispatch_table.get(weakref.KeyedRef) - BasedBase.__reduce__ = _reduce_object_state - DescriptorBase.__reduce__ = _reduce_object_state - copyreg.pickle(weakref.ReferenceType, _reduce_weakref) - copyreg.pickle(weakref.KeyedRef, _reduce_weakref) - try: - problem_bytes = serializer.dumps(problem) - finally: - BasedBase.__reduce__ = original_reduce - if original_descriptor_reduce is None: - delattr(DescriptorBase, '__reduce__') - else: - DescriptorBase.__reduce__ = original_descriptor_reduce - if original_weakref_reduce is None: - copyreg.dispatch_table.pop(weakref.ReferenceType, None) - else: - copyreg.pickle(weakref.ReferenceType, original_weakref_reduce) - if original_keyedref_reduce is None: - copyreg.dispatch_table.pop(weakref.KeyedRef, None) - else: - copyreg.pickle(weakref.KeyedRef, original_keyedref_reduce) + pickler_cls = _problem_pickler_class() + buffer = io.BytesIO() + pickler_cls(buffer).dump(problem) + problem_bytes = buffer.getvalue() except Exception as exc: raise FitError( 'BUMPS multiprocessing requires the FitProblem and fit function to be ' - 'serializable. Install cloudpickle for closure support, or use ' - 'n_workers=1 for sequential sampling.' + 'serializable. Use n_workers=1 for sequential sampling.' ) from exc context = mp.get_context('spawn') diff --git a/tests/integration/fitting/test_multi_fitter.py b/tests/integration/fitting/test_multi_fitter.py index 6b3e2cec..3df69e5e 100644 --- a/tests/integration/fitting/test_multi_fitter.py +++ b/tests/integration/fitting/test_multi_fitter.py @@ -614,7 +614,7 @@ def test_n_workers_one_uses_sequential_mapper(self): pytest.skip('BUMPS is not installed') result = f.sample( - x=[x], y=[y], weights=[weights], samples=50, burn=10, thin=2, n_workers=2 + x=[x], y=[y], weights=[weights], samples=50, burn=10, thin=2, n_workers=1 ) assert result['draws'].ndim == 2 diff --git a/tests/unit/fitting/minimizers/test_minimizer_bumps.py b/tests/unit/fitting/minimizers/test_minimizer_bumps.py index 28a55fe7..76d1cdf6 100644 --- a/tests/unit/fitting/minimizers/test_minimizer_bumps.py +++ b/tests/unit/fitting/minimizers/test_minimizer_bumps.py @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +import io +import pickle +import weakref from unittest.mock import ANY from unittest.mock import MagicMock from unittest.mock import patch @@ -9,8 +12,14 @@ import pytest import easyscience.fitting.minimizers.minimizer_bumps +import easyscience.fitting.minimizers.minimizer_bumps as _bumps_mod from easyscience.fitting.minimizers.bumps_utils import BumpsProgressMonitor from easyscience.fitting.minimizers.minimizer_bumps import Bumps +from easyscience.fitting.minimizers.minimizer_bumps import BumpsPoolMapper +from easyscience.fitting.minimizers.minimizer_bumps import _evaluate_bumps_point +from easyscience.fitting.minimizers.minimizer_bumps import _init_bumps_worker +from easyscience.fitting.minimizers.minimizer_bumps import _problem_pickler_class +from easyscience.fitting.minimizers.minimizer_bumps import _restore_none from easyscience.fitting.minimizers.utils import FitError @@ -1086,3 +1095,348 @@ def test_abort_test_passed_to_fit_driver(self, minimizer: Bumps, monkeypatch) -> call_kwargs = mock_FitDriver.call_args.kwargs assert callable(call_kwargs['abort_test']) assert call_kwargs['abort_test'] is not (lambda: False) + + +# =================================================================== +# Worker helper functions: _evaluate_bumps_point, _init_bumps_worker +# =================================================================== + + +class TestWorkerFunctions: + def test_evaluate_raises_when_problem_not_initialized(self, monkeypatch): + monkeypatch.setattr(_bumps_mod, '_WORKER_PROBLEM', None) + with pytest.raises(RuntimeError, match='not initialized'): + _evaluate_bumps_point(np.array([1.0])) + + def test_evaluate_calls_nllf_and_returns_python_float(self, monkeypatch): + mock_problem = MagicMock() + mock_problem.nllf.return_value = np.float64(3.5) + monkeypatch.setattr(_bumps_mod, '_WORKER_PROBLEM', mock_problem) + + result = _evaluate_bumps_point(np.array([1.0, 2.0])) + + assert isinstance(result, float) + assert result == 3.5 + mock_problem.nllf.assert_called_once() + np.testing.assert_array_equal(mock_problem.nllf.call_args[0][0], np.array([1.0, 2.0])) + + def test_init_worker_populates_global_problem(self, monkeypatch): + monkeypatch.setattr(_bumps_mod, '_WORKER_PROBLEM', None) + _init_bumps_worker(pickle.dumps({'sentinel': True})) + assert _bumps_mod._WORKER_PROBLEM == {'sentinel': True} + + def test_init_worker_disables_global_stack(self, monkeypatch): + from easyscience import global_object + + monkeypatch.setattr(_bumps_mod, '_WORKER_PROBLEM', None) + global_object.stack.enabled = True + _init_bumps_worker(pickle.dumps(42)) + assert global_object.stack.enabled is False + + +# =================================================================== +# _problem_pickler_class +# =================================================================== + + +class TestProblemPicklerClass: + def test_returns_cloudpickler_subclass(self): + from cloudpickle import CloudPickler + + assert issubclass(_problem_pickler_class(), CloudPickler) + + def test_reducer_override_replaces_weakref_with_none_restorer(self): + cls = _problem_pickler_class() + + class _Dummy: + pass + + obj = _Dummy() + ref = weakref.ref(obj) + + result = cls(io.BytesIO()).reducer_override(ref) + assert result == (_restore_none, ()) + + def test_reducer_override_falls_through_for_plain_dict(self): + cls = _problem_pickler_class() + result = cls(io.BytesIO()).reducer_override({'key': 'val'}) + assert result is NotImplemented + + def test_weakref_survives_round_trip_as_none(self): + cls = _problem_pickler_class() + + class _Dummy: + pass + + obj = _Dummy() + + class _Container: + ref = weakref.ref(obj) + + buf = io.BytesIO() + cls(buf).dump(_Container()) + buf.seek(0) + restored = pickle.load(buf) + assert restored.ref is None + + +# =================================================================== +# BumpsPoolMapper — lifecycle (terminate / close) +# =================================================================== + + +class TestBumpsPoolMapperLifecycle: + def _mapper(self): + m = BumpsPoolMapper.__new__(BumpsPoolMapper) + m._pool = MagicMock() + m.n_workers = 2 + return m + + def test_terminate_shuts_down_pool(self): + mapper = self._mapper() + pool = mapper._pool + mapper.terminate() + pool.terminate.assert_called_once() + pool.join.assert_called_once() + assert mapper._pool is None + + def test_terminate_is_idempotent_when_pool_is_none(self): + mapper = BumpsPoolMapper.__new__(BumpsPoolMapper) + mapper._pool = None + mapper.terminate() # must not raise + + def test_close_delegates_to_terminate(self): + mapper = self._mapper() + pool = mapper._pool + mapper.close() + pool.terminate.assert_called_once() + assert mapper._pool is None + + +# =================================================================== +# BumpsPoolMapper — __call__ +# =================================================================== + + +class TestBumpsPoolMapperCall: + def _mapper(self, map_return): + m = BumpsPoolMapper.__new__(BumpsPoolMapper) + m._pool = MagicMock() + m._pool.map.return_value = map_return + m.n_workers = 2 + return m + + def test_maps_2d_population_and_passes_chunksize_one(self): + pop = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) + mapper = self._mapper([1.0, 2.0, 3.0]) + + result = mapper(pop) + + assert result == [1.0, 2.0, 3.0] + assert mapper._pool.map.call_args.kwargs.get('chunksize') == 1 + + def test_reshapes_1d_point_to_single_row(self): + mapper = self._mapper([7.0]) + + result = mapper(np.array([1.0, 2.0])) + + assert result == [7.0] + points_arg = mapper._pool.map.call_args[0][1] + assert len(points_arg) == 1 + # list(atleast_2d([1., 2.])) produces 1D rows, one per chain member + np.testing.assert_array_equal(points_arg[0], np.array([1.0, 2.0])) + + def test_raises_on_result_count_mismatch(self): + mapper = self._mapper([42.0]) # one result for two points + with pytest.raises( + RuntimeError, match='Mapper returned 1 results for 2 population points' + ): + mapper(np.array([[1.0], [2.0]])) + + +# =================================================================== +# BumpsPoolMapper — __init__ (serialization + pool creation) +# =================================================================== + + +class TestBumpsPoolMapperInit: + @staticmethod + def _patch_pickler(monkeypatch, written_bytes=b'fake_problem'): + class _FakePickler: + def __init__(self, buf): + self._buf = buf + + def dump(self, obj): + self._buf.write(written_bytes) + + monkeypatch.setattr( + easyscience.fitting.minimizers.minimizer_bumps, + '_problem_pickler_class', + lambda: _FakePickler, + ) + + def test_creates_spawn_pool_with_correct_args(self, monkeypatch): + self._patch_pickler(monkeypatch) + mock_pool = MagicMock() + mock_context = MagicMock() + mock_context.Pool.return_value = mock_pool + monkeypatch.setattr(_bumps_mod.mp, 'get_context', lambda _: mock_context) + + mapper = BumpsPoolMapper(MagicMock(), n_workers=3) + + assert mapper._pool is mock_pool + mock_context.Pool.assert_called_once_with( + processes=3, + initializer=_bumps_mod._init_bumps_worker, + initargs=(b'fake_problem',), + ) + + def test_serialization_failure_raises_fit_error(self, monkeypatch): + class _BadPickler: + def __init__(self, buf): + pass + + def dump(self, obj): + raise TypeError('not serializable') + + monkeypatch.setattr( + easyscience.fitting.minimizers.minimizer_bumps, + '_problem_pickler_class', + lambda: _BadPickler, + ) + with pytest.raises(FitError, match='serializable'): + BumpsPoolMapper(MagicMock(), n_workers=2) + + +# =================================================================== +# Bumps.sample() — n_workers wiring +# =================================================================== + + +class TestBumpsSampleNWorkers: + @pytest.fixture + def minimizer(self) -> Bumps: + return Bumps( + obj='obj', + fit_function='fit_function', + minimizer_enum=MagicMock(package='bumps', method='amoeba'), + ) + + @pytest.fixture(autouse=True) + def _mock_bumps_internals(self, monkeypatch): + import bumps.fitters + import bumps.names + + monkeypatch.setattr(bumps.fitters, 'DreamFit', MagicMock()) + monkeypatch.setattr(bumps.names, 'FitProblem', MagicMock(return_value=MagicMock())) + monkeypatch.setattr( + Bumps, '_make_model', MagicMock(return_value=MagicMock(return_value=MagicMock())) + ) + + def _setup_driver(self, monkeypatch): + from easyscience import global_object + + global_object.stack.enabled = False + + mock_state = MagicMock() + mock_state.draw.return_value.points = np.array([[1.0]]) + mock_state.logp = None + mock_driver = MagicMock() + mock_driver.clip = MagicMock() + mock_driver.fit.return_value = (np.array([1.0]), 0.0) + mock_driver.fitter.state = mock_state + + mock_FitDriver = MagicMock(return_value=mock_driver) + monkeypatch.setattr( + easyscience.fitting.minimizers.minimizer_bumps, 'FitDriver', mock_FitDriver + ) + return mock_FitDriver, mock_driver + + def _patch_mapper(self, monkeypatch): + mock_mapper = MagicMock() + mock_cls = MagicMock(return_value=mock_mapper) + monkeypatch.setattr( + easyscience.fitting.minimizers.minimizer_bumps, 'BumpsPoolMapper', mock_cls + ) + return mock_cls, mock_mapper + + def test_n_workers_zero_raises(self, minimizer, monkeypatch): + self._setup_driver(monkeypatch) + with pytest.raises(ValueError, match='n_workers must be at least 1'): + minimizer.sample( + x=np.array([1.0]), y=np.array([0.1]), weights=np.array([1.0]), n_workers=0 + ) + + def test_n_workers_one_does_not_create_mapper(self, minimizer, monkeypatch): + mock_FitDriver, _ = self._setup_driver(monkeypatch) + mock_cls, _ = self._patch_mapper(monkeypatch) + + minimizer.sample( + x=np.array([1.0]), y=np.array([0.1]), weights=np.array([1.0]), n_workers=1 + ) + + mock_cls.assert_not_called() + assert mock_FitDriver.call_args.kwargs['mapper'] is None + + def test_n_workers_two_creates_mapper_and_passes_to_driver(self, minimizer, monkeypatch): + mock_FitDriver, _ = self._setup_driver(monkeypatch) + mock_cls, mock_mapper = self._patch_mapper(monkeypatch) + + minimizer.sample( + x=np.array([1.0]), + y=np.array([0.1]), + weights=np.array([1.0]), + n_workers=2, + population=5, + ) + + mock_cls.assert_called_once() + assert mock_FitDriver.call_args.kwargs['mapper'] is mock_mapper + mock_mapper.close.assert_called_once() + + def test_n_workers_clipped_to_population_size(self, minimizer, monkeypatch): + self._setup_driver(monkeypatch) + mock_cls, _ = self._patch_mapper(monkeypatch) + + minimizer.sample( + x=np.array([1.0]), + y=np.array([0.1]), + weights=np.array([1.0]), + n_workers=8, + population=3, + ) + + assert mock_cls.call_args.kwargs['n_workers'] == 3 + + def test_mapper_terminated_on_driver_exception(self, minimizer, monkeypatch): + from easyscience import global_object + + global_object.stack.enabled = False + mock_driver = MagicMock() + mock_driver.clip = MagicMock() + mock_driver.fit.side_effect = RuntimeError('driver failed') + monkeypatch.setattr( + easyscience.fitting.minimizers.minimizer_bumps, + 'FitDriver', + MagicMock(return_value=mock_driver), + ) + mock_cls, mock_mapper = self._patch_mapper(monkeypatch) + + with pytest.raises(RuntimeError, match='driver failed'): + minimizer.sample( + x=np.array([1.0]), y=np.array([0.1]), weights=np.array([1.0]), n_workers=2 + ) + + mock_mapper.terminate.assert_called_once() + mock_mapper.close.assert_not_called() + + def test_mapper_closed_on_success(self, minimizer, monkeypatch): + self._setup_driver(monkeypatch) + mock_cls, mock_mapper = self._patch_mapper(monkeypatch) + + minimizer.sample( + x=np.array([1.0]), y=np.array([0.1]), weights=np.array([1.0]), n_workers=2 + ) + + mock_mapper.close.assert_called_once() + mock_mapper.terminate.assert_not_called() From a0257c312169e79f18b4aa5c419aa23359ec6e62 Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Fri, 5 Jun 2026 22:21:04 +0200 Subject: [PATCH 12/15] ruff --- tests/integration/fitting/test_multi_fitter.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/integration/fitting/test_multi_fitter.py b/tests/integration/fitting/test_multi_fitter.py index b27eb7c4..e3045607 100644 --- a/tests/integration/fitting/test_multi_fitter.py +++ b/tests/integration/fitting/test_multi_fitter.py @@ -567,7 +567,9 @@ def test_seed_produces_valid_draws(self): except AttributeError: pytest.skip('BUMPS is not installed') - result = f.mcmc_sample(x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, seed=42) + result = f.mcmc_sample( + x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, seed=42 + ) assert result['draws'].ndim == 2 assert result['draws'].shape[0] > 0 @@ -594,7 +596,9 @@ def test_different_seeds_both_produce_valid_draws(self): except AttributeError: pytest.skip('BUMPS is not installed') - result1 = f.mcmc_sample(x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, seed=42) + result1 = f.mcmc_sample( + x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, seed=42 + ) result2 = f.mcmc_sample( x=[x], y=[y], weights=[weights], samples=100, burn=20, thin=2, seed=12345 ) From 1c09280fab1a90eff11fea11b801d19d007cd9dd Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 8 Jun 2026 11:40:35 +0200 Subject: [PATCH 13/15] added MP related info to the notebook --- docs/docs/tutorials/fitting-bayesian.ipynb | 102 ++++++++++++++++++++- pyproject.toml | 3 +- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/docs/docs/tutorials/fitting-bayesian.ipynb b/docs/docs/tutorials/fitting-bayesian.ipynb index 77e8b884..7f1019a9 100644 --- a/docs/docs/tutorials/fitting-bayesian.ipynb +++ b/docs/docs/tutorials/fitting-bayesian.ipynb @@ -262,8 +262,11 @@ "- `samples` (4000): the total number of posterior draws to generate across all chains;\n", "- `burn` (500): the number of initial *burn-in* iterations to discard — the sampler needs time to find the typical set of the posterior, and early samples are not representative;\n", "- `thin` (2): the *thinning* interval — only every second sample is kept, which reduces autocorrelation between consecutive draws;\n", + "- `population`: DREAM population count (number of parallel chains).\n", "\n", - "First, we switch to the BUMPS minimizer:\n" + "For expensive models, you can parallelise the population evaluation across multiple CPU processes by passing `n_workers`. This is covered in the next section.\n", + "\n", + "First, we switch to the BUMPS minimizer:" ] }, { @@ -298,6 +301,103 @@ "print('parameters:', result['param_names'])" ] }, + { + "cell_type": "markdown", + "id": "01c7cf9d", + "metadata": {}, + "source": [ + "## Faster sampling with multiprocessing\n", + "\n", + "Each DREAM generation evaluates the model for an entire *population* of\n", + "candidate parameter sets. By default these evaluations run sequentially in a\n", + "single process. For **expensive models** (where each evaluation takes tens of\n", + "milliseconds or more) you can speed up sampling by distributing the population\n", + "across multiple worker processes.\n", + "\n", + "Set `n_workers` to the number of parallel workers:\n", + "\n", + "```python\n", + "result = fitter.mcmc_sample(\n", + " ...,\n", + " n_workers=4, # evaluate 4 population members in parallel\n", + ")\n", + "```\n", + "\n", + "**How it works:** `mcmc_sample` serialises the BUMPS `FitProblem` (including the\n", + "model and all parameters) and spawns a `multiprocessing.Pool` using the\n", + "`spawn` start method. Each worker process independently evaluates a single\n", + "population member and returns the negative log-likelihood. The pool is\n", + "automatically closed after sampling finishes or if an error occurs.\n", + "\n", + "**When to use it:**\n", + "\n", + "| `n_workers` | Behaviour |\n", + "|---|---|\n", + "| `None` (default) | Sequential evaluation — no pool is created. |\n", + "| `1` | Also sequential, but explicitly requested. |\n", + "| `2` or more | Parallel evaluation using that many worker processes. |\n", + "\n", + "**Requirements:**\n", + "\n", + "- The `cloudpickle` package must be installed (it is a dependency of\n", + " `easyscience`).\n", + "- The BUMPS `FitProblem` and your model's fit function must be\n", + " *serialisable*. Models that capture non-picklable objects (e.g. open file\n", + " handles, bare `lambda` closures over module-level state) will raise\n", + " `FitError`. If this happens, fall back to `n_workers=None`.\n", + "- On Windows and macOS, the `spawn` start method is used automatically.\n", + " Make sure the sampling call is inside an `if __name__ == '__main__':`\n", + " guard when running from a script.\n", + "\n", + "**Choosing `n_workers`:** as a rule of thumb, set `n_workers` to the number\n", + "of physical CPU cores. If your model is very cheap (< 1 ms per evaluation),\n", + "the overhead of serialisation and inter-process communication may outweigh\n", + "the parallelism gain — try it and compare. The\n", + "`tools/benchmarks/sampling_mpi.py` script in the repository provides a\n", + "ready-made benchmark for your own model.\n", + "\n", + "```{note}\n", + "`n_workers` is capped at the DREAM population size (``population`` × number of\n", + "free parameters) because there is no benefit from more workers than there\n", + "are population members to evaluate.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47c4fb9b", + "metadata": {}, + "outputs": [], + "source": [ + "import multiprocessing\n", + "import time\n", + "\n", + "# For this simple model the overhead of multiprocessing is not worth it,\n", + "# but the pattern below shows exactly how to enable it for expensive models.\n", + "\n", + "n_cores = multiprocessing.cpu_count()\n", + "print(f'Detected {n_cores} CPU cores.')\n", + "\n", + "# Example (commented out because the simple Lorentzian model is too cheap\n", + "# to benefit from parallelism; uncomment and adjust n_workers for your own\n", + "# expensive model):\n", + "#\n", + "# t0 = time.perf_counter()\n", + "# result_parallel = mle_fitter.mcmc_sample(\n", + "# x=omega,\n", + "# y=intensity_obs,\n", + "# weights=1 / intensity_error,\n", + "# samples=10000,\n", + "# burn=500,\n", + "# thin=2,\n", + "# n_workers=min(4, n_cores), # use up to 4 workers\n", + "# )\n", + "# elapsed = time.perf_counter() - t0\n", + "# print(f'Parallel sampling took {elapsed:.1f} s')\n", + "# print(f'Drew {result_parallel[\"draws\"].shape[0]} samples')" + ] + }, { "cell_type": "markdown", "id": "8766b170", diff --git a/pyproject.toml b/pyproject.toml index 1dba9e62..2394f0f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -212,7 +212,8 @@ select = [ # 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 + # The following is replaced by 'D' plus pydocstyle and 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/ ] From 965a38134e0c3fc8488c706454fcd314f2cf2f9f Mon Sep 17 00:00:00 2001 From: Piotr Rozyczko Date: Mon, 8 Jun 2026 14:39:30 +0200 Subject: [PATCH 14/15] silly ruff --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2394f0f3..36ed3fb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -213,7 +213,7 @@ select = [ ignore = [ 'COM812', # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ # The following is replaced by 'D' plus pydocstyle and pydoclint - 'DOC',# https://docs.astral.sh/ruff/rules/#pydoclint-doc + '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/ ] From b4ced1326f8283e112ed91647513f51ee06fc280 Mon Sep 17 00:00:00 2001 From: rozyczko Date: Tue, 9 Jun 2026 10:57:15 +0200 Subject: [PATCH 15/15] Distribute the population across workers in as few tasks as possible --- src/easyscience/fitting/minimizers/minimizer_bumps.py | 7 ++++++- tests/unit/fitting/minimizers/test_minimizer_bumps.py | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 111ea8d7..6e49d055 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -212,7 +212,12 @@ def __call__(self, population: np.ndarray) -> list[float]: # Always reshape to 2D so list() produces one element per chain member. pop = np.atleast_2d(np.asarray(population)) n_points = pop.shape[0] - results = self._pool.map(_evaluate_bumps_point, list(pop), chunksize=1) + # Distribute the population across workers in as few tasks as possible. + # DREAM evaluations are individually cheap, so per-task IPC overhead + # (pickling + queue round-trip) dominates when chunksize=1. Sending one + # chunk per worker amortizes that overhead across the whole generation. + chunksize = max(1, (n_points + self.n_workers - 1) // self.n_workers) + results = self._pool.map(_evaluate_bumps_point, list(pop), chunksize=chunksize) # Safety check: BUMPS DREAM state corruption can occur if the # mapper returns a different number of values than expected. diff --git a/tests/unit/fitting/minimizers/test_minimizer_bumps.py b/tests/unit/fitting/minimizers/test_minimizer_bumps.py index 708c6324..1a1e4dd6 100644 --- a/tests/unit/fitting/minimizers/test_minimizer_bumps.py +++ b/tests/unit/fitting/minimizers/test_minimizer_bumps.py @@ -1231,14 +1231,16 @@ def _mapper(self, map_return): m.n_workers = 2 return m - def test_maps_2d_population_and_passes_chunksize_one(self): + def test_maps_2d_population_and_chunks_across_workers(self): pop = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) mapper = self._mapper([1.0, 2.0, 3.0]) result = mapper(pop) assert result == [1.0, 2.0, 3.0] - assert mapper._pool.map.call_args.kwargs.get('chunksize') == 1 + # 3 points across 2 workers => ceil(3/2) = 2 points per IPC task, + # amortizing per-task multiprocessing overhead over the generation. + assert mapper._pool.map.call_args.kwargs.get('chunksize') == 2 def test_reshapes_1d_point_to_single_row(self): mapper = self._mapper([7.0])