From 6285933ba90c4d9dc70350d12ad0bec5c23550ce Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Wed, 5 Aug 2026 22:09:01 +0200 Subject: [PATCH 1/8] FEAT: accept array-valued parameters in estimators Parameter values passed to ChiSquared and UnbinnedNLL may now be one-dimensional arrays of shape (p,), which are broadcast against the event axis so that the estimator returns one value per parameter point, all in a single backend-parallelized call. This makes it cheap to e.g. propagate fit uncertainties over bootstrapped parameter samples, as done in ComPWA/polarimetry. Behavior for scalar parameter values is unchanged. Closes #571 --- .cspell.json | 1 + benchmarks/unbinned_nll.py | 6 ++-- src/tensorwaves/estimator.py | 50 ++++++++++++++++++---------- src/tensorwaves/function/__init__.py | 20 +++++------ src/tensorwaves/interface.py | 27 +++++++++++---- src/tensorwaves/optimizer/minuit.py | 2 +- src/tensorwaves/optimizer/scipy.py | 4 +-- tests/optimizer/__init__.py | 6 ++-- tests/test_estimator.py | 42 +++++++++++++++++++++++ 9 files changed, 115 insertions(+), 43 deletions(-) diff --git a/.cspell.json b/.cspell.json index af5fdd97..0fa9a858 100644 --- a/.cspell.json +++ b/.cspell.json @@ -242,6 +242,7 @@ "vectorize", "venv", "vmap", + "vmapped", "weisskopf", "wirtinger", "xcode", diff --git a/benchmarks/unbinned_nll.py b/benchmarks/unbinned_nll.py index c5ed4906..2b632222 100644 --- a/benchmarks/unbinned_nll.py +++ b/benchmarks/unbinned_nll.py @@ -277,16 +277,16 @@ def _compute_estimator_reference( def _benchmark_estimator_numpy( - benchmark: Callable[[Callable[[], float]], float], + benchmark: Callable[[Callable[[], float | np.ndarray]], float | np.ndarray], backend: str, data: dict[str, np.ndarray], phsp: dict[str, np.ndarray], parameters: dict[str, float], -) -> float: +) -> float | np.ndarray: estimator = _create_estimator(backend, data, phsp) estimator(parameters) - def run() -> float: + def run() -> float | np.ndarray: return estimator(parameters) return benchmark(run) diff --git a/src/tensorwaves/estimator.py b/src/tensorwaves/estimator.py index 07470c3d..d1d08a76 100644 --- a/src/tensorwaves/estimator.py +++ b/src/tensorwaves/estimator.py @@ -15,6 +15,7 @@ DataSample, DataTransformer, Estimator, + ParameterType, ParameterValue, ParametrizedFunction, ) @@ -85,14 +86,23 @@ def _determine_backend(function: ParametrizedFunction, backend: str | None) -> s def _coerce_parameter_types( - parameters: Mapping[str, ParameterValue], -) -> dict[str, ParameterValue]: - # normalize to float/complex so that JIT compilers see stable input types - # (an int value would otherwise trigger a re-trace once it becomes a float) - return { - name: complex(value) if isinstance(value, complex) else float(value) - for name, value in parameters.items() - } + parameters: Mapping[str, ParameterType], +) -> dict[str, ParameterType]: + return {name: _coerce_parameter_value(value) for name, value in parameters.items()} + + +def _coerce_parameter_value(value: ParameterType) -> ParameterType: + # normalize scalars to float/complex so that JIT compilers see stable input + # types (an int value would otherwise trigger a re-trace once it becomes a + # float) and give parameter arrays a new trailing axis, so that they + # broadcast against the event axis of the data samples + if isinstance(value, complex): + return complex(value) + if isinstance(value, (int, float)): + return float(value) + if getattr(value, "ndim", 0) >= 1: + return value[..., None] + return value def _import_jax(): # ruff: ignore[missing-return-type-private-function] @@ -220,19 +230,19 @@ def __init__( sum_function = find_function("sum", backend) def estimator( - parameters: Mapping[str, ParameterValue], + parameters: Mapping[str, ParameterType], domain: DataSample, observed_values: np.ndarray, weights: np.ndarray, ) -> float: computed_values = function(domain, parameters) chi_squared = weights * (computed_values - observed_values) ** 2 - return sum_function(chi_squared) + return sum_function(chi_squared, axis=-1) self.__estimator = _jit_estimator_core(estimator, backend) self.__gradient = _create_core_gradient(estimator, backend) - def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: return self.__estimator(*self.__estimator_args(parameters)) def gradient( @@ -240,7 +250,7 @@ def gradient( ) -> dict[str, ParameterValue]: return self.__gradient(*self.__estimator_args(parameters)) - def __estimator_args(self, parameters: Mapping[str, ParameterValue]) -> tuple: + def __estimator_args(self, parameters: Mapping[str, ParameterType]) -> tuple: return ( _coerce_parameter_types(parameters), self.__domain, @@ -306,7 +316,7 @@ def __init__( log_function = find_function("log", backend) def estimator( - parameters: Mapping[str, ParameterValue], + parameters: Mapping[str, ParameterType], data: DataSample, phsp: DataSample, phsp_weights: np.ndarray | None, @@ -315,16 +325,20 @@ def estimator( phsp_intensities = function(phsp, parameters) if phsp_weights is not None: phsp_intensities *= phsp_weights - normalization_integral = phsp_volume * mean_function(phsp_intensities) - log_normalization = len(bare_intensities) * log_function( + normalization_integral = phsp_volume * mean_function( + phsp_intensities, axis=-1 + ) + log_normalization = bare_intensities.shape[-1] * log_function( normalization_integral ) - return log_normalization - sum_function(log_function(bare_intensities)) + return log_normalization - sum_function( + log_function(bare_intensities), axis=-1 + ) self.__estimator = _jit_estimator_core(estimator, backend) self.__gradient = _create_core_gradient(estimator, backend) - def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: return self.__estimator(*self.__estimator_args(parameters)) def gradient( @@ -332,7 +346,7 @@ def gradient( ) -> dict[str, ParameterValue]: return self.__gradient(*self.__estimator_args(parameters)) - def __estimator_args(self, parameters: Mapping[str, ParameterValue]) -> tuple: + def __estimator_args(self, parameters: Mapping[str, ParameterType]) -> tuple: return ( _coerce_parameter_types(parameters), self.__data, diff --git a/src/tensorwaves/function/__init__.py b/src/tensorwaves/function/__init__.py index e8f1ad9b..cd2ef0d8 100644 --- a/src/tensorwaves/function/__init__.py +++ b/src/tensorwaves/function/__init__.py @@ -12,6 +12,7 @@ from tensorwaves.interface import ( DataSample, Function, + ParameterType, ParameterValue, ParametrizedFunction, ) @@ -146,10 +147,13 @@ def __init__( def __call__( self, data: DataSample, - parameters: Mapping[str, ParameterValue] | None = None, + parameters: Mapping[str, ParameterType] | None = None, ) -> np.ndarray: - extended_data = {**data, **self.__merge_parameters(parameters)} - return self.__function(extended_data) # ty:ignore[invalid-argument-type] + extended_data: dict = {**data, **self.__parameters} + if parameters is not None: + self.__validate_parameters(parameters) + extended_data.update(parameters) + return self.__function(extended_data) @property def function(self) -> Callable[..., np.ndarray]: @@ -170,18 +174,15 @@ def parameters(self) -> dict[str, ParameterValue]: def with_parameters( self, parameters: Mapping[str, ParameterValue] ) -> ParametrizedBackendFunction: + self.__validate_parameters(parameters) return ParametrizedBackendFunction( function=self.function, argument_order=self.argument_order, - parameters=self.__merge_parameters(parameters), + parameters={**self.__parameters, **parameters}, backend=self.backend, ) - def __merge_parameters( - self, parameters: Mapping[str, ParameterValue] | None - ) -> dict[str, ParameterValue]: - if parameters is None: - return self.__parameters + def __validate_parameters(self, parameters: Mapping[str, ParameterType]) -> None: over_defined = set(parameters) - set(self.__parameters) if over_defined: sep = "\n " @@ -191,7 +192,6 @@ def __merge_parameters( f" Expecting one of:{sep}{parameter_listing}" ) raise ValueError(msg) - return {**self.__parameters, **parameters} def get_source_code(function: Function) -> str: diff --git a/src/tensorwaves/interface.py b/src/tensorwaves/interface.py index 85c5e740..9db00655 100644 --- a/src/tensorwaves/interface.py +++ b/src/tensorwaves/interface.py @@ -39,7 +39,16 @@ def __call__(self, data: InputType) -> OutputType: ... DataSample = dict[str, np.ndarray] """Mapping of variable names to a sequence of data points, used by `Function`.""" ParameterValue = complex | float -"""Allowed types for parameter values.""" +"""Allowed types for scalar parameter values.""" +ParameterType = ParameterValue | np.ndarray +"""Types for parameter values in an evaluation, including arrays of values. + +An array of parameter values represents several parameter points that are evaluated in +one call through `broadcasting +`_ against the event axis +of a `.DataSample`. This can be used to propagate fit uncertainties by evaluating over +e.g. bootstrapped parameter samples in a single, backend-parallelized call. +""" class ParametrizedFunction(Function[InputType, OutputType]): @@ -62,12 +71,13 @@ class ParametrizedFunction(Function[InputType, OutputType]): def __call__( self, data: InputType, - parameters: Mapping[str, ParameterValue] | None = None, + parameters: Mapping[str, ParameterType] | None = None, ) -> OutputType: """Evaluate the function over :code:`data` for these parameter values. Given parameter values are merged with the defaults in :attr:`parameters` for - this evaluation only. + this evaluation only. Parameter values may be arrays, as long as they + broadcast against the event arrays in :code:`data` (see `.ParameterType`). """ @property @@ -90,7 +100,7 @@ class DataTransformer(Function[DataSample, DataSample]): """ -class Estimator(Function[Mapping[str, ParameterValue], float]): +class Estimator(Function[Mapping[str, ParameterType], float | np.ndarray]): """Estimator for discrepancy model and data. See the :mod:`.estimator` module for different implementations of this interface. @@ -99,8 +109,13 @@ class Estimator(Function[Mapping[str, ParameterValue], float]): """ @abstractmethod - def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: # ty:ignore[invalid-method-override] - """Compute estimator value for this combination of parameter values.""" + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: # ty:ignore[invalid-method-override] + """Compute estimator value for this combination of parameter values. + + Parameter values may be one-dimensional arrays of shape :code:`(p,)`, in which + case the estimator returns an array of :code:`p` estimator values, one for + each parameter point (see `.ParameterType`). + """ @abstractmethod def gradient( diff --git a/src/tensorwaves/optimizer/minuit.py b/src/tensorwaves/optimizer/minuit.py index 9bb01cd8..41a27c33 100644 --- a/src/tensorwaves/optimizer/minuit.py +++ b/src/tensorwaves/optimizer/minuit.py @@ -70,7 +70,7 @@ def optimize( logs=_create_log( optimizer=type(self), estimator_type=type(estimator), - estimator_value=estimator(parameters), + estimator_value=float(estimator(parameters)), function_call=n_function_calls, parameters=parameters, ) diff --git a/src/tensorwaves/optimizer/scipy.py b/src/tensorwaves/optimizer/scipy.py index 915ea2b2..a546f536 100644 --- a/src/tensorwaves/optimizer/scipy.py +++ b/src/tensorwaves/optimizer/scipy.py @@ -62,7 +62,7 @@ def optimize( # ruff:ignore[complex-structure] logs=_create_log( optimizer=type(self), estimator_type=type(estimator), - estimator_value=estimator(parameters), + estimator_value=float(estimator(parameters)), function_call=n_function_calls, parameters=parameters, ) @@ -91,7 +91,7 @@ def wrapped_function(pars: list) -> float: logs=_create_log( optimizer=type(self), estimator_type=type(estimator), - estimator_value=estimator(parameters), + estimator_value=float(estimator(parameters)), function_call=n_function_calls, parameters=parameters, ), diff --git a/tests/optimizer/__init__.py b/tests/optimizer/__init__.py index 03aae18b..5ff3f1d2 100644 --- a/tests/optimizer/__init__.py +++ b/tests/optimizer/__init__.py @@ -3,7 +3,7 @@ from enum import Enum from typing import TYPE_CHECKING, Any -from tensorwaves.interface import Estimator, ParameterValue +from tensorwaves.interface import Estimator, ParameterType, ParameterValue from tensorwaves.optimizer.callbacks import Callback if TYPE_CHECKING: @@ -60,7 +60,7 @@ class Polynomial1DMinimaEstimator(Estimator): def __init__(self, polynomial: Callable) -> None: self.__polynomial = polynomial - def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: + def __call__(self, parameters: Mapping[str, ParameterType]) -> float: x = parameters["x"] return self.__polynomial(x) @@ -74,7 +74,7 @@ class Polynomial2DMinimaEstimator(Estimator): def __init__(self, polynomial: Callable) -> None: self.__polynomial = polynomial - def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: + def __call__(self, parameters: Mapping[str, ParameterType]) -> float: x = parameters["x"] y = parameters["y"] return self.__polynomial(x, y) diff --git a/tests/test_estimator.py b/tests/test_estimator.py index c915c0f7..aa78bf8a 100644 --- a/tests/test_estimator.py +++ b/tests/test_estimator.py @@ -109,6 +109,11 @@ def it_scales_the_residuals_with_the_weights(backend: str, linear_function): ) assert estimator({"a": 0, "b": 2}) == 2.5 + def it_broadcasts_array_valued_parameters(linear_function): + estimator = ChiSquared(linear_function, x_data, y_data) + b_values = np.array([1.0, 2.0]) + np.testing.assert_allclose(estimator({"b": b_values}), [0.0, 5.0]) + def it_traces_the_jax_function_only_once(): trace_count = 0 @@ -315,3 +320,40 @@ def it_recovers_the_parameters_that_generated_the_data( assert isinstance(par_error, float) assert abs(par_values[par_name] - par_value) < 4.0 * par_error assert par_value == pytest.approx(par_values[par_name], rel=0.1) + + @pytest.mark.parametrize("backend", ["jax", "numba", "numpy", "tf"]) + def it_broadcasts_array_valued_parameters(backend: str): + x, mu, sigma = sp.symbols("x mu sigma") + function = create_parametrized_function( + expression=sp.exp(-(((x - mu) / sigma) ** 2) / 2), + parameters={mu: 0.5, sigma: 0.1}, + backend=backend, + ) + rng = np.random.default_rng(seed=0) + data = {"x": rng.normal(0.5, 0.1, size=2_000)} + phsp = {"x": rng.uniform(-2.0, 5.0, size=5_000)} + estimator = UnbinnedNLL(function, data, phsp, phsp_volume=7.0) + mu_values = np.array([0.4, 0.5, 0.6]) + batched_output = np.asarray(estimator({"mu": mu_values})) + scalar_outputs = [float(estimator({"mu": value})) for value in mu_values] + assert batched_output.shape == mu_values.shape + np.testing.assert_allclose(batched_output, scalar_outputs, rtol=1e-8) + + def it_matches_jax_vmap_for_batched_evaluation(): + jax = pytest.importorskip("jax") + x, mu, sigma = sp.symbols("x mu sigma") + function = create_parametrized_function( + expression=sp.exp(-(((x - mu) / sigma) ** 2) / 2), + parameters={mu: 0.5, sigma: 0.1}, + backend="jax", + ) + rng = np.random.default_rng(seed=0) + data = {"x": rng.normal(0.5, 0.1, size=2_000)} + phsp = {"x": rng.uniform(-2.0, 5.0, size=5_000)} + estimator = UnbinnedNLL(function, data, phsp, phsp_volume=7.0) + mu_values = np.array([0.4, 0.5, 0.6]) + batched_output = np.asarray(estimator({"mu": mu_values})) + vmapped_output = jax.vmap(lambda value: estimator({"mu": value}))(mu_values) + np.testing.assert_allclose( + batched_output, np.asarray(vmapped_output), rtol=1e-8 + ) From d0163bcaeed0af5b46ea42fcda7f6006df867b00 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:38:14 +0200 Subject: [PATCH 2/8] MAINT: minor formatting improvements --- src/tensorwaves/estimator.py | 10 ++++++---- src/tensorwaves/interface.py | 14 +++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/tensorwaves/estimator.py b/src/tensorwaves/estimator.py index d1d08a76..894e3f8c 100644 --- a/src/tensorwaves/estimator.py +++ b/src/tensorwaves/estimator.py @@ -92,10 +92,12 @@ def _coerce_parameter_types( def _coerce_parameter_value(value: ParameterType) -> ParameterType: - # normalize scalars to float/complex so that JIT compilers see stable input - # types (an int value would otherwise trigger a re-trace once it becomes a - # float) and give parameter arrays a new trailing axis, so that they - # broadcast against the event axis of the data samples + """Normalize values for stable JIT inputs and event-axis broadcasting. + + Scalars are converted to ``float`` or ``complex`` so that a change from an integer + value does not trigger retracing. Parameter arrays receive a new trailing axis so + that they broadcast against the data samples' event axis. + """ if isinstance(value, complex): return complex(value) if isinstance(value, (int, float)): diff --git a/src/tensorwaves/interface.py b/src/tensorwaves/interface.py index 9db00655..7b5a77e8 100644 --- a/src/tensorwaves/interface.py +++ b/src/tensorwaves/interface.py @@ -45,9 +45,9 @@ def __call__(self, data: InputType) -> OutputType: ... An array of parameter values represents several parameter points that are evaluated in one call through `broadcasting -`_ against the event axis -of a `.DataSample`. This can be used to propagate fit uncertainties by evaluating over -e.g. bootstrapped parameter samples in a single, backend-parallelized call. +`_ against the event axis of +a `.DataSample`. This can be used to propagate fit uncertainties by evaluating over e.g. +bootstrapped parameter samples in a single, backend-parallelized call. """ @@ -76,8 +76,8 @@ def __call__( """Evaluate the function over :code:`data` for these parameter values. Given parameter values are merged with the defaults in :attr:`parameters` for - this evaluation only. Parameter values may be arrays, as long as they - broadcast against the event arrays in :code:`data` (see `.ParameterType`). + this evaluation only. Parameter values may be arrays, as long as they broadcast + against the event arrays in :code:`data` (see `.ParameterType`). """ @property @@ -113,8 +113,8 @@ def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarra """Compute estimator value for this combination of parameter values. Parameter values may be one-dimensional arrays of shape :code:`(p,)`, in which - case the estimator returns an array of :code:`p` estimator values, one for - each parameter point (see `.ParameterType`). + case the estimator returns an array of :code:`p` estimator values, one for each + parameter point (see `.ParameterType`). """ @abstractmethod From 1108ab9665c0d89c6aca9edc31f57a861ff9bce1 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:51:17 +0200 Subject: [PATCH 3/8] ENH: use `@overload` for estimator calls --- src/tensorwaves/estimator.py | 18 +++++++++++++++++- src/tensorwaves/interface.py | 10 +++++++++- tests/optimizer/__init__.py | 24 +++++++++++++++++++++--- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/tensorwaves/estimator.py b/src/tensorwaves/estimator.py index 894e3f8c..1e35012e 100644 --- a/src/tensorwaves/estimator.py +++ b/src/tensorwaves/estimator.py @@ -5,7 +5,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, overload from tensorwaves.config import _initialize_jax from tensorwaves.data.transform import SympyDataTransformer @@ -244,6 +244,14 @@ def estimator( self.__estimator = _jit_estimator_core(estimator, backend) self.__gradient = _create_core_gradient(estimator, backend) + @overload + def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... + @overload + def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + @overload + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | np.ndarray: ... def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: return self.__estimator(*self.__estimator_args(parameters)) @@ -340,6 +348,14 @@ def estimator( self.__estimator = _jit_estimator_core(estimator, backend) self.__gradient = _create_core_gradient(estimator, backend) + @overload + def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... + @overload + def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + @overload + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | np.ndarray: ... def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: return self.__estimator(*self.__estimator_args(parameters)) diff --git a/src/tensorwaves/interface.py b/src/tensorwaves/interface.py index 7b5a77e8..caf254c6 100644 --- a/src/tensorwaves/interface.py +++ b/src/tensorwaves/interface.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, Generic, TypeVar +from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload import attrs import numpy as np @@ -108,6 +108,14 @@ class Estimator(Function[Mapping[str, ParameterType], float | np.ndarray]): .. automethod:: __call__ """ + @overload + def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... + @overload + def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + @overload + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | np.ndarray: ... @abstractmethod def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: # ty:ignore[invalid-method-override] """Compute estimator value for this combination of parameter values. diff --git a/tests/optimizer/__init__.py b/tests/optimizer/__init__.py index 5ff3f1d2..966ee5ba 100644 --- a/tests/optimizer/__init__.py +++ b/tests/optimizer/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations from enum import Enum -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, overload from tensorwaves.interface import Estimator, ParameterType, ParameterValue from tensorwaves.optimizer.callbacks import Callback @@ -10,6 +10,8 @@ from collections.abc import Callable, Mapping from unittest.mock import MagicMock + import numpy as np + class CallbackType(Enum): ON_OPTIMIZE_START = "on_optimize_start" @@ -60,7 +62,15 @@ class Polynomial1DMinimaEstimator(Estimator): def __init__(self, polynomial: Callable) -> None: self.__polynomial = polynomial - def __call__(self, parameters: Mapping[str, ParameterType]) -> float: + @overload + def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... + @overload + def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + @overload + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | np.ndarray: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: x = parameters["x"] return self.__polynomial(x) @@ -74,7 +84,15 @@ class Polynomial2DMinimaEstimator(Estimator): def __init__(self, polynomial: Callable) -> None: self.__polynomial = polynomial - def __call__(self, parameters: Mapping[str, ParameterType]) -> float: + @overload + def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... + @overload + def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + @overload + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | np.ndarray: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: x = parameters["x"] y = parameters["y"] return self.__polynomial(x, y) From 6bbb240250c17bcc4b4dbb70273f9b0933768d29 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:16:56 +0200 Subject: [PATCH 4/8] ENH: implement `Array` type alias --- benchmarks/ampform.py | 5 ++-- benchmarks/expression.py | 4 +-- benchmarks/unbinned_nll.py | 24 ++++++++------- docs/conf.py | 3 ++ src/tensorwaves/data/__init__.py | 3 +- src/tensorwaves/data/_data_sample.py | 6 ++-- src/tensorwaves/data/rng.py | 6 ++-- src/tensorwaves/data/transform.py | 9 ++---- src/tensorwaves/estimator.py | 38 +++++++++++------------- src/tensorwaves/function/__init__.py | 18 +++++------ src/tensorwaves/interface.py | 20 ++++++------- tests/data/test_data.py | 5 ++-- tests/optimizer/__init__.py | 20 +++++-------- tests/optimizer/test_fit_simple_model.py | 4 +-- 14 files changed, 80 insertions(+), 85 deletions(-) diff --git a/benchmarks/ampform.py b/benchmarks/ampform.py index d6b64149..91eb3c1b 100644 --- a/benchmarks/ampform.py +++ b/benchmarks/ampform.py @@ -24,6 +24,7 @@ from tensorwaves.function import ParametrizedBackendFunction from tensorwaves.interface import ( + Array, DataSample, FitResult, Function, @@ -72,7 +73,7 @@ def create_function( def generate_data( model: HelicityModel, - function: Function[DataSample, np.ndarray], + function: Function[DataSample, Array], data_sample_size: int, phsp_sample_size: int, backend: str, @@ -109,7 +110,7 @@ def generate_data( def fit( data: DataSample, phsp: DataSample, - function: ParametrizedFunction[DataSample, np.ndarray], + function: ParametrizedFunction[DataSample, Array], initial_parameters: Mapping[str, ParameterValue], backend: str, ) -> FitResult: diff --git a/benchmarks/expression.py b/benchmarks/expression.py index 796b9c9a..f8a9606d 100644 --- a/benchmarks/expression.py +++ b/benchmarks/expression.py @@ -12,7 +12,7 @@ from tensorwaves.optimizer.scipy import ScipyMinimizer if TYPE_CHECKING: - from tensorwaves.interface import DataSample, Function + from tensorwaves.interface import Array, DataSample, Function def gaussian(x: sp.Symbol, mu: sp.Symbol, sigma: sp.Symbol) -> sp.Expr: @@ -64,7 +64,7 @@ def _generate_domain( def _generate_data( size: int, - function: Function[DataSample, np.ndarray], + function: Function[DataSample, Array], rng: np.random.Generator, bunch_size: int = 10_000, ) -> DataSample: diff --git a/benchmarks/unbinned_nll.py b/benchmarks/unbinned_nll.py index 2b632222..c7bd1a82 100644 --- a/benchmarks/unbinned_nll.py +++ b/benchmarks/unbinned_nll.py @@ -18,6 +18,8 @@ if TYPE_CHECKING: from collections.abc import Callable + from tensorwaves.interface import Array, DataSample + def prange(stop: int) -> range: ... else: @@ -193,7 +195,7 @@ def intensities() -> tuple[np.ndarray, np.ndarray]: @pytest.fixture(scope="module") -def estimator_samples() -> tuple[dict[str, np.ndarray], dict[str, np.ndarray]]: +def estimator_samples() -> tuple[DataSample, DataSample]: rng = np.random.default_rng(seed=0) data = {"x": rng.uniform(low=-2.0, high=2.0, size=1_000_000)} phsp = {"x": rng.uniform(low=-2.0, high=2.0, size=1_000_000)} @@ -221,7 +223,7 @@ def tensorflow_intensities( @pytest.fixture(scope="module") def jax_estimator_samples( - estimator_samples: tuple[dict[str, np.ndarray], dict[str, np.ndarray]], + estimator_samples: tuple[DataSample, DataSample], ) -> tuple[dict[str, jax.Array], dict[str, jax.Array]]: configure(jax_precision="float64") data, phsp = estimator_samples @@ -233,7 +235,7 @@ def jax_estimator_samples( @pytest.fixture(scope="module") def tensorflow_estimator_samples( - estimator_samples: tuple[dict[str, np.ndarray], dict[str, np.ndarray]], + estimator_samples: tuple[DataSample, DataSample], ) -> tuple[dict[str, tf.Tensor], dict[str, tf.Tensor]]: data, phsp = estimator_samples return {"x": tnp.asarray(data["x"])}, {"x": tnp.asarray(phsp["x"])} @@ -267,8 +269,8 @@ def _create_estimator( def _compute_estimator_reference( - data: dict[str, np.ndarray], - phsp: dict[str, np.ndarray], + data: DataSample, + phsp: DataSample, center: float, ) -> float: data_intensities = _numpy_intensity(data["x"], center) @@ -277,16 +279,16 @@ def _compute_estimator_reference( def _benchmark_estimator_numpy( - benchmark: Callable[[Callable[[], float | np.ndarray]], float | np.ndarray], + benchmark: Callable[[Callable[[], float | Array]], float | Array], backend: str, - data: dict[str, np.ndarray], - phsp: dict[str, np.ndarray], + data: DataSample, + phsp: DataSample, parameters: dict[str, float], -) -> float | np.ndarray: +) -> float | Array: estimator = _create_estimator(backend, data, phsp) estimator(parameters) - def run() -> float | np.ndarray: + def run() -> float | Array: return estimator(parameters) return benchmark(run) @@ -413,7 +415,7 @@ def test_unbinned_nll_normalization_formula( def test_unbinned_nll_estimator( benchmark, backend: str, - estimator_samples: tuple[dict[str, np.ndarray], dict[str, np.ndarray]], + estimator_samples: tuple[DataSample, DataSample], request: pytest.FixtureRequest, ) -> None: data, phsp = estimator_samples diff --git a/docs/conf.py b/docs/conf.py index 38984f42..6f870356 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,6 +53,7 @@ def get_tensorflow_url() -> str: add_module_names = False api_github_repo = f"{ORGANIZATION}/{REPO_NAME}" api_target_substitutions: dict[str, str | tuple[str, str]] = { + "Array": "tensorwaves.interface.Array", "DataSample": "tensorwaves.interface.DataSample", "np.ndarray": "numpy.ndarray", "ParameterValue": "tensorwaves.interface.ParameterValue", @@ -64,6 +65,7 @@ def get_tensorflow_url() -> str: "TypeAliasForwardRef": "typing.TypeAlias", } api_target_types: dict[str, str | tuple[str, str]] = { + "tensorwaves.interface.Array": "obj", "tensorwaves.interface.DataSample": "obj", "tensorwaves.interface.InputType": "obj", "tensorwaves.interface.OutputType": "obj", @@ -77,6 +79,7 @@ def get_tensorflow_url() -> str: } autodoc_member_order = "bysource" autodoc_type_aliases = { + "Array": "tensorwaves.interface.Array", "DataSample": "tensorwaves.interface.DataSample", "InputType": "tensorwaves.interface.InputType", "OutputType": "tensorwaves.interface.OutputType", diff --git a/src/tensorwaves/data/__init__.py b/src/tensorwaves/data/__init__.py index fb6622a5..8983fc03 100644 --- a/src/tensorwaves/data/__init__.py +++ b/src/tensorwaves/data/__init__.py @@ -8,6 +8,7 @@ from tqdm.auto import tqdm from tensorwaves.interface import ( + Array, DataGenerator, DataSample, DataTransformer, @@ -69,7 +70,7 @@ class IntensityDistributionGenerator(DataGenerator): def __init__( self, domain_generator: DataGenerator, - function: Function[DataSample, np.ndarray], + function: Function[DataSample, Array], domain_transformer: DataTransformer | None = None, bunch_size: int = 50_000, ) -> None: diff --git a/src/tensorwaves/data/_data_sample.py b/src/tensorwaves/data/_data_sample.py index bbb35dee..34f937d5 100644 --- a/src/tensorwaves/data/_data_sample.py +++ b/src/tensorwaves/data/_data_sample.py @@ -12,7 +12,7 @@ from tqdm.auto import tqdm - from tensorwaves.interface import DataSample + from tensorwaves.interface import Array, DataSample def get_number_of_events(four_momenta: DataSample) -> int: @@ -28,7 +28,7 @@ def merge_events(sample1: DataSample, sample2: DataSample) -> DataSample: def _determine_merge_method( sample: DataSample, -) -> Callable[[tuple[np.ndarray, np.ndarray]], np.ndarray]: +) -> Callable[[tuple[Array, Array]], Array]: if len(sample) == 0: return operator.itemgetter(1) some_array = next(iter(sample.values())) @@ -44,7 +44,7 @@ def _determine_merge_method( def _merge_events( sample1: DataSample, sample2: DataSample, - merge_method: Callable[[tuple[np.ndarray, np.ndarray]], np.ndarray], + merge_method: Callable[[tuple[Array, Array]], Array], ) -> DataSample: if len(sample1) and len(sample2) and set(sample1) != set(sample2): msg = "Keys of data sets are not matching" diff --git a/src/tensorwaves/data/rng.py b/src/tensorwaves/data/rng.py index 3054ef71..68b8d6df 100644 --- a/src/tensorwaves/data/rng.py +++ b/src/tensorwaves/data/rng.py @@ -8,7 +8,7 @@ from tensorwaves.config import _tensorflow_precision from tensorwaves.function._backend import raise_missing_module_error -from tensorwaves.interface import RealNumberGenerator +from tensorwaves.interface import Array, RealNumberGenerator if TYPE_CHECKING: # pragma: no cover import tensorflow as tf @@ -24,7 +24,7 @@ def __init__(self, seed: int | None = None) -> None: def __call__( self, size: int, min_value: float = 0.0, max_value: float = 1.0 - ) -> np.ndarray: + ) -> Array: return self.generator.uniform(size=size, low=min_value, high=max_value) @property @@ -50,7 +50,7 @@ def __init__(self, seed: int | None = None) -> None: def __call__( self, size: int, min_value: float = 0.0, max_value: float = 1.0 - ) -> np.ndarray: + ) -> Array: return self.generator.uniform( shape=[size], minval=min_value, diff --git a/src/tensorwaves/data/transform.py b/src/tensorwaves/data/transform.py index af244716..05e1d97c 100644 --- a/src/tensorwaves/data/transform.py +++ b/src/tensorwaves/data/transform.py @@ -8,14 +8,13 @@ from tensorwaves.function import PositionalArgumentFunction from tensorwaves.function.sympy import _get_free_symbols, _lambdify_normal_or_fast -from tensorwaves.interface import DataSample, DataTransformer, Function +from tensorwaves.interface import Array, DataSample, DataTransformer, Function from ._attrs import to_tuple if TYPE_CHECKING: # pragma: no cover from collections.abc import Mapping - import numpy as np import sympy as sp @@ -55,9 +54,7 @@ def __call__(self, data: DataSample) -> DataSample: class SympyDataTransformer(DataTransformer): """Implementation of a `.DataTransformer`.""" - def __init__( - self, functions: Mapping[str, Function[DataSample, np.ndarray]] - ) -> None: + def __init__(self, functions: Mapping[str, Function[DataSample, Array]]) -> None: if any(not isinstance(f, Function) for f in functions.values()): msg = ( f"Not all values in the mapping are an instance of {Function.__name__}" @@ -66,7 +63,7 @@ def __init__( self.__functions = dict(functions) @property - def functions(self) -> dict[str, Function[DataSample, np.ndarray]]: + def functions(self) -> dict[str, Function[DataSample, Array]]: """Read-only access to the internal mapping of functions.""" return dict(self.__functions) diff --git a/src/tensorwaves/estimator.py b/src/tensorwaves/estimator.py index 1e35012e..2df5df0f 100644 --- a/src/tensorwaves/estimator.py +++ b/src/tensorwaves/estimator.py @@ -12,6 +12,7 @@ from tensorwaves.function._backend import find_function, raise_missing_module_error from tensorwaves.function.sympy import create_parametrized_function, prepare_caching from tensorwaves.interface import ( + Array, DataSample, DataTransformer, Estimator, @@ -23,7 +24,6 @@ if TYPE_CHECKING: from collections.abc import Callable, Iterable, Mapping - import numpy as np import sympy as sp @@ -34,7 +34,7 @@ def create_cached_function( free_parameters: Iterable[sp.Basic], *, use_cse: bool = True, -) -> tuple[ParametrizedFunction[DataSample, np.ndarray], DataTransformer]: +) -> tuple[ParametrizedFunction[DataSample, Array], DataTransformer]: """Create a function and data transformer for cached computations. Once it is known which parameters in an expression are to be optimized, this @@ -171,7 +171,7 @@ def _create_core_gradient(core: Callable, backend: str) -> Callable: def gradient( parameters: Mapping[str, ParameterValue], - *data_args: DataSample | np.ndarray | None, + *data_args: DataSample | Array | None, ) -> dict[str, ParameterValue]: return _conjugate_complex_gradient(raw_gradient(parameters, *data_args)) @@ -179,7 +179,7 @@ def gradient( def raise_gradient_not_implemented( parameters: Mapping[str, ParameterValue], - *data_args: DataSample | np.ndarray | None, + *data_args: DataSample | Array | None, ) -> dict[str, ParameterValue]: msg = f"Gradient not implemented for back-end {backend}." raise NotImplementedError(msg) @@ -213,10 +213,10 @@ class ChiSquared(Estimator): def __init__( self, - function: ParametrizedFunction[DataSample, np.ndarray], + function: ParametrizedFunction[DataSample, Array], domain: DataSample, - observed_values: np.ndarray, - weights: np.ndarray | None = None, + observed_values: Array, + weights: Array | None = None, backend: str | None = None, ) -> None: backend = _determine_backend(function, backend) @@ -234,8 +234,8 @@ def __init__( def estimator( parameters: Mapping[str, ParameterType], domain: DataSample, - observed_values: np.ndarray, - weights: np.ndarray, + observed_values: Array, + weights: Array, ) -> float: computed_values = function(domain, parameters) chi_squared = weights * (computed_values - observed_values) ** 2 @@ -247,12 +247,10 @@ def estimator( @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + def __call__(self, parameters: Mapping[str, Array]) -> Array: ... @overload - def __call__( - self, parameters: Mapping[str, ParameterType] - ) -> float | np.ndarray: ... - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: return self.__estimator(*self.__estimator_args(parameters)) def gradient( @@ -310,7 +308,7 @@ class UnbinnedNLL(Estimator): def __init__( self, - function: ParametrizedFunction[DataSample, np.ndarray], + function: ParametrizedFunction[DataSample, Array], data: DataSample, phsp: DataSample, phsp_volume: float = 1.0, @@ -329,7 +327,7 @@ def estimator( parameters: Mapping[str, ParameterType], data: DataSample, phsp: DataSample, - phsp_weights: np.ndarray | None, + phsp_weights: Array | None, ) -> float: bare_intensities = function(data, parameters) phsp_intensities = function(phsp, parameters) @@ -351,12 +349,10 @@ def estimator( @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + def __call__(self, parameters: Mapping[str, Array]) -> Array: ... @overload - def __call__( - self, parameters: Mapping[str, ParameterType] - ) -> float | np.ndarray: ... - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: return self.__estimator(*self.__estimator_args(parameters)) def gradient( diff --git a/src/tensorwaves/function/__init__.py b/src/tensorwaves/function/__init__.py index cd2ef0d8..07350cf7 100644 --- a/src/tensorwaves/function/__init__.py +++ b/src/tensorwaves/function/__init__.py @@ -6,10 +6,10 @@ from typing import TYPE_CHECKING, Protocol, runtime_checkable import attrs -import numpy as np from attrs import field, frozen from tensorwaves.interface import ( + Array, DataSample, Function, ParameterType, @@ -43,7 +43,7 @@ class BackendFunction(Protocol): """ @property - def function(self) -> Callable[..., np.ndarray]: + def function(self) -> Callable[..., Array]: """Backend-native function that takes positional arguments only.""" @property @@ -102,7 +102,7 @@ def _to_tuple(argument_order: Iterable[str]) -> tuple[str, ...]: @frozen -class PositionalArgumentFunction(Function[DataSample, np.ndarray]): +class PositionalArgumentFunction(Function[DataSample, Array]): """Wrapper around a function with positional arguments. This class provides a :meth:`~.Function.__call__` that can take a `.DataSample` for @@ -114,7 +114,7 @@ class PositionalArgumentFunction(Function[DataSample, np.ndarray]): .. seealso:: :func:`.create_function` """ - function: Callable[..., np.ndarray] = field(validator=_validate_arguments) + function: Callable[..., Array] = field(validator=_validate_arguments) """A function with positional arguments only.""" argument_order: tuple[str, ...] = field( converter=_to_tuple, validator=[_all_str, _all_unique] @@ -123,12 +123,12 @@ class PositionalArgumentFunction(Function[DataSample, np.ndarray]): backend: str | None = None """Name of the computational backend that :attr:`function` was compiled for.""" - def __call__(self, data: DataSample) -> np.ndarray: + def __call__(self, data: DataSample) -> Array: args = [data[var_name] for var_name in self.argument_order] return self.function(*args) -class ParametrizedBackendFunction(ParametrizedFunction[DataSample, np.ndarray]): +class ParametrizedBackendFunction(ParametrizedFunction[DataSample, Array]): """Implements `.ParametrizedFunction` for a specific computational back-end. .. seealso:: :func:`.create_parametrized_function` @@ -136,7 +136,7 @@ class ParametrizedBackendFunction(ParametrizedFunction[DataSample, np.ndarray]): def __init__( self, - function: Callable[..., np.ndarray], + function: Callable[..., Array], argument_order: Iterable[str], parameters: Mapping[str, ParameterValue], backend: str | None = None, @@ -148,7 +148,7 @@ def __call__( self, data: DataSample, parameters: Mapping[str, ParameterType] | None = None, - ) -> np.ndarray: + ) -> Array: extended_data: dict = {**data, **self.__parameters} if parameters is not None: self.__validate_parameters(parameters) @@ -156,7 +156,7 @@ def __call__( return self.__function(extended_data) @property - def function(self) -> Callable[..., np.ndarray]: + def function(self) -> Callable[..., Array]: return self.__function.function @property diff --git a/src/tensorwaves/interface.py b/src/tensorwaves/interface.py index caf254c6..584c23a3 100644 --- a/src/tensorwaves/interface.py +++ b/src/tensorwaves/interface.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload +from typing import TYPE_CHECKING, Any, Generic, TypeAlias, TypeVar, overload import attrs import numpy as np @@ -36,11 +36,13 @@ class Function(ABC, Generic[InputType, OutputType]): def __call__(self, data: InputType) -> OutputType: ... -DataSample = dict[str, np.ndarray] +Array: TypeAlias = np.ndarray[Any, np.dtype[Any]] +"""Type representing numerical arrays.""" +DataSample = dict[str, Array] """Mapping of variable names to a sequence of data points, used by `Function`.""" ParameterValue = complex | float """Allowed types for scalar parameter values.""" -ParameterType = ParameterValue | np.ndarray +ParameterType = ParameterValue | Array """Types for parameter values in an evaluation, including arrays of values. An array of parameter values represents several parameter points that are evaluated in @@ -100,7 +102,7 @@ class DataTransformer(Function[DataSample, DataSample]): """ -class Estimator(Function[Mapping[str, ParameterType], float | np.ndarray]): +class Estimator(Function[Mapping[str, ParameterType], float | Array]): """Estimator for discrepancy model and data. See the :mod:`.estimator` module for different implementations of this interface. @@ -111,13 +113,11 @@ class Estimator(Function[Mapping[str, ParameterType], float | np.ndarray]): @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + def __call__(self, parameters: Mapping[str, Array]) -> Array: ... @overload - def __call__( - self, parameters: Mapping[str, ParameterType] - ) -> float | np.ndarray: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... @abstractmethod - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: # ty:ignore[invalid-method-override] + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: # ty:ignore[invalid-method-override] """Compute estimator value for this combination of parameter values. Parameter values may be one-dimensional arrays of shape :code:`(p,)`, in which @@ -247,7 +247,7 @@ class RealNumberGenerator(ABC): @abstractmethod def __call__( self, size: int, min_value: float = 0.0, max_value: float = 1.0 - ) -> np.ndarray: + ) -> Array: """Generate random floats in the range [min_value, max_value).""" @property diff --git a/tests/data/test_data.py b/tests/data/test_data.py index ee2f694a..0bbdf5a9 100644 --- a/tests/data/test_data.py +++ b/tests/data/test_data.py @@ -17,6 +17,7 @@ ) from tensorwaves.function.sympy import create_function from tensorwaves.interface import ( + Array, DataGenerator, DataSample, Function, @@ -27,8 +28,8 @@ from _pytest.capture import CaptureFixture -class FlatDistribution(Function[DataSample, np.ndarray]): - def __call__(self, data: DataSample) -> np.ndarray: +class FlatDistribution(Function[DataSample, Array]): + def __call__(self, data: DataSample) -> Array: some_key = next(iter(data)) sample_size = len(data[some_key]) return np.ones(sample_size) diff --git a/tests/optimizer/__init__.py b/tests/optimizer/__init__.py index 966ee5ba..1e228bd9 100644 --- a/tests/optimizer/__init__.py +++ b/tests/optimizer/__init__.py @@ -3,15 +3,13 @@ from enum import Enum from typing import TYPE_CHECKING, Any, overload -from tensorwaves.interface import Estimator, ParameterType, ParameterValue +from tensorwaves.interface import Array, Estimator, ParameterType, ParameterValue from tensorwaves.optimizer.callbacks import Callback if TYPE_CHECKING: from collections.abc import Callable, Mapping from unittest.mock import MagicMock - import numpy as np - class CallbackType(Enum): ON_OPTIMIZE_START = "on_optimize_start" @@ -65,12 +63,10 @@ def __init__(self, polynomial: Callable) -> None: @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + def __call__(self, parameters: Mapping[str, Array]) -> Array: ... @overload - def __call__( - self, parameters: Mapping[str, ParameterType] - ) -> float | np.ndarray: ... - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: x = parameters["x"] return self.__polynomial(x) @@ -87,12 +83,10 @@ def __init__(self, polynomial: Callable) -> None: @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, np.ndarray]) -> np.ndarray: ... + def __call__(self, parameters: Mapping[str, Array]) -> Array: ... @overload - def __call__( - self, parameters: Mapping[str, ParameterType] - ) -> float | np.ndarray: ... - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | np.ndarray: + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: x = parameters["x"] y = parameters["y"] return self.__polynomial(x, y) diff --git a/tests/optimizer/test_fit_simple_model.py b/tests/optimizer/test_fit_simple_model.py index afb576fd..fcc3eb05 100644 --- a/tests/optimizer/test_fit_simple_model.py +++ b/tests/optimizer/test_fit_simple_model.py @@ -23,7 +23,7 @@ import iminuit - from tensorwaves.interface import DataSample, Function + from tensorwaves.interface import Array, DataSample, Function def generate_domain( @@ -40,7 +40,7 @@ def generate_domain( def generate_data( size: int, boundaries: dict[str, tuple[float, float]], - function: Function[DataSample, np.ndarray], + function: Function[DataSample, Array], rng: np.random.Generator, bunch_size: int = 10_000, ) -> DataSample: From a64f925aaca8703775d3ee29b235f5dc7f3575dc Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:50:31 +0200 Subject: [PATCH 5/8] ENH: parametrize `Array` type alias by scalar type --- docs/conf.py | 7 ++ pyproject.toml | 1 + src/tensorwaves/estimator.py | 17 ++-- src/tensorwaves/interface.py | 30 +++++-- tests/optimizer/__init__.py | 24 ++++-- tests/test_estimator.py | 10 +-- uv.lock | 146 ++++++++++++++++++----------------- 7 files changed, 137 insertions(+), 98 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6f870356..7a3f7fa7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,10 +55,13 @@ def get_tensorflow_url() -> str: api_target_substitutions: dict[str, str | tuple[str, str]] = { "Array": "tensorwaves.interface.Array", "DataSample": "tensorwaves.interface.DataSample", + "FloatArray": "tensorwaves.interface.FloatArray", + "np.floating": "numpy.floating", "np.ndarray": "numpy.ndarray", "ParameterValue": "tensorwaves.interface.ParameterValue", "Path": "pathlib.Path", "ProgressColumn": "rich.progress.ProgressColumn", + "ScalarT": "tensorwaves.interface.ScalarT", "sp.Basic": "sympy.core.basic.Basic", "sp.Expr": "sympy.core.expr.Expr", "sp.Symbol": "sympy.core.symbol.Symbol", @@ -67,9 +70,11 @@ def get_tensorflow_url() -> str: api_target_types: dict[str, str | tuple[str, str]] = { "tensorwaves.interface.Array": "obj", "tensorwaves.interface.DataSample": "obj", + "tensorwaves.interface.FloatArray": "obj", "tensorwaves.interface.InputType": "obj", "tensorwaves.interface.OutputType": "obj", "tensorwaves.interface.ParameterValue": "obj", + "tensorwaves.interface.ScalarT": "obj", } author = "" autodoc_default_options = { @@ -81,9 +86,11 @@ def get_tensorflow_url() -> str: autodoc_type_aliases = { "Array": "tensorwaves.interface.Array", "DataSample": "tensorwaves.interface.DataSample", + "FloatArray": "tensorwaves.interface.FloatArray", "InputType": "tensorwaves.interface.InputType", "OutputType": "tensorwaves.interface.OutputType", "ParameterValue": "tensorwaves.interface.ParameterValue", + "ScalarT": "tensorwaves.interface.ScalarT", } autodoc_typehints_format = "short" autosectionlabel_prefix_document = True diff --git a/pyproject.toml b/pyproject.toml index aecde89f..a74e4e44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ "rich", "sympy >=1.9", # lambdify cse "tqdm >=4.24.0", # autonotebook + "typing-extensions >=4.4.0; python_version <'3.13.0'", # TypeVar defaults ] dynamic = ["version"] diff --git a/src/tensorwaves/estimator.py b/src/tensorwaves/estimator.py index 2df5df0f..b127d3c8 100644 --- a/src/tensorwaves/estimator.py +++ b/src/tensorwaves/estimator.py @@ -16,6 +16,7 @@ DataSample, DataTransformer, Estimator, + FloatArray, ParameterType, ParameterValue, ParametrizedFunction, @@ -247,10 +248,12 @@ def estimator( @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, Array]) -> Array: ... + def __call__(self, parameters: Mapping[str, Array]) -> FloatArray: ... @overload - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | FloatArray: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | FloatArray: return self.__estimator(*self.__estimator_args(parameters)) def gradient( @@ -349,10 +352,12 @@ def estimator( @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, Array]) -> Array: ... + def __call__(self, parameters: Mapping[str, Array]) -> FloatArray: ... @overload - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | FloatArray: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | FloatArray: return self.__estimator(*self.__estimator_args(parameters)) def gradient( diff --git a/src/tensorwaves/interface.py b/src/tensorwaves/interface.py index 584c23a3..69733b5c 100644 --- a/src/tensorwaves/interface.py +++ b/src/tensorwaves/interface.py @@ -2,15 +2,21 @@ from __future__ import annotations +import sys from abc import ABC, abstractmethod from collections.abc import Mapping -from typing import TYPE_CHECKING, Any, Generic, TypeAlias, TypeVar, overload +from typing import TYPE_CHECKING, Any, Generic, TypeAlias, overload import attrs import numpy as np from attrs import field, frozen from attrs.validators import instance_of, optional +if sys.version_info >= (3, 13): + from typing import TypeVar +else: + from typing_extensions import TypeVar # https://peps.python.org/pep-0696 + if TYPE_CHECKING: # pragma: no cover from IPython.lib.pretty import PrettyPrinter @@ -36,8 +42,16 @@ class Function(ABC, Generic[InputType, OutputType]): def __call__(self, data: InputType) -> OutputType: ... -Array: TypeAlias = np.ndarray[Any, np.dtype[Any]] -"""Type representing numerical arrays.""" +ScalarT = TypeVar("ScalarT", bound=np.generic, default=Any) +"""The scalar type (dtype) of an `Array`.""" +Array: TypeAlias = np.ndarray[Any, np.dtype[ScalarT]] +"""Type representing numerical arrays. + +The alias is generic in its scalar type, so `Array` is dtype-agnostic, while +`FloatArray` narrows to an array of floats. +""" +FloatArray: TypeAlias = Array[np.floating] +"""An `Array` of real-valued numbers.""" DataSample = dict[str, Array] """Mapping of variable names to a sequence of data points, used by `Function`.""" ParameterValue = complex | float @@ -102,7 +116,7 @@ class DataTransformer(Function[DataSample, DataSample]): """ -class Estimator(Function[Mapping[str, ParameterType], float | Array]): +class Estimator(Function[Mapping[str, ParameterType], float | FloatArray]): """Estimator for discrepancy model and data. See the :mod:`.estimator` module for different implementations of this interface. @@ -113,11 +127,13 @@ class Estimator(Function[Mapping[str, ParameterType], float | Array]): @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, Array]) -> Array: ... + def __call__(self, parameters: Mapping[str, Array]) -> FloatArray: ... @overload - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | FloatArray: ... @abstractmethod - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: # ty:ignore[invalid-method-override] + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | FloatArray: # ty:ignore[invalid-method-override] """Compute estimator value for this combination of parameter values. Parameter values may be one-dimensional arrays of shape :code:`(p,)`, in which diff --git a/tests/optimizer/__init__.py b/tests/optimizer/__init__.py index 1e228bd9..e32b034c 100644 --- a/tests/optimizer/__init__.py +++ b/tests/optimizer/__init__.py @@ -3,7 +3,13 @@ from enum import Enum from typing import TYPE_CHECKING, Any, overload -from tensorwaves.interface import Array, Estimator, ParameterType, ParameterValue +from tensorwaves.interface import ( + Array, + Estimator, + FloatArray, + ParameterType, + ParameterValue, +) from tensorwaves.optimizer.callbacks import Callback if TYPE_CHECKING: @@ -63,10 +69,12 @@ def __init__(self, polynomial: Callable) -> None: @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, Array]) -> Array: ... + def __call__(self, parameters: Mapping[str, Array]) -> FloatArray: ... @overload - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | FloatArray: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | FloatArray: x = parameters["x"] return self.__polynomial(x) @@ -83,10 +91,12 @@ def __init__(self, polynomial: Callable) -> None: @overload def __call__(self, parameters: Mapping[str, ParameterValue]) -> float: ... @overload - def __call__(self, parameters: Mapping[str, Array]) -> Array: ... + def __call__(self, parameters: Mapping[str, Array]) -> FloatArray: ... @overload - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: ... - def __call__(self, parameters: Mapping[str, ParameterType]) -> float | Array: + def __call__( + self, parameters: Mapping[str, ParameterType] + ) -> float | FloatArray: ... + def __call__(self, parameters: Mapping[str, ParameterType]) -> float | FloatArray: x = parameters["x"] y = parameters["y"] return self.__polynomial(x, y) diff --git a/tests/test_estimator.py b/tests/test_estimator.py index aa78bf8a..9c0d713a 100644 --- a/tests/test_estimator.py +++ b/tests/test_estimator.py @@ -334,8 +334,8 @@ def it_broadcasts_array_valued_parameters(backend: str): phsp = {"x": rng.uniform(-2.0, 5.0, size=5_000)} estimator = UnbinnedNLL(function, data, phsp, phsp_volume=7.0) mu_values = np.array([0.4, 0.5, 0.6]) - batched_output = np.asarray(estimator({"mu": mu_values})) - scalar_outputs = [float(estimator({"mu": value})) for value in mu_values] + batched_output = estimator({"mu": mu_values}) + scalar_outputs = [estimator({"mu": value}) for value in mu_values] assert batched_output.shape == mu_values.shape np.testing.assert_allclose(batched_output, scalar_outputs, rtol=1e-8) @@ -352,8 +352,6 @@ def it_matches_jax_vmap_for_batched_evaluation(): phsp = {"x": rng.uniform(-2.0, 5.0, size=5_000)} estimator = UnbinnedNLL(function, data, phsp, phsp_volume=7.0) mu_values = np.array([0.4, 0.5, 0.6]) - batched_output = np.asarray(estimator({"mu": mu_values})) + batched_output = estimator({"mu": mu_values}) vmapped_output = jax.vmap(lambda value: estimator({"mu": value}))(mu_values) - np.testing.assert_allclose( - batched_output, np.asarray(vmapped_output), rtol=1e-8 - ) + np.testing.assert_allclose(batched_output, vmapped_output, rtol=1e-8) diff --git a/uv.lock b/uv.lock index 31a03f07..1165430a 100644 --- a/uv.lock +++ b/uv.lock @@ -3,16 +3,16 @@ revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -703,16 +703,16 @@ version = "1.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -1059,16 +1059,16 @@ version = "0.22.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -1683,16 +1683,16 @@ version = "9.17.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -1803,16 +1803,16 @@ version = "0.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -1905,16 +1905,16 @@ version = "0.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -2339,16 +2339,16 @@ version = "3.15.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -2620,16 +2620,16 @@ version = "4.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -2810,16 +2810,16 @@ version = "3.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -3053,16 +3053,16 @@ version = "5.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -3380,16 +3380,16 @@ version = "2.5.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } @@ -3681,16 +3681,16 @@ version = "3.0.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -4108,16 +4108,16 @@ version = "0.20.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -4843,16 +4843,16 @@ version = "2026.6.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -5143,16 +5143,16 @@ version = "1.18.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -5346,16 +5346,16 @@ version = "9.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -5421,16 +5421,16 @@ version = "1.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -5516,16 +5516,16 @@ version = "0.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -5852,6 +5852,7 @@ dependencies = [ { name = "rich" }, { name = "sympy" }, { name = "tqdm" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] [package.optional-dependencies] @@ -6074,6 +6075,7 @@ requires-dist = [ { name = "tensorwaves", extras = ["phsp"], marker = "extra == 'pwa'" }, { name = "tensorwaves", extras = ["tf"], marker = "extra == 'phsp'" }, { name = "tqdm", specifier = ">=4.24.0" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'", specifier = ">=4.4.0" }, ] provides-extras = ["jax", "numba", "phsp", "pwa", "scipy", "tf"] From 3e62f806257017775a64a1d8ab19f72c221e6939 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:54:39 +0200 Subject: [PATCH 6/8] MAINT: upgrade lock files --- uv.lock | 144 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/uv.lock b/uv.lock index 1165430a..5a23886c 100644 --- a/uv.lock +++ b/uv.lock @@ -3,16 +3,16 @@ revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -703,16 +703,16 @@ version = "1.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -1059,16 +1059,16 @@ version = "0.22.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -1683,16 +1683,16 @@ version = "9.17.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -1803,16 +1803,16 @@ version = "0.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -1905,16 +1905,16 @@ version = "0.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -2339,16 +2339,16 @@ version = "3.15.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -2620,16 +2620,16 @@ version = "4.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -2810,16 +2810,16 @@ version = "3.11.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -3053,16 +3053,16 @@ version = "5.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -3380,16 +3380,16 @@ version = "2.5.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } @@ -3681,16 +3681,16 @@ version = "3.0.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -4108,16 +4108,16 @@ version = "0.20.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -4843,16 +4843,16 @@ version = "2026.6.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -5143,16 +5143,16 @@ version = "1.18.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -5346,16 +5346,16 @@ version = "9.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -5421,16 +5421,16 @@ version = "1.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", @@ -5516,16 +5516,16 @@ version = "0.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.13.*' and sys_platform == 'win32'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'emscripten'", From db5d0666627a0fb1f4784cb0e0d6e174a9237022 Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:57:35 +0200 Subject: [PATCH 7/8] ENH: narrow intensity function types to `FloatArray` --- benchmarks/ampform.py | 6 +++--- src/tensorwaves/data/__init__.py | 6 +++--- src/tensorwaves/estimator.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/benchmarks/ampform.py b/benchmarks/ampform.py index 91eb3c1b..5cf3f151 100644 --- a/benchmarks/ampform.py +++ b/benchmarks/ampform.py @@ -24,9 +24,9 @@ from tensorwaves.function import ParametrizedBackendFunction from tensorwaves.interface import ( - Array, DataSample, FitResult, + FloatArray, Function, ParameterValue, ParametrizedFunction, @@ -73,7 +73,7 @@ def create_function( def generate_data( model: HelicityModel, - function: Function[DataSample, Array], + function: Function[DataSample, FloatArray], data_sample_size: int, phsp_sample_size: int, backend: str, @@ -110,7 +110,7 @@ def generate_data( def fit( data: DataSample, phsp: DataSample, - function: ParametrizedFunction[DataSample, Array], + function: ParametrizedFunction[DataSample, FloatArray], initial_parameters: Mapping[str, ParameterValue], backend: str, ) -> FitResult: diff --git a/src/tensorwaves/data/__init__.py b/src/tensorwaves/data/__init__.py index 8983fc03..54ec2178 100644 --- a/src/tensorwaves/data/__init__.py +++ b/src/tensorwaves/data/__init__.py @@ -8,10 +8,10 @@ from tqdm.auto import tqdm from tensorwaves.interface import ( - Array, DataGenerator, DataSample, DataTransformer, + FloatArray, Function, RealNumberGenerator, ) @@ -70,7 +70,7 @@ class IntensityDistributionGenerator(DataGenerator): def __init__( self, domain_generator: DataGenerator, - function: Function[DataSample, Array], + function: Function[DataSample, FloatArray], domain_transformer: DataTransformer | None = None, bunch_size: int = 50_000, ) -> None: @@ -118,7 +118,7 @@ def _generate_bunch(self, rng: RealNumberGenerator) -> tuple[DataSample, float]: ) transformed_domain = self.__domain_transformer(domain) computed_intensities = self.__function(transformed_domain) - max_intensity: float = np.max(computed_intensities) + max_intensity = float(np.max(computed_intensities)) random_intensities = rng(size=self.__bunch_size, max_value=max_intensity) weights = domain.get("weights", 1) hit_and_miss_sample = select_events( diff --git a/src/tensorwaves/estimator.py b/src/tensorwaves/estimator.py index b127d3c8..0a10ba9a 100644 --- a/src/tensorwaves/estimator.py +++ b/src/tensorwaves/estimator.py @@ -214,7 +214,7 @@ class ChiSquared(Estimator): def __init__( self, - function: ParametrizedFunction[DataSample, Array], + function: ParametrizedFunction[DataSample, FloatArray], domain: DataSample, observed_values: Array, weights: Array | None = None, @@ -311,7 +311,7 @@ class UnbinnedNLL(Estimator): def __init__( self, - function: ParametrizedFunction[DataSample, Array], + function: ParametrizedFunction[DataSample, FloatArray], data: DataSample, phsp: DataSample, phsp_volume: float = 1.0, From 0add2733f71611d4f01158044626e05977f23e3c Mon Sep 17 00:00:00 2001 From: Remco de Boer <29308176+redeboer@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:07:03 +0200 Subject: [PATCH 8/8] MAINT: remove redundant estimator value casts --- src/tensorwaves/optimizer/minuit.py | 2 +- src/tensorwaves/optimizer/scipy.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tensorwaves/optimizer/minuit.py b/src/tensorwaves/optimizer/minuit.py index 41a27c33..9bb01cd8 100644 --- a/src/tensorwaves/optimizer/minuit.py +++ b/src/tensorwaves/optimizer/minuit.py @@ -70,7 +70,7 @@ def optimize( logs=_create_log( optimizer=type(self), estimator_type=type(estimator), - estimator_value=float(estimator(parameters)), + estimator_value=estimator(parameters), function_call=n_function_calls, parameters=parameters, ) diff --git a/src/tensorwaves/optimizer/scipy.py b/src/tensorwaves/optimizer/scipy.py index a546f536..7e033dfd 100644 --- a/src/tensorwaves/optimizer/scipy.py +++ b/src/tensorwaves/optimizer/scipy.py @@ -62,7 +62,7 @@ def optimize( # ruff:ignore[complex-structure] logs=_create_log( optimizer=type(self), estimator_type=type(estimator), - estimator_value=float(estimator(parameters)), + estimator_value=estimator(parameters), function_call=n_function_calls, parameters=parameters, ) @@ -91,7 +91,7 @@ def wrapped_function(pars: list) -> float: logs=_create_log( optimizer=type(self), estimator_type=type(estimator), - estimator_value=float(estimator(parameters)), + estimator_value=estimator_value, function_call=n_function_calls, parameters=parameters, ), @@ -113,7 +113,7 @@ def wrapped_callback(pars: Iterable[float]) -> None: logs=_create_log( optimizer=type(self), estimator_type=type(estimator), - estimator_value=float(estimator_value), + estimator_value=estimator_value, function_call=n_function_calls, parameters=create_parameter_dict(pars), ),