Skip to content

Families/ adding base version of exponential family#67

Open
domosedy wants to merge 5 commits intoPySATL:mainfrom
domosedy:exponential-family
Open

Families/ adding base version of exponential family#67
domosedy wants to merge 5 commits intoPySATL:mainfrom
domosedy:exponential-family

Conversation

@domosedy
Copy link

@domosedy domosedy commented Feb 3, 2026

No description provided.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change ExponentialFamily name to ContinuousExponentialClassFamily and other derivals

Comment on lines +30 to +37
PDF = "pdf"
CDF = "cdf"
PPF = "ppf"
CF = "char_func"
MEAN = "mean"
VAR = "var"
SKEW = "skewness"
KURT = "kurtosis"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have EnumStr with the names of the characteristic. We should use it to avoid declarations in each file

Comment on lines +49 to +55
class ExponentialConjugateHyperparameters:
def __init__(self, alpha: Any, beta: int):
self.alpha = alpha
self.beta = beta

def __str__(self) -> str:
return f"alpha={self.alpha}, beta={self.beta}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dataclass

@dataclass
class ExponentialConjugateHyperparameters:
    self.alpha : Any
    self.beta : int

    def __str__(self) -> str:
        return f"alpha={self.alpha}, beta={self.beta}"

Comment on lines +58 to +60
def doesAccept(x: list[float] | float, support: list[tuple[float, float]]) -> bool:
if not hasattr(x, "__len__"):
x = [x]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not list in the annotations but Iterable or Sequence

Comment on lines +58 to +60
def doesAccept(x: list[float] | float, support: list[tuple[float, float]]) -> bool:
if not hasattr(x, "__len__"):
x = [x]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support can be list[ContinuousSupport] or just list[Interval1D]

Comment on lines +64 to +68
def accept_1D(x: float, borders: tuple[float, float]) -> bool:
left, right = borders
if abs(x) == 0 and (abs(left) == 0 or abs(right) == 0):
return False
return left <= x <= right
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be replaced with Interval1D.contains

Comment on lines +163 to +165
def conjugate_sufficient(
theta: float,
) -> list[Any]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accept multivariate parameter

@Desiment Desiment self-requested a review February 20, 2026 19:02
Comment on lines +73 to +79
class SpacePredicate:
def __init__(self, predicate: Callable[[Any], bool]):
self._predicate = predicate

def accepts(self, x: Any) -> bool:
return self._predicate(x)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to types.py as type allias

Comment on lines +81 to +84
class SpacePredicateArray(SpacePredicate):
def __init__(self, space: list[tuple[float, float]]):
SpacePredicate.__init__(self, lambda x: doesAccept(x, space))
self._space = space
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to support.py; use interval_1d contains, or add doesAccept functionality to contains method

Comment on lines +176 to +180
parametrization: ExponentialFamilyParametrization,
) -> Any:
alpha = parametrization.theta[0]
beta = parametrization.theta[1]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accept multivariate parameter

if not hasattr(theta, "__len__"):
theta = [theta]
parametrization = ExponentialFamilyParametrization(theta=theta)
return np.exp(np.dot(theta, alpha) + beta * self._log_partition(parametrization))[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead x[0] use x.item() for single element array

alpha = parametrization.theta[0]
beta = parametrization.theta[1]

def pdf(theta: Any) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use conjugate_sufficent instead

something like

np.exp(np.dot(conjugate_sufficient(theta), chi + [nu])).item()


def transform(
self,
transform_function: Callable[[Any], Any],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add docstring
transform function is mapping from user parametrization to canonical

Comment on lines +294 to +295
alpha = prior_hyper.alpha
beta = prior_hyper.beta
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming:

alpha -> effective_suff_stat_value
beta -> effective_sample_size

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change globally

self,
*,
log_partition: Callable[[ExponentialFamilyParametrization], float],
sufficient_statistics: Callable[[Any], Any],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe enforce Callable[[npt.NDArray[np.float64]], npt.NDArray[np.float64]]?

def __init__(
self,
*,
log_partition: Callable[[ExponentialFamilyParametrization], float],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since for density from Exponential class, one has to take product between vector of parameters and vector of statistics, we should

  1. either enforce both log_partition and sufficent_statistics had annotated arguments
  2. or leave checking correctness of argument order to user;

In the second case maybe it is better to use Callable[[npt.NDArray[np.float64]], np.float64]?

Currently, it seems that we have a kind of prototype to annotate natural parameters, but if want keep it, we should make possible to give arbitrary names to parameters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that NEF is actually a class of families where sufficient statistic is provided by identity function; Functionality of ExponentialClass can be derived from existing functionality for multiple parametrizations

For example add optional argument to conjugate_prior which will highlight parametrization name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExponentialClassFamily: conjugate prior support and posterior hyperparameters

3 participants