autofit.ClipperPriorBox#

class ClipperPriorBox[source]#

Bases: AbstractClipper

Hard projection onto the prior support, inset to stay strictly inside it.

The inset is keyed on the kind of each bound. Collapsing these three cases into one relative margin * (upper - lower) is the obvious implementation and it is wrong in two separate ways, both silent:

  • Two-sided finite (UniformPrior, LogUniformPrior, TruncatedGaussianPrior) — inset by margin relative to the box width. This is deliberately not for prior support: those priors are inclusive at the limit (log_prior is finite exactly on the bound), so clipping onto the bound would already be valid. It is to avoid parking a lane exactly on a prior edge, where the model’s own transforms are prone to singularities (arctan2 / sqrt at exactly 0) — the same reason MultiStartGradient draws its broad starts from the interior (0.15, 0.85) of the unit cube rather than from (0, 1).

  • Unbounded (GaussianPrior) — no inset, and critically no width arithmetic at all. lower + margin * (upper - lower) evaluates -inf + inf, which is NaN; clipping against NaN bounds turns the coordinate into NaN and the whole objective with it. That failure looks exactly like the lane death this class exists to prevent, so the guarantee that an unbounded coordinate passes through untouched holds only if the width is never computed for it.

  • Half-open and exclusive (LogGaussianPrior, whose support is (0, inf)) — inset by an absolute strict_epsilon. A relative margin is identically zero here, there being no finite width, so it would clip exactly onto 0.0, where the support is strict and log_prior is -inf. Only an absolute nudge lands strictly inside. Which bounds are exclusive is read off the prior’s lower_limit_strict / upper_limit_strict, never inferred from its type.

Parameters:
  • margin (float) – The relative inset applied to two-sided finite bounds, as a fraction of the box width.

  • strict_epsilon (float) – The absolute inset applied to half-open bounds whose support excludes the limit itself. Deliberately small: the floor it produces is a very low-density point, but it is finite, and the resulting steep prior gradient is what pushes the lane back into the bulk rather than leaving it dead.

Methods

bounds_from_model

The (lower, upper) box, in physical parameter order.

project

Project vector onto the prior support.

bounds_from_model(model)[source]#

The (lower, upper) box, in physical parameter order.

Unbounded coordinates are -inf / +inf. The two arrays are returned separately because that is the shape project broadcasts against.

Note that this is not the shape scipy.optimize.minimize accepts: it reads a (lower, upper) tuple as a sequence of ``(min, max)`` pairs, which for a two-parameter model silently produces a wrong fit rather than an error. Callers handing these to scipy must build an explicit scipy.optimize.Bounds — see _bounds_from.

Parameters:

model – The model whose priors define the support.

project(vector, model, xp=<module 'numpy' from '/home/docs/checkouts/readthedocs.org/user_builds/pyautofit/envs/latest/lib/python3.12/site-packages/numpy/__init__.py'>, scale=None, bijector=None)[source]#

Project vector onto the prior support.

Parameters:
  • vector – A parameter vector, either a single (n_params,) vector or a batched (n_starts, n_params) array of them. Broadcasting handles both, so no vmap is required of the caller. Physical unless scale or bijector is given, in which case it is in that change of variables’ coordinates.

  • model – The model whose priors define the support.

  • xp – The array module, numpy or jax.numpy.

  • scale – The per-parameter step scale from a scaler, when the caller is stepping in phi = theta / scale rather than in physical parameters. The bounds are divided by it, so the projection happens in the caller’s own coordinates and no round-trip through physical space is needed. Scales are strictly positive, so dividing preserves the ordering of each (lower, upper) pair and +/-inf stay +/-inf. Mutually exclusive with bijector.

  • bijector – A resolved AbstractBijector (.from_model already called), when the caller is stepping in phi = bijector.forward(theta). The inset bounds are computed in physical space (kind-aware — see the module docstring) and then mapped through bijector.bounds_forward, which commutes with clipping because every bijector kind is monotone increasing. Mutually exclusive with scale.

Returns:

  • A (projected, clipped_mask) pair. clipped_mask is boolean, the same

  • shape as vector, and True exactly where a coordinate was moved.