Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions examples/example-parametric.ipynb

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ coverage = { version = ">=7.5", extras = [ "toml" ] }
pre-commit = ">=3.6"
types-setuptools = "*"

[tool.poetry.group.docs.dependencies]
jupyter = "^1.1.1"
matplotlib = "^3.10.6"

[tool.ruff]
target-version = "py312"
line-length = 100
Expand Down
35 changes: 35 additions & 0 deletions src/pysatl_core/families/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Parametric Families module for working with statistical distribution families.

This package provides a comprehensive framework for defining, managing, and
working with parametric families of statistical distributions. It supports
multiple parameterizations, constraint validation, and automatic conversion
between different parameter formats.
"""

__author__ = "Leonid Elkin, Mikhail, Mikhailov"
__copyright__ = "Copyright (c) 2025 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"


from pysatl_core.families.distribution import ParametricFamilyDistribution
from pysatl_core.families.parametric_family import ParametricFamily
from pysatl_core.families.parametrizations import (
Parametrization,
ParametrizationConstraint,
ParametrizationSpec,
constraint,
parametrization,
)
from pysatl_core.families.registry import ParametricFamilyRegister

__all__ = [
"ParametricFamilyRegister",
"ParametrizationConstraint",
"Parametrization",
"ParametrizationSpec",
"ParametricFamily",
"ParametricFamilyDistribution",
"constraint",
"parametrization",
]
151 changes: 151 additions & 0 deletions src/pysatl_core/families/distribution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""
Concrete distribution instances with specific parameter values.

This module provides the implementation for individual distribution instances
created from parametric families. It handles distribution characteristics
computation, sampling, and provides access to analytical methods for
specific parameter sets.
"""

from __future__ import annotations

__author__ = "Leonid Elkin, Mikhail, Mikhailov"
__copyright__ = "Copyright (c) 2025 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from collections.abc import Mapping
from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Any

from pysatl_core.distributions import (
AnalyticalComputation,
ComputationStrategy,
Sample,
SamplingStrategy,
)
from pysatl_core.families.parametrizations import Parametrization
from pysatl_core.families.registry import ParametricFamilyRegister
from pysatl_core.types import (
DistributionType,
GenericCharacteristicName,
)

if TYPE_CHECKING:
from pysatl_core.families.parametric_family import ParametricFamily


@dataclass
class ParametricFamilyDistribution:
"""
A specific distribution instance from a parametric family.

This class represents a concrete distribution with specific parameter
values, providing methods for computation and sampling.

Attributes
----------
distr_name : str
Name of the distribution family.
distribution_type : DistributionType
Type of this distribution.
parameters : Parametrization
Parameter values for this distribution.
"""

distr_name: str
distribution_type: DistributionType
parameters: Parametrization

@property
def family(self) -> ParametricFamily:
"""
Get the parametric family this distribution belongs to.

Returns
-------
ParametricFamily
The parametric family of this distribution.
"""
return ParametricFamilyRegister.get(self.distr_name)

@property
def analytical_computations(
self,
) -> Mapping[GenericCharacteristicName, AnalyticalComputation[Any, Any]]:
"""
Get analytical computation functions for this distribution.

Returns
-------
Mapping[GenericCharacteristicName, AnalyticalComputation]
Mapping from characteristic names to computation functions.
"""
analytical_computations = {}

# First form list of all characteristics, available from current parametrization
for characteristic, forms in self.family.distr_characteristics.items():
if self.parameters.name in forms:
analytical_computations[characteristic] = AnalyticalComputation(
target=characteristic,
func=partial(forms[self.parameters.name], self.parameters),
)
# TODO: Second, apply rule set, for, e.g. approximations

# Finally, fill other chacteristics
base_name = self.family.parametrizations.base_parametrization_name
base_parameters = self.family.parametrizations.get_base_parameters(self.parameters)
for characteristic, forms in self.family.distr_characteristics.items():
if characteristic in analytical_computations:
continue
if base_name in forms:
analytical_computations[characteristic] = AnalyticalComputation(
target=characteristic, func=partial(forms[base_name], base_parameters)
)

return analytical_computations

@property
def sampling_strategy(self) -> SamplingStrategy:
"""
Get the sampling strategy for this distribution.

Returns
-------
SamplingStrategy
Strategy for sampling from this distribution.
"""
return self.family.sampling_strategy

@property
def computation_strategy(self) -> ComputationStrategy[Any, Any]:
"""
Get the computation strategy for this distribution.

Returns
-------
ComputationStrategy
Strategy for computing characteristics of this distribution.
"""
return self.family.computation_strategy

def log_likelihood(self, batch: Sample) -> float:
raise NotImplementedError

def sample(self, n: int, **options: Any) -> Sample:
"""
Generate samples from this distribution.

Parameters
----------
n : int
Number of samples to generate.
**options : Any
Additional options for the sampling algorithm.

Returns
-------
Sample
The generated samples.
"""
return self.sampling_strategy.sample(n, distr=self, **options)
157 changes: 157 additions & 0 deletions src/pysatl_core/families/parametric_family.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"""
Parametric family definitions and management infrastructure.

This module contains the main class for defining parametric families of
distributions, including support for multiple parameterizations, distribution
characteristics, sampling strategies, and computation methods. It serves as
the central definition point for statistical distribution families.
"""

from __future__ import annotations

__author__ = "Leonid Elkin, Mikhail, Mikhailov"
__copyright__ = "Copyright (c) 2025 PySATL project"
__license__ = "SPDX-License-Identifier: MIT"

from collections.abc import Callable
from typing import Any

from pysatl_core.distributions import (
ComputationStrategy,
SamplingStrategy,
)
from pysatl_core.families.distribution import ParametricFamilyDistribution
from pysatl_core.families.parametrizations import Parametrization, ParametrizationSpec
from pysatl_core.types import (
DistributionType,
GenericCharacteristicName,
ParametrizationName,
)

type ParametrizedFunction = Callable[[Parametrization, Any], Any]


class ParametricFamily:
"""
A family of distributions with multiple parametrizations.

This class represents a parametric family of distributions, such as
the normal or lognormal family, which can be parameterized in different
ways (e.g., mean-variance or canonical parametrization).

Attributes
----------
name : str
Name of the distribution family.
distr_type : DistributionType | Callable[[Parametrization] | DistributionType]
Type of distributions in this family.
parametrizations : ParametrizationSpec
Specification of available parametrizations.
distr_parametrizations :

distr_characteristics : Dict[GenericCharacteristicName, Callable[[Any, Any], Any]]
Mapping from characteristic names to computation functions.
sampling_strategy : SamplingStrategy
Strategy for sampling from distributions in this family.
computation_strategy : ComputationStrategy
Strategy for computing distribution characteristics.
"""

def __init__(
self,
name: str,
distr_type: DistributionType | Callable[[Parametrization], DistributionType],
distr_parametrizations: list[ParametrizationName],
distr_characteristics: dict[
GenericCharacteristicName,
dict[ParametrizationName, ParametrizedFunction] | ParametrizedFunction,
],
sampling_strategy: SamplingStrategy,
computation_strategy: ComputationStrategy[Any, Any],
):
"""
Initialize a new parametric family.

Parameters
----------
name : str
Name of the distribution family.

distr_type : DistributionType | Callable[[Parametrization], DistributionType]
Type of distributions in this family or, if type is parameter-depended, function
that takes as input *base* parametrization and inferes type based on it.

distr_parametrizations : List[ParametrizationName]
List of parametrizations for this distribution. *First parametrization is always
base parametrization*.

distr_characteristics:
Mapping from characteristics names to computation functions or dictionary of those,
if for multiple parametrizations same characteristic available.

sampling_strategy : SamplingStrategy
Strategy for sampling from distributions in this family.

computation_strategy : ComputationStrategy
Strategy for computing distribution characteristics.
"""
self._name = name
self._distr_type: Callable[[Parametrization], DistributionType] = (
(lambda params: distr_type) if isinstance(distr_type, DistributionType) else distr_type
)

# Parametrizations must be built by user
self.parametrization_names = distr_parametrizations
self.parametrizations = ParametrizationSpec(self.parametrization_names[0])

self.sampling_strategy = sampling_strategy
self.computation_strategy = computation_strategy

def _process_char_val(
value: dict[ParametrizationName, ParametrizedFunction] | ParametrizedFunction,
) -> dict[ParametrizationName, ParametrizedFunction]:
return value if isinstance(value, dict) else {self.parametrization_names[0]: value}

self.distr_characteristics = {
key: _process_char_val(value) for key, value in distr_characteristics.items()
}

@property
def name(self) -> str:
return self._name

def distribution(
self, parametrization_name: str | None = None, **parameters_values: Any
) -> ParametricFamilyDistribution:
"""
Create a distribution instance with the given parameters.

Parameters
----------
parametrization_name : str | None, optional
Name of the parametrization to use, or None for base parametrization.
**parameters_values
Parameter values for the distribution.

Returns
-------
ParametricFamilyDistribution
A distribution instance with the specified parameters.

Raises
------
ValueError
If the parameters don't satisfy the parametrization constraints.
"""
if parametrization_name is None:
parametrization_class = self.parametrizations.base
else:
parametrization_class = self.parametrizations.parametrizations[parametrization_name]

parameters = parametrization_class(**parameters_values)
base_parameters = self.parametrizations.get_base_parameters(parameters)
parameters.validate()
distribution_type = self._distr_type(base_parameters)
return ParametricFamilyDistribution(self.name, distribution_type, parameters)

__call__ = distribution
Loading
Loading