Skip to content

Commit bcb4cad

Browse files
author
TheodorDM
committed
feat(families): add uniform distribution family
- Implement Uniform distribution family with multiple parameterizations: * standard (a, b) - base parametrization with constraint a < b * meanWidth (m, r) - center-radius parametrization * minRange (minimum, range) - [minimum, minimum+range] parametrization - Add all analytical characteristics: PDF, CDF, PPF, characteristic function, moments - Implement proper support calculation for uniform distribution [a, b] - Register family in ParametricFamilyRegister - Families' naming style changed from 'typefamily family' to 'typefamily'
1 parent c1dc0ed commit bcb4cad

File tree

1 file changed

+331
-2
lines changed

1 file changed

+331
-2
lines changed

src/pysatl_core/families/configuration.py

Lines changed: 331 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
This module defines and configures parametric distribution families for the PySATL library:
66
77
- :class:`Normal Family` — Gaussian distribution with multiple parameterizations.
8+
- :class:`Uniform Family` — Gaussian distribution with multiple parameterizations.
89
910
Notes
1011
-----
@@ -69,6 +70,7 @@ def configure_families_register() -> ParametricFamilyRegister:
6970
The global registry of parametric families.
7071
"""
7172
_configure_normal_family()
73+
_configure_uniform_family()
7274
return ParametricFamilyRegister()
7375

7476

@@ -323,7 +325,7 @@ def skew_func(_1: Parametrization, _2: Any) -> int:
323325
return 0
324326

325327
def kurt_func(_1: Parametrization, _2: Any, excess: bool = False) -> int:
326-
"""Raw or excess kurtosis of normal distribution (always 3).
328+
"""Raw or excess kurtosis of normal distribution.
327329
328330
Parameters
329331
----------
@@ -344,10 +346,11 @@ def kurt_func(_1: Parametrization, _2: Any, excess: bool = False) -> int:
344346
return 0
345347

346348
def _normal_support(_: Parametrization) -> ContinuousSupport:
349+
"""Support of normal distribution"""
347350
return ContinuousSupport()
348351

349352
Normal = ParametricFamily(
350-
name="Normal Family",
353+
name="Normal",
351354
distr_type=UnivariateContinuous,
352355
distr_parametrizations=["meanStd", "meanPrec", "exponential"],
353356
distr_characteristics={
@@ -372,6 +375,332 @@ def _normal_support(_: Parametrization) -> ContinuousSupport:
372375
ParametricFamilyRegister.register(Normal)
373376

374377

378+
@dataclass
379+
class UniformStandardParametrization(Parametrization):
380+
"""
381+
Standard parametrization of uniform distribution.
382+
383+
Parameters
384+
----------
385+
lower_bound : float
386+
Lower bound of the distribution
387+
upper_bound : float
388+
Upper bound of the distribution
389+
"""
390+
391+
lower_bound: float
392+
upper_bound: float
393+
394+
@constraint(description="lower_bound < upper_bound")
395+
def check_lower_less_than_upper(self) -> bool:
396+
"""Check that lower bound is less than upper bound."""
397+
return self.lower_bound < self.upper_bound
398+
399+
400+
@dataclass
401+
class UniformMeanWidthParametrization(Parametrization):
402+
"""
403+
Mean-width parametrization of uniform distribution.
404+
405+
Parameters
406+
----------
407+
mean : float
408+
Mean (center) of the distribution
409+
width : float
410+
Width of the distribution (upper_bound - lower_bound)
411+
"""
412+
413+
mean: float
414+
width: float
415+
416+
@constraint(description="width > 0")
417+
def check_width_positive(self) -> bool:
418+
"""Check that width is positive."""
419+
return self.width > 0
420+
421+
def transform_to_base_parametrization(self) -> Parametrization:
422+
"""
423+
Transform to Standard parametrization.
424+
425+
Returns
426+
-------
427+
Parametrization
428+
Standard parametrization instance
429+
"""
430+
half_width = self.width / 2
431+
return UniformStandardParametrization(
432+
lower_bound=self.mean - half_width, upper_bound=self.mean + half_width
433+
)
434+
435+
436+
@dataclass
437+
class UniformMinRangeParametrization(Parametrization):
438+
"""
439+
Minimum-range parametrization of uniform distribution.
440+
441+
Parameters
442+
----------
443+
minimum : float
444+
Minimum value (lower bound)
445+
range_val : float
446+
Range of the distribution (upper_bound - lower_bound)
447+
"""
448+
449+
minimum: float
450+
range_val: float
451+
452+
@constraint(description="range_val > 0")
453+
def check_range_positive(self) -> bool:
454+
"""Check that range is positive."""
455+
return self.range_val > 0
456+
457+
def transform_to_base_parametrization(self) -> Parametrization:
458+
"""
459+
Transform to Standard parametrization.
460+
461+
Returns
462+
-------
463+
Parametrization
464+
Standard parametrization instance
465+
"""
466+
return UniformStandardParametrization(
467+
lower_bound=self.minimum, upper_bound=self.minimum + self.range_val
468+
)
469+
470+
471+
def _configure_uniform_family() -> None:
472+
UNIFORM_DOC = """
473+
Uniform (continuous) distribution.
474+
475+
The uniform distribution is a continuous probability distribution where
476+
all intervals of the same length are equally probable. It is defined by
477+
two parameters: lower bound and upper bound.
478+
479+
Probability density function:
480+
f(x) = 1/(upper_bound - lower_bound) for x in [lower_bound, upper_bound], 0 otherwise
481+
482+
The uniform distribution is often used when there is no prior knowledge
483+
about the possible values of a variable, representing maximum uncertainty.
484+
"""
485+
486+
def uniform_pdf(
487+
parameters: Parametrization, x: npt.NDArray[np.float64]
488+
) -> npt.NDArray[np.float64]:
489+
"""
490+
Probability density function for uniform distribution.
491+
Uses np.clip for vectorized computation:
492+
- For x < lower_bound: returns 0
493+
- For x > upper_bound: returns 1
494+
- Otherwise: returns (x - lower_bound) / (upper_bound - lower_bound)
495+
496+
Parameters
497+
----------
498+
parameters : Parametrization
499+
Distribution parameters object with fields:
500+
- lower_bound: float (lower bound)
501+
- upper_bound: float (upper bound)
502+
x : npt.NDArray[np.float64]
503+
Points at which to evaluate the probability density function
504+
505+
Returns
506+
-------
507+
npt.NDArray[np.float64]
508+
Probability density values at points x
509+
"""
510+
parameters = cast(UniformStandardParametrization, parameters)
511+
512+
lower_bound = parameters.lower_bound
513+
upper_bound = parameters.upper_bound
514+
515+
return np.where(
516+
(x >= lower_bound) & (x <= upper_bound), 1.0 / (upper_bound - lower_bound), 0.0
517+
)
518+
519+
def uniform_cdf(
520+
parameters: Parametrization, x: npt.NDArray[np.float64]
521+
) -> npt.NDArray[np.float64]:
522+
"""
523+
Cumulative distribution function for uniform distribution.
524+
Uses np.clip for vectorized computation:
525+
- For x < lower_bound: returns 0
526+
- For x > upper_bound: returns 1
527+
528+
Parameters
529+
----------
530+
parameters : Parametrization
531+
Distribution parameters object with fields:
532+
- lower_bound: float (lower bound)
533+
- upper_bound: float (upper bound)
534+
x : npt.NDArray[np.float64]
535+
Points at which to evaluate the cumulative distribution function
536+
537+
Returns
538+
-------
539+
npt.NDArray[np.float64]
540+
Probabilities P(X ≤ x) for each point x
541+
"""
542+
parameters = cast(UniformStandardParametrization, parameters)
543+
544+
lower_bound = parameters.lower_bound
545+
upper_bound = parameters.upper_bound
546+
547+
return np.clip((x - lower_bound) / (upper_bound - lower_bound), 0.0, 1.0)
548+
549+
def uniform_ppf(
550+
parameters: Parametrization, p: npt.NDArray[np.float64]
551+
) -> npt.NDArray[np.float64]:
552+
"""
553+
Percent point function (inverse CDF) for uniform distribution.
554+
555+
For uniform distribution on [lower_bound, upper_bound]:
556+
- For p = 0: returns lower_bound
557+
- For p = 1: returns upper_bound
558+
- For p in (0, 1): returns lower_bound + p × (upper_bound - lower_bound)
559+
560+
Parameters
561+
----------
562+
parameters : Parametrization
563+
Distribution parameters object with fields:
564+
- lower_bound: float (lower bound)
565+
- upper_bound: float (upper bound)
566+
p : npt.NDArray[np.float64]
567+
Probability from [0, 1]
568+
569+
Returns
570+
-------
571+
npt.NDArray[np.float64]
572+
Quantiles corresponding to probabilities p
573+
574+
Raises
575+
------
576+
ValueError
577+
If probability is outside [0, 1]
578+
"""
579+
if np.any((p < 0) | (p > 1)):
580+
raise ValueError("Probability must be in [0, 1]")
581+
582+
parameters = cast(UniformStandardParametrization, parameters)
583+
lower_bound = parameters.lower_bound
584+
upper_bound = parameters.upper_bound
585+
586+
return cast(npt.NDArray[np.float64], lower_bound + p * (upper_bound - lower_bound))
587+
588+
def uniform_char_func(
589+
parameters: Parametrization, t: npt.NDArray[np.float64]
590+
) -> npt.NDArray[np.complex128]:
591+
"""
592+
Characteristic function of uniform distribution.
593+
594+
Characteristic function formula for uniform distribution on [lower_bound, upper bound]:
595+
φ(t) = sinc((upper bound - lower_bound) * t / 2) *
596+
* exp(i * (lower_bound + upper bound) * t / 2)
597+
where sinc(x) = sin(πx)/(πx) as defined by numpy.
598+
599+
Parameters
600+
----------
601+
parameters : Parametrization
602+
Distribution parameters object with fields:
603+
- lower_bound: float (lower bound)
604+
- upper_bound: float (upper bound)
605+
t : npt.NDArray[np.float64]
606+
Points at which to evaluate the characteristic function
607+
608+
Returns
609+
-------
610+
npt.NDArray[np.complex128]
611+
Characteristic function values at points t
612+
"""
613+
parameters = cast(UniformStandardParametrization, parameters)
614+
615+
lower_bound = parameters.lower_bound
616+
upper_bound = parameters.upper_bound
617+
618+
width = upper_bound - lower_bound
619+
center = (lower_bound + upper_bound) / 2
620+
621+
t_arr = np.asarray(t, dtype=np.float64)
622+
623+
x = width * t_arr / (2 * np.pi)
624+
sinc_val = np.sinc(x)
625+
626+
return cast(npt.NDArray[np.complex128], sinc_val * np.exp(1j * center * t_arr))
627+
628+
def mean_func(parameters: Parametrization, _: Any) -> float:
629+
"""Mean of uniform distribution."""
630+
parameters = cast(UniformStandardParametrization, parameters)
631+
return (parameters.lower_bound + parameters.upper_bound) / 2
632+
633+
def var_func(parameters: Parametrization, _: Any) -> float:
634+
"""Variance of uniform distribution."""
635+
parameters = cast(UniformStandardParametrization, parameters)
636+
width = parameters.upper_bound - parameters.lower_bound
637+
return width**2 / 12
638+
639+
def skew_func(_1: Parametrization, _2: Any) -> int:
640+
"""Skewness of uniform distribution (always 0)."""
641+
return 0
642+
643+
def kurt_func(_1: Parametrization, _2: Any, excess: bool = False) -> float:
644+
"""Raw or excess kurtosis of uniform distribution.
645+
646+
Parameters
647+
----------
648+
_1 : Parametrization
649+
Needed by architecture parameter
650+
_2 : Any
651+
Needed by architecture parameter
652+
excess : bool
653+
A value defines if there will be raw or excess kurtosis
654+
default is False
655+
656+
Returns
657+
-------
658+
float
659+
Kurtosis value
660+
"""
661+
if not excess:
662+
return 1.8
663+
else:
664+
return -1.2
665+
666+
def _uniform_support(parameters: Parametrization) -> ContinuousSupport:
667+
"""Support of uniform distribution"""
668+
parameters = cast(
669+
UniformStandardParametrization, parameters.transform_to_base_parametrization()
670+
)
671+
return ContinuousSupport(
672+
left=parameters.lower_bound,
673+
right=parameters.upper_bound,
674+
left_closed=True,
675+
right_closed=True,
676+
)
677+
678+
Uniform = ParametricFamily(
679+
name="ContinuousUniform",
680+
distr_type=UnivariateContinuous,
681+
distr_parametrizations=["standard", "meanWidth", "minRange"],
682+
distr_characteristics={
683+
PDF: uniform_pdf,
684+
CDF: uniform_cdf,
685+
PPF: uniform_ppf,
686+
CF: uniform_char_func,
687+
MEAN: mean_func,
688+
VAR: var_func,
689+
SKEW: skew_func,
690+
KURT: kurt_func,
691+
},
692+
sampling_strategy=DefaultSamplingUnivariateStrategy(),
693+
support_by_parametrization=_uniform_support,
694+
)
695+
Uniform.__doc__ = UNIFORM_DOC
696+
697+
parametrization(family=Uniform, name="standard")(UniformStandardParametrization)
698+
parametrization(family=Uniform, name="meanWidth")(UniformMeanWidthParametrization)
699+
parametrization(family=Uniform, name="minRange")(UniformMinRangeParametrization)
700+
701+
ParametricFamilyRegister.register(Uniform)
702+
703+
375704
def reset_families_register() -> None:
376705
configure_families_register.cache_clear()
377706
ParametricFamilyRegister._reset()

0 commit comments

Comments
 (0)