1919
2020from pysatl_core .distributions .computation import AnalyticalComputation
2121from pysatl_core .families .distribution import ParametricFamilyDistribution
22- from pysatl_core .types import ComputationFunc , DistributionType
22+ from pysatl_core .types import (
23+ DEFAULT_ANALYTICAL_COMPUTATION_LABEL ,
24+ ComputationFunc ,
25+ DistributionType ,
26+ )
2327
2428if TYPE_CHECKING :
2529 from collections .abc import Callable
3135 )
3236 from pysatl_core .types import (
3337 GenericCharacteristicName ,
38+ LabelName ,
3439 ParametrizationName ,
3540 )
3641
3742 type SupportArg = Callable [[Parametrization ], Support | None ] | None
3843 type SupportResolver = Callable [[Parametrization ], Support | None ]
44+ type LabeledCharacteristicProvider = (
45+ Mapping [LabelName , ParametricFamilyCharacteristic [Any , Any ]]
46+ | ParametricFamilyCharacteristic [Any , Any ]
47+ )
3948 type CharacteristicProvider = (
40- Mapping [ParametrizationName , CharacteristicFunction [ Any , Any ] ]
41- | CharacteristicFunction [Any , Any ]
49+ Mapping [ParametrizationName , LabeledCharacteristicProvider ]
50+ | ParametricFamilyCharacteristic [Any , Any ]
4251 )
4352 type CharacteristicsMap = Mapping [GenericCharacteristicName , CharacteristicProvider ]
4453 type NonParametrizedCharacteristic [In , Out ] = Callable [[], Out ]
45- type CharacteristicFunction [In , Out ] = (
54+ type ParametricFamilyCharacteristic [In , Out ] = (
4655 NonParametrizedCharacteristic [In , Out ] | ParametrizedCharacteristic [In , Out ]
4756 )
4857
@@ -78,7 +87,9 @@ class ParametricFamily:
7887 - nullary characteristics (e.g., mean, var): provider(params, **kwargs) -> Any
7988 - pointwise characteristics (e.g., pdf, cdf, ppf): provider(params, x, **kwargs) -> Any
8089
81- If a single callable is provided, it is treated as defined for the base parametrization.
90+ Providers are grouped by parametrization and may define multiple labeled methods.
91+ If a single callable is provided, it is treated as the base-parametrization method
92+ under ``DEFAULT_ANALYTICAL_COMPUTATION_LABEL``.
8293 support_by_parametrization : Callable or None, optional
8394 Function that returns support for given parameters.
8495 """
@@ -109,31 +120,63 @@ def __init__(
109120 # Runtime registry of parametrization classes
110121 self ._parametrizations : dict [ParametrizationName , type [Parametrization ]] = {}
111122
112- def _normalize_characteristic (
113- value : Mapping [ParametrizationName , CharacteristicFunction [Any , Any ]]
114- | CharacteristicFunction [Any , Any ],
115- ) -> dict [ParametrizationName , CharacteristicFunction [Any , Any ]]:
116- return (
117- dict (value )
118- if isinstance (value , Mapping )
119- else {self .base_parametrization_name : value }
123+ def _normalize_labeled_provider (
124+ characteristic_name : GenericCharacteristicName ,
125+ parametrization_name : ParametrizationName ,
126+ provider : LabeledCharacteristicProvider ,
127+ ) -> dict [LabelName , ParametricFamilyCharacteristic [Any , Any ]]:
128+ normalized = (
129+ dict (provider )
130+ if isinstance (provider , Mapping )
131+ else {DEFAULT_ANALYTICAL_COMPUTATION_LABEL : provider }
120132 )
133+ if not normalized :
134+ raise ValueError (
135+ f"Characteristic '{ characteristic_name } ' has no labeled providers for "
136+ f"parametrization '{ parametrization_name } '."
137+ )
138+ return normalized
139+
140+ def _normalize_characteristic (
141+ characteristic_name : GenericCharacteristicName ,
142+ value : CharacteristicProvider ,
143+ ) -> dict [ParametrizationName , dict [LabelName , ParametricFamilyCharacteristic [Any , Any ]]]:
144+ if not isinstance (value , Mapping ):
145+ base_name = self .base_parametrization_name
146+ return {
147+ base_name : _normalize_labeled_provider (characteristic_name , base_name , value )
148+ }
149+
150+ normalized_by_parametrization : dict [
151+ ParametrizationName , dict [LabelName , ParametricFamilyCharacteristic [Any , Any ]]
152+ ] = {}
153+ for parametrization_name , provider in value .items ():
154+ normalized_by_parametrization [parametrization_name ] = _normalize_labeled_provider (
155+ characteristic_name ,
156+ parametrization_name ,
157+ provider ,
158+ )
159+ return normalized_by_parametrization
121160
122161 self .distr_characteristics : dict [
123- GenericCharacteristicName , dict [ParametrizationName , CharacteristicFunction [Any , Any ]]
124- ] = {k : _normalize_characteristic (v ) for k , v in distr_characteristics .items ()}
162+ GenericCharacteristicName ,
163+ dict [ParametrizationName , dict [LabelName , ParametricFamilyCharacteristic [Any , Any ]]],
164+ ] = {
165+ characteristic_name : _normalize_characteristic (characteristic_name , provider )
166+ for characteristic_name , provider in distr_characteristics .items ()
167+ }
125168
126169 # Validate characteristic providers
127170 valid_names = set (self .parametrization_names )
128171 for char_name , forms in self .distr_characteristics .items ():
172+ if not forms :
173+ raise ValueError (f"Characteristic '{ char_name } ' has no providers." )
129174 unknown = set (forms ) - valid_names
130175 if unknown :
131176 raise ValueError (
132177 f"Characteristic '{ char_name } ' has providers for unknown parametrizations: "
133178 f"{ sorted (unknown )} ."
134179 )
135- if self .base_parametrization_name not in forms and len (forms ) == 0 :
136- raise ValueError (f"Characteristic '{ char_name } ' has no providers." )
137180
138181 # Precompute analytical plan: for each parametrization pick provider (self or base)
139182 self ._analytical_plan : dict [
@@ -236,7 +279,7 @@ def to_base(self, parameters: Parametrization) -> Parametrization:
236279
237280 @staticmethod
238281 def _bind_parametrization [In , Out ](
239- func : CharacteristicFunction [In , Out ], params_obj : Parametrization
282+ func : ParametricFamilyCharacteristic [In , Out ], params_obj : Parametrization
240283 ) -> ComputationFunc [In , Out ]:
241284 """Bind ``params_obj`` to ``func`` only when ``func`` can accept positional arguments.
242285
@@ -265,14 +308,16 @@ def _bind_parametrization[In, Out](
265308
266309 def _build_analytical_computations (
267310 self , parameters : Parametrization
268- ) -> dict [GenericCharacteristicName , AnalyticalComputation [Any , Any ]]:
311+ ) -> dict [GenericCharacteristicName , dict [ LabelName , AnalyticalComputation [Any , Any ] ]]:
269312 """
270313 Build analytical computations for given parameters.
271314
272315 Uses precomputed provider plan for efficient computation.
273316 """
274317 plan = self ._analytical_plan .get (parameters .name , {})
275- result : dict [GenericCharacteristicName , AnalyticalComputation [Any , Any ]] = {}
318+ result : dict [
319+ GenericCharacteristicName , dict [LabelName , AnalyticalComputation [Any , Any ]]
320+ ] = {}
276321 base_params : Parametrization | None = None
277322
278323 for characteristic , provider_name in plan .items ():
@@ -282,11 +327,14 @@ def _build_analytical_computations(
282327 base_params = base_params or self .to_base (parameters )
283328 params_obj = base_params
284329
285- func_factory = self .distr_characteristics [characteristic ][provider_name ]
286- result [characteristic ] = AnalyticalComputation (
287- target = characteristic ,
288- func = self ._bind_parametrization (func_factory , params_obj ),
289- )
330+ labeled_providers = self .distr_characteristics [characteristic ][provider_name ]
331+ result [characteristic ] = {
332+ label_name : AnalyticalComputation (
333+ target = characteristic ,
334+ func = self ._bind_parametrization (func_factory , params_obj ),
335+ )
336+ for label_name , func_factory in labeled_providers .items ()
337+ }
290338
291339 return result
292340
0 commit comments