Skip to content

Add ExtendedMixtureModel for yield-weighted fits - #51

Merged
mmikhasenko merged 14 commits into
mainfrom
codex/extended-mixture-model
Jul 5, 2026
Merged

mmikhasenko merged 14 commits into
mainfrom
codex/extended-mixture-model

Conversation

@mmikhasenko

@mmikhasenko mmikhasenko commented May 25, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR adds ExtendedMixtureModel, a small distribution helper for extended likelihood fits where component weights are expected event yields rather than normalized mixture fractions.

The motivating use case came from the 2D fit work in BuildConstructors.jl. There, the fitted parameters are y_phiphi, y_mixed, and y_kkkk, and the model density is

$$f_{ext}(x) = \sum_k y_k f_k(x)$$

with extended negative log-likelihood

$$-\sum_i \log f_{ext}(x_i) + \sum_k y_k.$$

Distributions.MixtureModel is not a direct fit for this because its weights are probabilities and are normalized to fractions. Extended fits need to preserve the absolute yield scale during density evaluation.

Closes #50.

Calling the model

ExtendedMixtureModel follows much of the Distributions.jl vocabulary (components, ncomponents, support, minimum, maximum, length, …), but it is not a drop-in replacement for a normalized MixtureModel at the density API.

There is no pdf(model, x) or logpdf(model, x). The extended density is evaluated by calling the model directly:

using Distributions
using DistributionsHEP

components = [Normal(-1.0, 0.5), Normal(1.0, 0.25)]
model = ExtendedMixtureModel(components, [20.0, 5.0])

x = 0.1
density = model(x)   # sum_k y_k * pdf(component(model, k), x)

For multivariate components, pass a coordinate vector:

model([0.2, 0.4])

To use the usual pdf / logpdf interface, convert to a normalized mixture first:

normalized = MixtureModel(model)
pdf(normalized, x)   # model(x) / total_yield(model)

For fitting, extended_negative_log_likelihood(model, data) applies the standard extended NLL using model(x) internally:

extended_negative_log_likelihood(model, data)
# -sum(log(model(x) for x in data)) + total_yield(model)

Implemented Interface

  • ExtendedMixtureModel(components, yields): construct a distribution from components and yields.
  • model(x): evaluate the yield-weighted extended density (univariate x::Real or multivariate x::AbstractVector).
  • yields / total_yield: expose the expected event counts and their sum.
  • extended_negative_log_likelihood: extended NLL built from model(x).
  • support, minimum, maximum: component-wise bounds for nested product and mixture models.
  • ncomponents, components, component, component_type: match the component-introspection vocabulary used by Distributions.MixtureModel.
  • MixtureModel(model): convert to the corresponding normalized mixture when pdf / logpdf are needed.
  • length(model): multivariate dimension query.

Interface Justification

The minimal functionality needed by the motivating fit is construction, model(x), and total_yield: those are enough to write the extended likelihood. We deliberately omit pdf / logpdf on ExtendedMixtureModel itself so the yield-weighted density is not silently treated as a normalized probability density.

extended_negative_log_likelihood is included because every user of this helper otherwise has to repeat the same formula and data iteration. It keeps the sign convention and the Poisson extended term next to the distribution type, while still remaining small and transparent.

support, minimum, and maximum are included because multivariate fit models are built from nested products and mixtures. These methods return the per-dimension union of component bounds and are tested for consistency with the rest of the package.

The component accessors are not needed for the likelihood formula itself, but they are the standard Distributions.jl mixture vocabulary. Keeping them makes this type inspectable in the same way as MixtureModel and avoids forcing users to reach into fields.

MixtureModel(model) is also not required for fitting, but it is useful and unambiguous: divide yields by total_yield(model) to get the ordinary normalized mixture. This gives users an explicit bridge for diagnostics, sampling, or APIs that require a normalized MixtureModel.

The implementation intentionally does not subtype AbstractMixtureModel and does not define probs. In Distributions.jl, probs means normalized prior probabilities. Exposing yields through that interface would be misleading, while normalizing them implicitly would lose the central feature of this model.

Follow-up: coordinate marginalization

One-dimensional projections via marginalize(model, k) are tracked in #53 (merge after this PR lands).

Tests

  • julia --project=. -e 'using Pkg; Pkg.test()'

@mmikhasenko
mmikhasenko force-pushed the codex/extended-mixture-model branch from bd287a0 to 7134257 Compare May 25, 2026 14:54
@mmikhasenko
mmikhasenko marked this pull request as ready for review May 25, 2026 15:02
@Moelf

Moelf commented May 25, 2026

Copy link
Copy Markdown
Member

can component be template (i.e. histograms)?

@mmikhasenko

Copy link
Copy Markdown
Member Author

can component be template (i.e. histograms)?

sure thing

@mmikhasenko

Copy link
Copy Markdown
Member Author

@Moelf could you please install
https://github.com/marketplace/gemini-code-assist
for the JuliaHEP organization? -- it works very well.

I'd ask gemini to review before asking you

@mmikhasenko

mmikhasenko commented May 27, 2026 •

Copy link
Copy Markdown
Member Author

Here are some issues:

  • support(model) # not working, check what support(Normal x Normal)
  • marginalize(Normal x Normal) does not work since Normal x Normal is mutiNormal.

EDIT: resolved

Move marginalize and multivariate support helpers out of this branch so they
can land in a follow-up PR after ExtendedMixtureModel merges.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mmikhasenko

This comment was marked as outdated.

Provide component-wise bounds for nested product and mixture models, with
tests matching the support/min/max consistency used elsewhere in the package.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mmikhasenko
mmikhasenko requested a review from Moelf May 27, 2026 22:06
mmikhasenko and others added 5 commits May 28, 2026 00:10
The inner constructor already enforces length and non-emptiness invariants.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep minimum, maximum, and support only for ExtendedMixtureModel, which
this package owns. Drop tests that asserted pirated behavior on foreign types.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove the redundant _union_support helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use a single elementwise reduce over component bounds instead of separate
univariate and multivariate helper functions.

Co-authored-by: Cursor <cursoragent@cursor.com>
Deduplicate support checks and drop redundant pipe and gaussian cases.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Moelf

This comment was marked as outdated.

@mmikhasenko

This comment was marked as outdated.

@mmikhasenko

Copy link
Copy Markdown
Member Author

@oschulz

M(x,p) = p[1]*pdf1(x) + p[2]*pdf2(x)

where p[1] and p[2] are not fractions, they are yields (do not sum to 1).

The model I want to bring in in this PR is not a distribution, but a measure. Still it uses the same dispatch pattern as Distributions.jl.

Is there a better place for this?

@mmikhasenko
mmikhasenko merged commit bb22666 into main Jul 5, 2026
2 checks passed
@mmikhasenko
mmikhasenko deleted the codex/extended-mixture-model branch July 5, 2026 20:39
@oschulz

oschulz commented Jul 5, 2026

Copy link
Copy Markdown
Member

The model I want to bring in in this PR is not a distribution, but a measure. Still it uses the same dispatch pattern as Distributions.jl.
Is there a better place for this?

MeasureBase.jl supports things like

2 * StdNormal() + 3 * StdExponential()

out of the box. I'm currently in the middle of finally getting the MeasureBase Distributions extension done, to make this work with any Distribution wrapped as a measure.

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.

Add extended mixture model with yield weights

3 participants