Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Homogeneous Poisson Process — From Scratch and With NumPyro

Open In Colab

A homogeneous Poisson process (HPP) with intensity λ>0\lambda > 0 on [0,T][0, T] has

  • count N∼Poisson(λT)N \sim \mathrm{Poisson}(\lambda T),

  • conditional event times i.i.d. uniform on [0,T][0, T] given the count,

  • compensator Λ(t)=λt\Lambda(t) = \lambda t,

  • log-likelihood log⁡L=nlog⁡λ−λT\log L = n \log \lambda - \lambda T.

This notebook shows both faces of xtremax for the HPP:

  1. From scratch — work directly with the pure-JAX primitives (hpp_log_prob, hpp_sample, hpp_cumulative_intensity). No class state, no framework.

  2. With NumPyro — wrap the same process as a numpyro.distributions.Distribution and run Bayesian inference via NUTS.

Setup

/home/azureuser/localfiles/xtremax/.venv/bin/python: No module named pip
Note: you may need to restart the kernel to use updated packages.
/home/azureuser/localfiles/xtremax/.venv/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

1. From scratch — the primitives layer

xtremax.point_processes.primitives ships the HPP as a set of pure JAX functions. Every call takes arrays and returns arrays — no classes, no framework. That’s all we need to sample, evaluate the likelihood, and visualise the process.

Sampling

Drew n_events = 13 (λT = 20.0)
First five event times: [0.0729382  0.2089119  0.950073   0.96612096 1.1928833 ]

The sampler returns (times, mask, n_events). Variable-length sequences can’t be ragged under jax.jit, so events live in a padded buffer of size max_events with a boolean mask marking real events. This keeps the primitive jit and vmap compatible — useful for running many realisations in parallel (see below).

<Figure size 1000x350 with 2 Axes>

Evaluating the log-likelihood

hpp_log_prob(n_events, rate, T) returns nlog⁡λ−λTn \log \lambda - \lambda T. Because it’s a pure JAX function it composes with jax.vmap — scan a grid of candidate rates against the observed count in one call and read off the maximum-likelihood estimate.

Grid-search MLE    : λ̂ = 1.298
Closed-form MLE    : λ̂ = 1.300
<Figure size 700x350 with 1 Axes>

Many realisations in parallel via vmap

Verify empirically that N∼Poisson(λT)N \sim \mathrm{Poisson}(\lambda T) by drawing a few thousand sequences and comparing the count histogram with the exact PMF.

<Figure size 700x350 with 1 Axes>

2. With NumPyro — Bayesian inference

To put a prior on λ\lambda and get a full posterior we wrap the HPP as a numpyro.distributions.Distribution. The wrapper’s .log_prob delegates to the same primitive we just used — nothing magic happens in the shim — but NumPyro can now compose it as a likelihood site inside a model with priors and run NUTS.

Sample / log_prob round-trip

The distribution returns a (times, mask) PyTree from .sample, and .log_prob accepts exactly that PyTree — so log_prob(sample(key)) is a valid round-trip.

sample() returns tuple of length 2
round-trip log_prob: -2.6713199615478516

NUTS for the rate parameter

Place a weakly informative log-normal prior on λ\lambda and condition on the count we observed in section 1.

Posterior mean : 1.276
Posterior 95%  : [0.719, 2.016]
MLE            : 1.300
Ground truth   : 2.0
<Figure size 700x350 with 1 Axes>

Posterior predictive check

Draw rates from the posterior, simulate new event sequences with the same primitive as before, and check that the predicted count distribution covers the observation.

<Figure size 700x350 with 1 Axes>

Takeaways

  • The primitives layer gives you the math as plain JAX functions — hpp_log_prob, hpp_sample, hpp_cumulative_intensity. Everything is jit / vmap clean so you can scan log-likelihoods or batch thousands of draws with no ceremony.

  • The NumPyro wrapper is a thin shim that delegates to the same primitives; it exists so the HPP can appear as a likelihood site inside a numpyro.sample / MCMC / SVI workflow.

  • The (times, mask) padded PyTree is the canonical event-sequence representation, and the same representation round-trips between .sample and .log_prob on the distribution.