Skip to content

Commit f988423

Browse files
committed
refactor (computations): add computation and characteristic options
1 parent c308035 commit f988423

6 files changed

Lines changed: 637 additions & 151 deletions

File tree

src/pysatl_core/distributions/computations/base.py

Lines changed: 224 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,50 @@
11
"""
22
Computation descriptor abstractions.
33
4-
Provides ``ComputationOption``, ``FitterDescriptor``, and ``EvaluatorDescriptor``
5-
for declaring metadata about fitters and evaluators.
4+
Provides ``CharacteristicOption``, ``ComputationOption``,
5+
``FitterDescriptor``, and ``EvaluatorDescriptor`` for declaring metadata
6+
about fitters and evaluators.
7+
8+
Option taxonomy
9+
---------------
10+
``CharacteristicOption``
11+
Describes a parameter that is *intrinsic to the characteristic itself*
12+
(e.g. ``eps`` or ``x0`` for PPF). These options are shared between the
13+
fitter (pre-computation / caching path) and the evaluator (direct-query
14+
path). Because they affect the *meaning* of the result they must be
15+
encoded into the cache key.
16+
17+
``ComputationOption``
18+
Describes a parameter that controls the *numerical algorithm* used to
19+
compute the characteristic (e.g. ``max_iter``, ``x_tol``). These are
20+
specific to a particular fitter implementation and do **not** affect the
21+
evaluator.
22+
23+
Passing options — ``TypedDict`` + ``Unpack`` pattern
24+
-----------------------------------------------------
25+
Concrete fitters and evaluators declare their accepted keyword arguments via
26+
``TypedDict`` subclasses and annotate their signatures with
27+
``**kwargs: Unpack[MyOptionsDict]``. This gives static type-checkers full
28+
visibility while keeping the runtime interface simple ``**kwargs``.
29+
30+
Example::
31+
32+
from typing import TypedDict
33+
from typing_extensions import Unpack
34+
35+
class PpfCharacteristicOptions(TypedDict, total=False):
36+
eps: float
37+
x0: float
38+
39+
class PpfComputationOptions(TypedDict, total=False):
40+
max_iter: int
41+
x_tol: float
42+
43+
def fit_cdf_to_ppf(
44+
distribution: Distribution,
45+
/,
46+
**kwargs: Unpack[PpfComputationOptions],
47+
) -> FittedComputationMethod: ...
648
"""
749

850
from __future__ import annotations
@@ -29,9 +71,9 @@
2971

3072

3173
@dataclass(frozen=True, slots=True)
32-
class ComputationOption:
74+
class _BaseOption:
3375
"""
34-
Structured descriptor for a single computation option.
76+
Common base for option descriptors.
3577
3678
Attributes
3779
----------
@@ -88,6 +130,56 @@ def resolve(self, kwargs: dict[str, Any]) -> Any:
88130
return value
89131

90132

133+
@dataclass(frozen=True, slots=True)
134+
class CharacteristicOption(_BaseOption):
135+
"""
136+
Option that is *intrinsic to the characteristic* being computed.
137+
138+
Characteristic options are shared between the fitter (pre-computation /
139+
caching path) and the evaluator (direct-query path). Because they affect
140+
the *meaning* of the result they must be encoded into the cache key.
141+
142+
Examples: ``eps`` (tail threshold for PPF), ``x0`` (starting point for
143+
bound search), ``excess`` (excess kurtosis flag).
144+
145+
These options are declared in ``FitterDescriptor.characteristic_options``
146+
and ``EvaluatorDescriptor.characteristic_options``.
147+
"""
148+
149+
150+
@dataclass(frozen=True, slots=True)
151+
class ComputationOption(_BaseOption):
152+
"""
153+
Option that controls the *numerical algorithm* used to compute a
154+
characteristic.
155+
156+
Computation options are specific to a particular fitter implementation
157+
and do **not** affect the evaluator. They influence only the speed /
158+
accuracy trade-off of the fitting step, not the semantics of the result.
159+
160+
Examples: ``max_iter`` (bisection iterations), ``x_tol`` (bracket
161+
tolerance), ``limit`` (quad subdivisions), ``n_q_grid`` (PPF grid size).
162+
163+
These options are declared in ``FitterDescriptor.computation_options``.
164+
"""
165+
166+
167+
def _resolve_options(
168+
options: tuple[_BaseOption, ...],
169+
kwargs: dict[str, Any],
170+
) -> dict[str, Any]:
171+
"""Resolve a tuple of option descriptors from *kwargs* (mutates *kwargs*)."""
172+
return {opt.name: opt.resolve(kwargs) for opt in options}
173+
174+
175+
def _option_names(options: tuple[_BaseOption, ...]) -> tuple[str, ...]:
176+
return tuple(opt.name for opt in options)
177+
178+
179+
def _option_defaults(options: tuple[_BaseOption, ...]) -> dict[str, Any]:
180+
return {opt.name: opt.default for opt in options}
181+
182+
91183
@dataclass(frozen=True, slots=True)
92184
class FitterDescriptor:
93185
"""
@@ -106,25 +198,39 @@ class FitterDescriptor:
106198
Characteristics consumed by this fitter (typically length 1).
107199
fitter : FitterFunc
108200
The actual fitting callable.
109-
options : tuple[ComputationOption, ...]
110-
Structured option schema.
201+
characteristic_options : tuple[CharacteristicOption, ...]
202+
Options intrinsic to the characteristic (shared with evaluators,
203+
encoded into the cache key).
204+
computation_options : tuple[ComputationOption, ...]
205+
Options controlling the numerical algorithm (fitter-specific).
111206
constraint_tags : frozenset[str]
112207
Constraint tags used for matching (e.g. ``{"continuous", "univariate"}``).
113208
description : str
114209
Human-readable summary of what the fitter does.
210+
211+
Notes
212+
-----
213+
The combined ``options`` property returns all options (characteristic
214+
first, then computation) for backwards-compatible resolution.
115215
"""
116216

117217
name: str
118218
target: GenericCharacteristicName
119219
sources: Sequence[GenericCharacteristicName]
120220
fitter: FitterFunc
121-
options: tuple[ComputationOption, ...] = ()
221+
characteristic_options: tuple[CharacteristicOption, ...] = ()
222+
computation_options: tuple[ComputationOption, ...] = ()
122223
constraint_tags: frozenset[str] = field(default_factory=frozenset)
123224
description: str = ""
124225

226+
@property
227+
def options(self) -> tuple[_BaseOption, ...]:
228+
"""All options (characteristic first, then computation)."""
229+
return (*self.characteristic_options, *self.computation_options)
230+
125231
def to_computation_method(self) -> FitterMethod:
126232
"""
127-
Build a :class:`FitterMethod` (computation method) from this descriptor.
233+
Build a `FitterMethod` (computation method) from this descriptor.
128234
129235
Returns
130236
-------
@@ -138,9 +244,43 @@ def to_computation_method(self) -> FitterMethod:
138244
fitter=self.fitter,
139245
)
140246

247+
def resolve_characteristic_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
248+
"""
249+
Resolve only the *characteristic* options from *kwargs*.
250+
251+
Consumes recognised keys from *kwargs* and returns a dict of
252+
``{option_name: resolved_value}``. Unrecognised keys are left
253+
in *kwargs* untouched.
254+
255+
Parameters
256+
----------
257+
kwargs : dict[str, Any]
258+
Mutable keyword-argument dict from the caller.
259+
260+
Returns
261+
-------
262+
dict[str, Any]
263+
"""
264+
return _resolve_options(self.characteristic_options, kwargs)
265+
266+
def resolve_computation_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
267+
"""
268+
Resolve only the *computation* options from *kwargs*.
269+
270+
Parameters
271+
----------
272+
kwargs : dict[str, Any]
273+
Mutable keyword-argument dict from the caller.
274+
275+
Returns
276+
-------
277+
dict[str, Any]
278+
"""
279+
return _resolve_options(self.computation_options, kwargs)
280+
141281
def resolve_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
142282
"""
143-
Resolve all declared options from *kwargs*.
283+
Resolve *all* declared options (characteristic + computation) from *kwargs*.
144284
145285
Consumes recognised keys from *kwargs* and returns a dict of
146286
``{option_name: resolved_value}``. Unrecognised keys are left
@@ -156,15 +296,23 @@ def resolve_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
156296
dict[str, Any]
157297
Mapping from option name to resolved (validated, typed) value.
158298
"""
159-
return {opt.name: opt.resolve(kwargs) for opt in self.options}
299+
return _resolve_options(self.options, kwargs)
160300

161301
def option_names(self) -> tuple[str, ...]:
162-
"""Return the names of all declared options."""
163-
return tuple(opt.name for opt in self.options)
302+
"""Return the names of all declared options (characteristic + computation)."""
303+
return _option_names(self.options)
164304

165305
def option_defaults(self) -> dict[str, Any]:
166306
"""Return ``{name: default}`` for every declared option."""
167-
return {opt.name: opt.default for opt in self.options}
307+
return _option_defaults(self.options)
308+
309+
def characteristic_option_names(self) -> tuple[str, ...]:
310+
"""Return the names of characteristic options only."""
311+
return _option_names(self.characteristic_options)
312+
313+
def computation_option_names(self) -> tuple[str, ...]:
314+
"""Return the names of computation options only."""
315+
return _option_names(self.computation_options)
168316

169317

170318
@dataclass(frozen=True, slots=True)
@@ -186,8 +334,14 @@ class EvaluatorDescriptor:
186334
Characteristics consumed by this evaluator (typically length 1).
187335
evaluator : EvaluatorFunc
188336
The actual evaluator callable.
189-
options : tuple[ComputationOption, ...]
190-
Structured option schema.
337+
characteristic_options : tuple[CharacteristicOption, ...]
338+
Options intrinsic to the characteristic (shared with fitters).
339+
These affect the *meaning* of the result.
340+
computation_options : tuple[ComputationOption, ...]
341+
Options controlling the numerical algorithm used **on every call**.
342+
Unlike fitter computation options (used once at fit-time), evaluator
343+
computation options are applied on each invocation. Examples:
344+
integration tolerance, finite-difference step, iteration limit.
191345
constraint_tags : frozenset[str]
192346
Constraint tags used for matching.
193347
description : str
@@ -198,13 +352,19 @@ class EvaluatorDescriptor:
198352
target: GenericCharacteristicName
199353
sources: Sequence[GenericCharacteristicName]
200354
evaluator: EvaluatorFunc
201-
options: tuple[ComputationOption, ...] = ()
355+
characteristic_options: tuple[CharacteristicOption, ...] = ()
356+
computation_options: tuple[ComputationOption, ...] = ()
202357
constraint_tags: frozenset[str] = field(default_factory=frozenset)
203358
description: str = ""
204359

360+
@property
361+
def options(self) -> tuple[_BaseOption, ...]:
362+
"""All options (characteristic first, then computation)."""
363+
return (*self.characteristic_options, *self.computation_options)
364+
205365
def to_computation_method(self) -> EvaluatorMethod:
206366
"""
207-
Build an :class:`EvaluatorMethod` (computation method) from this descriptor.
367+
Build an `EvaluatorMethod` (computation method) from this descriptor.
208368
209369
Returns
210370
-------
@@ -218,13 +378,42 @@ def to_computation_method(self) -> EvaluatorMethod:
218378
evaluator=self.evaluator,
219379
)
220380

221-
def resolve_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
381+
def resolve_characteristic_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
222382
"""
223-
Resolve all declared options from *kwargs*.
383+
Resolve only the *characteristic* options from *kwargs*.
224384
225-
Consumes recognised keys from *kwargs* and returns a dict of
226-
``{option_name: resolved_value}``. Unrecognised keys are left
227-
in *kwargs* untouched.
385+
Parameters
386+
----------
387+
kwargs : dict[str, Any]
388+
Mutable keyword-argument dict from the caller.
389+
390+
Returns
391+
-------
392+
dict[str, Any]
393+
"""
394+
return _resolve_options(self.characteristic_options, kwargs)
395+
396+
def resolve_computation_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
397+
"""
398+
Resolve only the *computation* options from *kwargs*.
399+
400+
For evaluators these are applied on **every call** (not just at
401+
fit-time as for fitters).
402+
403+
Parameters
404+
----------
405+
kwargs : dict[str, Any]
406+
Mutable keyword-argument dict from the caller.
407+
408+
Returns
409+
-------
410+
dict[str, Any]
411+
"""
412+
return _resolve_options(self.computation_options, kwargs)
413+
414+
def resolve_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
415+
"""
416+
Resolve *all* declared options (characteristic + computation) from *kwargs*.
228417
229418
Parameters
230419
----------
@@ -236,18 +425,27 @@ def resolve_options(self, kwargs: dict[str, Any]) -> dict[str, Any]:
236425
dict[str, Any]
237426
Mapping from option name to resolved (validated, typed) value.
238427
"""
239-
return {opt.name: opt.resolve(kwargs) for opt in self.options}
428+
return _resolve_options(self.options, kwargs)
240429

241430
def option_names(self) -> tuple[str, ...]:
242-
"""Return the names of all declared options."""
243-
return tuple(opt.name for opt in self.options)
431+
"""Return the names of all declared options (characteristic + computation)."""
432+
return _option_names(self.options)
244433

245434
def option_defaults(self) -> dict[str, Any]:
246435
"""Return ``{name: default}`` for every declared option."""
247-
return {opt.name: opt.default for opt in self.options}
436+
return _option_defaults(self.options)
437+
438+
def characteristic_option_names(self) -> tuple[str, ...]:
439+
"""Return the names of characteristic options only."""
440+
return _option_names(self.characteristic_options)
441+
442+
def computation_option_names(self) -> tuple[str, ...]:
443+
"""Return the names of computation options only."""
444+
return _option_names(self.computation_options)
248445

249446

250447
__all__ = [
448+
"CharacteristicOption",
251449
"ComputationOption",
252450
"FitterDescriptor",
253451
"EvaluatorDescriptor",

0 commit comments

Comments
 (0)