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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Documentation sidebar links to sequince-density and sequince-examples under
Related Packages.

### Changed

- Add Gaussian noise to differential evolution proposals (`noise_scale=1e-5` in
mutation coordinates) to escape collapsed ensembles.
- Make MiniPCN optional.
- Default all sampling helpers to differential evolution.

## [0.1.0b1] - 2026-10-23

First public beta release.
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Install from PyPI::
pip install sequince

``sequince`` targets Python 3.11+. Its core dependencies are installed
automatically. The example below uses the included MiniPCN mutation backend,
automatically. The example below uses differential-evolution mutation,
see :doc:`installation` for all optional integrations supported.

.. _quick-start:
Expand Down
13 changes: 8 additions & 5 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ Installation

python -m pip install sequince

The core installation includes NumPy, SciPy, MiniPCN, and the Array API
compatibility packages. It supports NumPy-based sampling with the default
MiniPCN mutation backend.
The core installation includes NumPy, SciPy, and the Array API compatibility
packages. It supports NumPy-based sampling with the default
differential-evolution mutation method.

Optional integrations
---------------------
Expand All @@ -25,6 +25,8 @@ Install extras for the integrations you need, for example:

* - Extra
- Provides
* - ``minipcn``
- MiniPCN mutation and adaptive MiniPCN wrappers
* - ``jax``
- JAX arrays and functional execution
* - ``blackjax``
Expand All @@ -38,8 +40,9 @@ Install extras for the integrations you need, for example:
* - ``corner``
- Corner plots

For example, install ``sequince[emcee]`` to select emcee mutation instead of
the default MiniPCN backend. See
For example, install ``sequince[minipcn]`` to use MiniPCN mutation, or
``sequince[emcee]`` to select emcee mutation instead of
the default differential-evolution method. See
:doc:`user_guide/sampling_options/mutation_configuration` for mutation-specific
requirements.

Expand Down
10 changes: 5 additions & 5 deletions docs/user_guide/advanced_workflows/mcmc_sampler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Creating a sampler
------------------

Use :func:`~sequince.mcmc.make_mcmc` with a vectorised log-density. Each row of
the particle array is an independent chain:
the particle array is a chain. The default differential-evolution method
updates interacting chains and requires at least four particles:

.. code-block:: python

Expand All @@ -30,7 +31,6 @@ the particle array is an independent chain:

mcmc = make_mcmc(
log_prob,
mutation="minipcn",
n_steps=1_000,
)
result = mcmc.run(initial_state, rng=rng)
Expand Down Expand Up @@ -63,9 +63,9 @@ Choosing a mutation backend
---------------------------

``make_mcmc`` accepts the same named mutation backends used by the standard SMC
helper: ``"minipcn"``, ``"emcee"``, ``"blackjax"``, and
``"metropolis-hastings"``. Backend-specific settings are passed through
``mutation_kwargs``:
helper: ``"differential-evolution"`` (default), ``"minipcn"``, ``"emcee"``,
``"blackjax"``, and ``"metropolis-hastings"``. Backend-specific settings are passed through
``mutation_kwargs``. Install ``sequince[minipcn]`` before selecting MiniPCN:

.. code-block:: python

Expand Down
14 changes: 11 additions & 3 deletions docs/user_guide/sampling_options/mutation_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,17 @@ Prebuilt samplers
-----------------

:func:`~sequince.prebuilt_samplers.make_standard_bayesian_smc` accepts a method
name from the table in ``mutation`` (default: ``"minipcn"``), a transition count in
``mutation_n_steps`` (default: five), and constructor options in
``mutation_kwargs``. Use ``mutation="none"`` or ``None`` to disable mutation.
name from the table in ``mutation`` (default: ``"differential-evolution"``), a
transition count in ``mutation_n_steps`` (default: five), and constructor
options in ``mutation_kwargs``. Use ``mutation="none"`` or ``None`` to disable mutation.

Differential evolution requires at least four particles. Its default
``noise_scale=1e-5`` adds independent Gaussian noise in mutation coordinates.
Install ``sequince[minipcn]`` and use ``mutation="minipcn"`` to select MiniPCN
explicitly. The persistent SMC,
nested-sampling SMC, standalone MCMC, and additional-posterior-sample helpers
also default to differential evolution. Functional nested-sampling execution
requires an explicit ``mutation="minipcn"`` or ``mutation="blackjax"``.

The helper defaults to ``particle_transform="affine"``; configure it through
``particle_transform_kwargs`` or pass a transform object. With default ESS
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ dependencies = [
"scipy>=1.16",
"matplotlib>=3.5",
"numpy>=1.25.2",
"minipcn>=0.3.0a1",
"array-api-compat>=1.13",
"array-api-extra>=0.10",
"optree>=0.19",
Expand All @@ -39,6 +38,9 @@ include = ["sequince*"]
[tool.setuptools_scm]

[project.optional-dependencies]
minipcn = [
"minipcn>=0.3.0a1",
]
jax = [
"jax>0.5.3",
"jaxlib"
Expand All @@ -62,6 +64,7 @@ hdf5 = [

[dependency-groups]
test-core = [
"sequince[minipcn]",
"pytest>=7.0",
"pytest-cov>=4.0",
"corner",
Expand Down
4 changes: 2 additions & 2 deletions src/sequince/mcmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,14 @@ def resume_from(
def make_mcmc(
log_prob_fn: Callable[..., Array | float],
*,
mutation: str | MutationStep = "minipcn",
mutation: str | MutationStep = "differential-evolution",
n_steps: int = 100,
mutation_kwargs: dict[str, Any] | None = None,
particle_transform: ParticleTransform | None = None,
callbacks: Sequence[Callable[..., None]] | None = None,
checkpoint_writers: Sequence[CheckpointWriter] | None = None,
) -> MarkovChainMonteCarlo:
"""Construct standalone MCMC using an existing Sequince mutation backend."""
"""Construct standalone MCMC, defaulting to differential evolution."""
kwargs = dict(mutation_kwargs or {})
kwargs["store_chains"] = True
mutation_step = get_mutation_step(
Expand Down
5 changes: 3 additions & 2 deletions src/sequince/posterior.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ def draw_additional_samples(
result: SMCResult,
rng: "RandomGenerator",
n_particles: int,
mutation: MutationStrategy | None = "minipcn",
mutation: MutationStrategy | None = "differential-evolution",
mutation_n_steps: int = 10,
mutation_kwargs: dict[str, Any] | None = None,
particle_transform: ParticleTransform | str | None = "affine",
Expand Down Expand Up @@ -440,7 +440,8 @@ def draw_additional_samples(
n_particles:
Desired total number of particles after drawing additional samples.
mutation:
Mutation strategy to apply to the new particles.
Mutation strategy to apply to the new particles. Defaults to
``"differential-evolution"``.
mutation_n_steps:
Number of mutation steps to apply if mutation is used.
mutation_kwargs:
Expand Down
17 changes: 11 additions & 6 deletions src/sequince/prebuilt_samplers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def make_standard_bayesian_smc(
always_resample: bool | None = None,
resample_at_end: bool = True,
resampling_kwargs: Dict[str, Any] | None = None,
mutation: MutationStrategy | SMCStep = "minipcn",
mutation: MutationStrategy | SMCStep = "differential-evolution",
mutation_n_steps: int = 5,
mutation_kwargs: Dict[str, Any] | None = None,
always_mutate: bool | None = None,
Expand Down Expand Up @@ -141,8 +141,8 @@ def make_standard_bayesian_smc(
Extra keyword arguments forwarded to the resampling step constructor.
mutation : str or SMCStep, optional
Mutation strategy to use. Accepts ``"blackjax"``, ``"emcee"``, ``"eryn"``,
``"metropolis-hastings"``, ``"minipcn"`` (default), ``"none"``, or a
pre-built ``SMCStep`` instance.
``"differential-evolution"`` (default), ``"metropolis-hastings"``,
``"minipcn"``, ``"none"``, or a pre-built ``SMCStep`` instance.
mutation_n_steps : int, optional
Number of mutation steps to run per iteration.
mutation_kwargs : dict[str, Any] or None, optional
Expand Down Expand Up @@ -328,7 +328,7 @@ def make_persistent_bayesian_smc(
resampling: ResamplingStrategy | ResamplingStep = "systematic",
resample_at_end: bool = True,
resampling_kwargs: Dict[str, Any] | None = None,
mutation: MutationStrategy | SMCStep = "minipcn",
mutation: MutationStrategy | SMCStep = "differential-evolution",
mutation_n_steps: int = 5,
mutation_kwargs: Dict[str, Any] | None = None,
particle_transform: ParticleTransform | str | None = "affine",
Expand All @@ -343,6 +343,9 @@ def make_persistent_bayesian_smc(
) -> tuple[SequentialMonteCarlo, TemperedPosterior, MetaData]:
"""Return an SMC runner configured for persistent sampling.

Mutation defaults to differential evolution; other mutation options follow
:func:`make_standard_bayesian_smc`.

``n_particles`` is the active population size drawn from the complete
persistent archive on every iteration. ``target_ess`` defaults to twice
``n_particles``. ``target_ess`` and ``target_total_ess`` are absolute
Expand Down Expand Up @@ -561,7 +564,7 @@ def make_nested_sampling_smc(
resampling_ess_threshold: float | None = None,
always_resample: bool = True,
resampling_kwargs: Dict[str, Any] | None = None,
mutation: MutationStrategy | SMCStep | None = "minipcn",
mutation: MutationStrategy | SMCStep | None = "differential-evolution",
mutation_n_steps: int = 5,
mutation_kwargs: Dict[str, Any] | None = None,
particle_transform: ParticleTransform | str | None = "affine",
Expand Down Expand Up @@ -632,7 +635,9 @@ def make_nested_sampling_smc(

Functional execution requires JAX arrays, unconditional resampling with
unchanged population size, logging_frequency=0, and traceable mutation
(BlackJAX or MiniPCN). Use callbacks for host-side logging. Initialize with
(BlackJAX or MiniPCN). Select one explicitly with ``mutation``; the default
differential-evolution mutation is not supported for functional nested
sampling. Use callbacks for host-side logging. Initialize with
run_functional or initialize_functional before compiling iterations.
Compiled schedules must use backend operations and return a scalar array;
positive infinity finalizes the live tail. Python StopIteration is supported
Expand Down
10 changes: 9 additions & 1 deletion src/sequince/steps/mutation/adaptive/minipcn.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,15 @@ def __init__(
getattr(resolved_policy, "initialise", None)
) or not callable(getattr(resolved_policy, "update", None)):
raise TypeError("policy must implement initialise() and update().")
from minipcn import Sampler
try:
from minipcn import Sampler
except ModuleNotFoundError as exc:
if exc.name != "minipcn":
raise
raise ImportError(
"MiniPCN mutation requires the 'minipcn' extra; install it "
"with `pip install 'sequince[minipcn]'`."
) from exc

if not hasattr(Sampler, "init_step_state"):
raise ImportError(
Expand Down
10 changes: 9 additions & 1 deletion src/sequince/steps/mutation/minipcn.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,15 @@ def _prepare_sampler(
rng: Any = None,
) -> tuple[Any, Array, ParticleTransform | None, Any, StepContext]:
"""Prepare the latent particles and MiniPCN sampler."""
from minipcn import Sampler
try:
from minipcn import Sampler
except ModuleNotFoundError as exc:
if exc.name != "minipcn":
raise
raise ImportError(
"MiniPCN mutation requires the 'minipcn' extra; install it "
"with `pip install 'sequince[minipcn]'`."
) from exc

xp = namespace(state.particles)
block = self._proposal_block(context)
Expand Down
3 changes: 0 additions & 3 deletions tests/integration_tests/test_persistent_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ def test_make_persistent_bayesian_smc_reaches_gaussian_posterior(
target_ess=target_ess,
resample_at_end=resample_at_end,
mutation_n_steps=3,
mutation_kwargs={"step_fn": "tpcn"},
)
initial_particles = np.random.default_rng(1234).normal(
loc=0.0,
Expand Down Expand Up @@ -103,7 +102,6 @@ def test_runner_accumulates_posterior_batches_until_target_total_ess(
target_total_ess=target_total_ess,
resample_at_end=False,
mutation_n_steps=2,
mutation_kwargs={"step_fn": "tpcn"},
)
persist = smc._steps[0] # noqa: SLF001
assert isinstance(persist, PersistParticles)
Expand Down Expand Up @@ -200,7 +198,6 @@ def make_sampler(
target_total_ess=target_total_ess,
resample_at_end=resample_at_end,
mutation_n_steps=2,
mutation_kwargs={"step_fn": "tpcn"},
checkpoint_writers=checkpoint_writers,
callbacks=callbacks,
)
Expand Down
1 change: 0 additions & 1 deletion tests/integration_tests/test_standard_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ def test_standard_sampler_reaches_gaussian_posterior(
gaussian_log_prior,
gaussian_log_likelihood,
mutation_n_steps=3,
mutation_kwargs={"step_fn": "tpcn"},
logging_frequency=0,
)
initial_particles = np.random.default_rng(1234).normal(
Expand Down
18 changes: 18 additions & 0 deletions tests/test_mcmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from sequince.checkpoint import HDF5CheckpointWriter, SMCCheckpoint
from sequince.steps.mutation import (
CallableMutationKernel,
DifferentialEvolutionMutation,
MetropolisHastingsMutation,
MutationStep,
get_mutation_step,
Expand Down Expand Up @@ -131,3 +132,20 @@ def test_direct_mutation_step_is_configured_to_store_chains(

assert mutation.store_chains
assert result.chain.shape == (2, len(simple_particles), 2)


def test_default_mcmc_matches_explicit_differential_evolution(
simple_particles, standard_normal_log_prob
) -> None:
state = ParticleState(simple_particles)
default = make_mcmc(standard_normal_log_prob, n_steps=3)
explicit = make_mcmc(
standard_normal_log_prob,
mutation="differential-evolution",
n_steps=3,
)
assert isinstance(default.mutation, DifferentialEvolutionMutation)
actual = default.run(state, rng=321)
expected = explicit.run(state, rng=321)
assert actual.chain.shape == (3, len(simple_particles), 2)
np.testing.assert_array_equal(actual.chain, expected.chain)
Loading
Loading