Skip to content

Families/ adding base version of exponential family#67

Open
domosedy wants to merge 12 commits into
PySATL:mainfrom
domosedy:exponential-family
Open

Families/ adding base version of exponential family#67
domosedy wants to merge 12 commits into
PySATL:mainfrom
domosedy:exponential-family

Conversation

@domosedy

@domosedy domosedy commented Feb 3, 2026

Copy link
Copy Markdown

No description provided.

Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
@Desiment Desiment self-requested a review February 20, 2026 19:02
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py
@domosedy domosedy force-pushed the exponential-family branch 2 times, most recently from 779438f to 12516ff Compare March 29, 2026 18:56
@LeonidElkin LeonidElkin added core.families Enhancement New feature or request labels May 1, 2026
@LeonidElkin LeonidElkin added this to the Prototype alpha-version milestone May 1, 2026
Comment thread src/pysatl_core/types.py Outdated
Comment thread src/pysatl_core/distributions/support.py Outdated
Comment thread src/pysatl_core/distributions/support.py Outdated
Comment thread src/pysatl_core/families/__init__.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread tests/unit/families/test_exponential_family.py Outdated
@domosedy domosedy force-pushed the exponential-family branch 2 times, most recently from f67f3fd to 2d470c9 Compare May 3, 2026 21:20
@domosedy domosedy force-pushed the exponential-family branch from 2d470c9 to 189afea Compare May 11, 2026 12:36
@domosedy domosedy force-pushed the exponential-family branch 3 times, most recently from 1605771 to 1d58077 Compare May 11, 2026 13:09
@domosedy domosedy force-pushed the exponential-family branch from 83f5584 to 2500678 Compare May 17, 2026 12:15
Comment thread src/pysatl_core/distributions/support.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/distributions/support.py Outdated
Comment thread src/pysatl_core/distributions/support.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
@domosedy domosedy force-pushed the exponential-family branch from ec832d5 to d5a2be1 Compare May 31, 2026 09:33
@domosedy domosedy force-pushed the exponential-family branch from d5a2be1 to 804cfa4 Compare May 31, 2026 10:50
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment thread tests/unit/distributions/test_support.py
Comment thread src/pysatl_core/families/exponential_family.py
Comment thread src/pysatl_core/families/exponential_family.py Outdated
Comment on lines +328 to +339
return ContinuousExponentialClassFamily(
log_partition=conjugate_log_partition,
sufficient_statistics=conjugate_sufficient,
normalization_constant=lambda _: 1,
support=self._parameter_space,
sufficient_statistics_values=self._parameter_space,
parameter_space=PredicateSupport(predicate=conjugate_sufficient_accepts),
name=self.name,
distr_type=self._distr_type,
distr_parametrizations=self.parametrization_names,
support_by_parametrization=self.support_resolver,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_support is changed for the conjugate/transformed family, but the family still reuses self.support_resolver.

This makes the density use the new support while distribution.support can remain None or point to the original support. Expose the same transformed support through support_by_parametrization as well, e.g. for transform() return a resolver for PredicateSupport(new_support) and for the conjugate prior return the natural-parameter support.

Comment on lines +385 to +396
return ContinuousExponentialClassFamily(
log_partition=self._log_partition,
sufficient_statistics=new_sufficient,
normalization_constant=new_normalization,
support=PredicateSupport(predicate=new_support),
parameter_space=self._parameter_space,
sufficient_statistics_values=self._sufficient_statistics_values,
name=f"Transformed{self._name}",
distr_type=self._distr_type,
distr_parametrizations=self.parametrization_names,
support_by_parametrization=self.support_resolver,
)

@LeonidElkin LeonidElkin Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same issue as in comment above

Comment on lines +446 to +489
def posterior_hyperparameters(
self,
parametrization: ExponentialConjugateHyperparameters,
sample: list[Any] | Any,
) -> ExponentialConjugateHyperparameters:
"""
Update the conjugate prior hyperparameters given observed data.

For prior hyperparameters ``(nu_0, n_0)`` and observations ``x_i``,
the posterior hyperparameters are

``nu = nu_0 + sum_i T(x_i)`` and ``n = n_0 + N``.

Parameters
----------
parametrization : ExponentialConjugateHyperparameters
Current conjugate hyperparameters.
sample : list[Any] or Any
Observations used for the update. A non-string iterable is treated
as a sample; any other value is treated as one observation.

Returns
-------
ExponentialConjugateHyperparameters
Updated hyperparameters after incorporating ``sample``.
"""
if hasattr(sample, "__iter__") and not isinstance(sample, str):
posterior_effective_suff_stat_value = np.array(
parametrization.effective_suff_stat_value
) + np.sum(
[self._sufficient(x) for x in sample],
axis=0,
)
posterior_effective_sample_size = parametrization.effective_sample_size + len(sample)
else:
posterior_effective_suff_stat_value = np.array(
parametrization.effective_suff_stat_value,
) + np.asarray(self._sufficient(sample)) # type: ignore[arg-type]
posterior_effective_sample_size = parametrization.effective_sample_size + 1

return ExponentialConjugateHyperparameters(
effective_suff_stat_value=posterior_effective_suff_stat_value,
effective_sample_size=posterior_effective_sample_size,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI finding

hasattr(sample, "__iter__") misclassifies zero-dimensional NumPy arrays as samples.

posterior_hyperparameters(..., sample=np.asarray(0.5)) enters the iterable branch and raises TypeError: iteration over a 0-d array. Normalize the input explicitly, e.g. distinguish scalar/0-D observations from batches with np.ndim(sample).

Comment on lines +511 to +528
def conjugate_log_partition(
parametrization: ExponentialConjugateHyperparameters,
) -> Number:
conjugate_value = self.conjugate_prior_family._log_partition(
parametrization.transform_to_base_parametrization().theta
)
return cast(Number, np.exp(conjugate_value).item())

def posterior_density(parametrization: Parametrization, x: NumericArray) -> Number:
parametrization = cast(ExponentialConjugateHyperparameters, parametrization)
if not self._support.contains(np.asarray(x)):
return cast(np.float32, 0.0)
return cast(
np.float32,
self._normalization(x)
* conjugate_log_partition(parametrization)
/ conjugate_log_partition(
self.posterior_hyperparameters(parametrization=parametrization, sample=[x])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI finding

posterior_density recomputes the conjugate-prior family and its normalizing integral on every density evaluation.

conjugate_log_partition(...) accesses self.conjugate_prior_family, and each call eventually runs nquad; posterior_density calls it twice per x. Construct/cache the conjugate family once in posterior_predictive and avoid recomputing invariant normalizers for every point.

Comment on lines +182 to +189
family_characteristics: CharacteristicsMap = {
CharacteristicName.PDF: self.density,
CharacteristicName.MEAN: self._mean,
CharacteristicName.VAR: self._var,
}
distr_characteristics = dict(distr_characteristics or {})
merged_characteristics = dict(family_characteristics)
merged_characteristics.update(distr_characteristics)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The constructor now preserves user characteristics, but the merge is still shallow and does not keep the generic exponential-family methods under separate labels.

If a user provides MEAN/VAR/PDF, the generic fallback is replaced entirely. The intended contract is to "expand under other labels". Merge by label and add a test for the collision case, not only for adding CDF.

Comment thread src/pysatl_core/families/exponential_family.py
@domosedy domosedy force-pushed the exponential-family branch from 8b99bff to 16115cb Compare June 21, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core.families Enhancement New feature or request

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

ExponentialClassFamily: conjugate prior support and posterior hyperparameters

3 participants