Skip to content

Distributions

Runtime distributions for the reference evaluator.

distributions

Runtime distributions for the reference evaluator.

A :class:DistributionValue evaluates to a :class:RuntimeDistribution: the family's name and its named arguments as host values, with no host library behind it. Sampling and log densities go through a backend. The reference backend here implements the scalar and simplex families in plain Python, so the reference machine can run a model end to end and be compared against an independent oracle; a host provider may install a backend that covers every family through its own library.

DistributionBackend

Bases: Protocol

What a host must supply to sample and score a family.

sample

sample(family: str, arguments: Mapping[str, object], rng: Random) -> object

Draw one value.

PARAMETER DESCRIPTION
family

The family's source name.

TYPE: str

arguments

The named parameters as host values.

TYPE: Mapping[str, object]

rng

The generator to draw from.

TYPE: Random

RETURNS DESCRIPTION
object

The drawn host value.

Source code in src/quivers/qiec/distributions.py
def sample(
    self, family: str, arguments: Mapping[str, object], rng: random.Random
) -> object:
    """Draw one value.

    Parameters
    ----------
    family : str
        The family's source name.
    arguments : Mapping[str, object]
        The named parameters as host values.
    rng : random.Random
        The generator to draw from.

    Returns
    -------
    object
        The drawn host value.
    """
    ...

log_prob

log_prob(family: str, arguments: Mapping[str, object], value: object) -> float

Evaluate the log density at a value.

PARAMETER DESCRIPTION
family

The family's source name.

TYPE: str

arguments

The named parameters as host values.

TYPE: Mapping[str, object]

value

The point evaluated.

TYPE: object

RETURNS DESCRIPTION
float

The log density.

Source code in src/quivers/qiec/distributions.py
def log_prob(
    self, family: str, arguments: Mapping[str, object], value: object
) -> float:
    """Evaluate the log density at a value.

    Parameters
    ----------
    family : str
        The family's source name.
    arguments : Mapping[str, object]
        The named parameters as host values.
    value : object
        The point evaluated.

    Returns
    -------
    float
        The log density.
    """
    ...

log_mass

log_mass(family: str, arguments: Mapping[str, object]) -> float

The log of the total mass of a construction.

PARAMETER DESCRIPTION
family

The family's source name.

TYPE: str

arguments

The named parameters as host values.

TYPE: Mapping[str, object]

RETURNS DESCRIPTION
float

Zero for a probability measure; the log mass of a restriction or of a mixture of restrictions, which Normalize divides by.

Source code in src/quivers/qiec/distributions.py
def log_mass(self, family: str, arguments: Mapping[str, object]) -> float:
    """The log of the total mass of a construction.

    Parameters
    ----------
    family : str
        The family's source name.
    arguments : Mapping[str, object]
        The named parameters as host values.

    Returns
    -------
    float
        Zero for a probability measure; the log mass of a restriction
        or of a mixture of restrictions, which ``Normalize`` divides
        by.
    """
    ...

RuntimeDistribution dataclass

RuntimeDistribution(family: str, arguments: Mapping[str, object], batch: tuple[int, ...] = (), event: tuple[int, ...] = (), ranks: Mapping[str, int] = MappingProxyType({}), natural: tuple[int, ...] = ())

A constructed distribution, as host data.

PARAMETER DESCRIPTION
family

The family's source name.

TYPE: str

arguments

The named parameters as host values; frozen on construction.

TYPE: Mapping[str, object]

batch

The plate's batch extents, outermost first; each position is an independent draw scored on its own.

TYPE: tuple[int, ...] DEFAULT: ()

event

The plate's extended event extents, outermost first; draws along them join one event and are scored together.

TYPE: tuple[int, ...] DEFAULT: ()

ranks

Each parameter's own tensor rank, from the family registry, so an argument carrying plate dimensions can be told from one that does not.

TYPE: Mapping[str, int] DEFAULT: MappingProxyType({})

natural

The family's own event extents, so a scalar given for a tensor parameter can be broadcast to the shape the family expects.

TYPE: tuple[int, ...] DEFAULT: ()

plated property

plated: bool

Whether the distribution is constructed over a plate.

RETURNS DESCRIPTION
bool

True when it has batch or extended event axes.

__post_init__

__post_init__() -> None

Freeze the arguments so a distribution value cannot drift.

Source code in src/quivers/qiec/distributions.py
def __post_init__(self) -> None:
    """Freeze the arguments so a distribution value cannot drift."""
    object.__setattr__(self, "arguments", MappingProxyType(dict(self.arguments)))
    object.__setattr__(self, "ranks", MappingProxyType(dict(self.ranks)))

at

at(position: tuple[int, ...]) -> RuntimeDistribution

The distribution at one position of the plate, unplated.

PARAMETER DESCRIPTION
position

A position of the leading plate axes, outermost first; fewer positions than axes leave the remaining axes in place.

TYPE: tuple[int, ...]

RETURNS DESCRIPTION
RuntimeDistribution

The family with its arguments selected at the position and the selected axes dropped from the plate.

RAISES DESCRIPTION
DistributionError

If the position has more entries than the plate has axes.

Source code in src/quivers/qiec/distributions.py
def at(self, position: tuple[int, ...]) -> RuntimeDistribution:
    """The distribution at one position of the plate, unplated.

    Parameters
    ----------
    position : tuple[int, ...]
        A position of the leading plate axes, outermost first; fewer
        positions than axes leave the remaining axes in place.

    Returns
    -------
    RuntimeDistribution
        The family with its arguments selected at the position and
        the selected axes dropped from the plate.

    Raises
    ------
    DistributionError
        If the position has more entries than the plate has axes.
    """
    shape = (*self.batch, *self.event)
    if len(position) > len(shape):
        raise DistributionError(
            f"position {position!r} is deeper than a plate of rank {len(shape)}"
        )
    arguments = {
        name: self._broadcast(
            name, _slice_at(value, position, self.ranks.get(name, 0))
        )
        for name, value in self.arguments.items()
    }
    remaining = len(position)
    batch = self.batch[remaining:] if remaining < len(self.batch) else ()
    event = self.event[max(remaining - len(self.batch), 0) :]
    return RuntimeDistribution(
        self.family, arguments, batch, event, self.ranks, self.natural
    )

sample

sample(rng: Random | None = None) -> object

Draw one value through the installed backend.

PARAMETER DESCRIPTION
rng

The generator to draw from; the module generator by default, which :func:seed_reference_rng controls.

TYPE: Random | None DEFAULT: None

RETURNS DESCRIPTION
object

The drawn host value: one draw of the family, or nested tuples of draws over the plate's batch and event axes.

RAISES DESCRIPTION
DistributionError

If the backend cannot sample the family.

Source code in src/quivers/qiec/distributions.py
def sample(self, rng: random.Random | None = None) -> object:
    """Draw one value through the installed backend.

    Parameters
    ----------
    rng : random.Random | None
        The generator to draw from; the module generator by default,
        which :func:`seed_reference_rng` controls.

    Returns
    -------
    object
        The drawn host value: one draw of the family, or nested
        tuples of draws over the plate's batch and event axes.

    Raises
    ------
    DistributionError
        If the backend cannot sample the family.
    """
    generator = rng or _RNG
    if not self.plated:
        return _backend.sample(self.family, self._at(()), generator)
    shape = (*self.batch, *self.event)
    return _nest(
        {
            position: _backend.sample(self.family, self._at(position), generator)
            for position in _positions(shape)
        },
        shape,
    )

log_mass

log_mass() -> float

The log of the distribution's total mass through the installed backend.

RETURNS DESCRIPTION
float

Zero for a probability measure; over a plate, the sum of the masses at every position, since the plate's draw is their product.

Source code in src/quivers/qiec/distributions.py
def log_mass(self) -> float:
    """The log of the distribution's total mass through the installed backend.

    Returns
    -------
    float
        Zero for a probability measure; over a plate, the sum of the
        masses at every position, since the plate's draw is their
        product.
    """
    if not self.plated:
        return _backend.log_mass(self.family, self._at(()))
    shape = (*self.batch, *self.event)
    return math.fsum(
        _backend.log_mass(self.family, self._at(position))
        for position in _positions(shape)
    )

log_prob

log_prob(value: object, keep_batch: bool = False) -> object

Evaluate the log density at a value through the installed backend.

PARAMETER DESCRIPTION
value

The point evaluated; over a plate, nested tuples of points shaped like the plate.

TYPE: object

keep_batch

Whether to keep one weight per batch position rather than total them.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
object

The log density as a float, or, with keep_batch over a batch, nested tuples of floats shaped like the batch axes.

RAISES DESCRIPTION
DistributionError

If the backend cannot score the family, or the point is not shaped like the plate.

Source code in src/quivers/qiec/distributions.py
def log_prob(self, value: object, keep_batch: bool = False) -> object:
    """Evaluate the log density at a value through the installed backend.

    Parameters
    ----------
    value : object
        The point evaluated; over a plate, nested tuples of points
        shaped like the plate.
    keep_batch : bool
        Whether to keep one weight per batch position rather than
        total them.

    Returns
    -------
    object
        The log density as a float, or, with ``keep_batch`` over a
        batch, nested tuples of floats shaped like the batch axes.

    Raises
    ------
    DistributionError
        If the backend cannot score the family, or the point is not
        shaped like the plate.
    """
    if not self.plated:
        return _backend.log_prob(self.family, self._at(()), value)
    shape = (*self.batch, *self.event)
    if tensor_rank(value) < len(shape):
        raise DistributionError(
            f"a point scored under a plate of rank {len(shape)} has rank "
            f"{tensor_rank(value)}"
        )
    totals: dict[tuple[int, ...], float] = {
        position: 0.0 for position in _positions(self.batch)
    }
    for position in _positions(shape):
        point = value
        for index in position:
            point = point[index]  # type: ignore[index]
        totals[position[: len(self.batch)]] += _backend.log_prob(
            self.family, self._at(position), point
        )
    if keep_batch:
        return _nest(totals, self.batch)
    return math.fsum(totals.values())

ReferenceBackend

Plain-Python sampling and scoring for every family of the registry.

The scalar core lives beside the backend and the structured and compositional families in :mod:quivers.qiec.reference_families; a family outside the tables is reported by name rather than approximated, so a host provider can be installed for it.

families property

families: frozenset[str]

The families this backend implements.

RETURNS DESCRIPTION
frozenset[str]

The family names.

sample

sample(family: str, arguments: Mapping[str, object], rng: Random) -> object

Draw one value from an implemented family.

PARAMETER DESCRIPTION
family

The family's source name.

TYPE: str

arguments

The named parameters as host values.

TYPE: Mapping[str, object]

rng

The generator to draw from.

TYPE: Random

RETURNS DESCRIPTION
object

The drawn value: a float, an int, a bool, or a tuple of floats.

RAISES DESCRIPTION
DistributionError

If the family is not implemented here or a parameter is missing or malformed.

Source code in src/quivers/qiec/distributions.py
def sample(
    self, family: str, arguments: Mapping[str, object], rng: random.Random
) -> object:
    """Draw one value from an implemented family.

    Parameters
    ----------
    family : str
        The family's source name.
    arguments : Mapping[str, object]
        The named parameters as host values.
    rng : random.Random
        The generator to draw from.

    Returns
    -------
    object
        The drawn value: a float, an int, a bool, or a tuple of floats.

    Raises
    ------
    DistributionError
        If the family is not implemented here or a parameter is
        missing or malformed.
    """
    sampler = _SAMPLERS.get(family)
    if sampler is None:
        raise DistributionError(
            f"the reference backend cannot sample {family}; install a "
            "distribution backend for it"
        )
    return sampler(arguments, rng)

log_prob

log_prob(family: str, arguments: Mapping[str, object], value: object) -> float

Evaluate the log density of an implemented family.

PARAMETER DESCRIPTION
family

The family's source name.

TYPE: str

arguments

The named parameters as host values.

TYPE: Mapping[str, object]

value

The point evaluated.

TYPE: object

RETURNS DESCRIPTION
float

The log density, -inf outside the support.

RAISES DESCRIPTION
DistributionError

If the family is not implemented here or a parameter or the point is malformed.

Source code in src/quivers/qiec/distributions.py
def log_prob(
    self, family: str, arguments: Mapping[str, object], value: object
) -> float:
    """Evaluate the log density of an implemented family.

    Parameters
    ----------
    family : str
        The family's source name.
    arguments : Mapping[str, object]
        The named parameters as host values.
    value : object
        The point evaluated.

    Returns
    -------
    float
        The log density, ``-inf`` outside the support.

    Raises
    ------
    DistributionError
        If the family is not implemented here or a parameter or the
        point is malformed.
    """
    density = _DENSITIES.get(family)
    if density is None:
        raise DistributionError(
            f"the reference backend cannot score {family}; install a "
            "distribution backend for it"
        )
    return density(arguments, value)

log_mass

log_mass(family: str, arguments: Mapping[str, object]) -> float

The log of the total mass of a construction.

PARAMETER DESCRIPTION
family

The family's source name.

TYPE: str

arguments

The named parameters as host values.

TYPE: Mapping[str, object]

RETURNS DESCRIPTION
float

The family's log mass, zero for every probability family.

RAISES DESCRIPTION
DistributionError

If the family is not implemented here.

Source code in src/quivers/qiec/distributions.py
def log_mass(self, family: str, arguments: Mapping[str, object]) -> float:
    """The log of the total mass of a construction.

    Parameters
    ----------
    family : str
        The family's source name.
    arguments : Mapping[str, object]
        The named parameters as host values.

    Returns
    -------
    float
        The family's log mass, zero for every probability family.

    Raises
    ------
    DistributionError
        If the family is not implemented here.
    """
    if family not in _DENSITIES:
        raise DistributionError(
            f"the reference backend cannot weigh {family}; install a "
            "distribution backend for it"
        )
    mass = _LOG_MASSES.get(family)
    return 0.0 if mass is None else mass(arguments)

tensor_rank

tensor_rank(value: object) -> int

The nesting depth of a host tensor.

PARAMETER DESCRIPTION
value

A host value: a scalar, or a tuple of values of one rank.

TYPE: object

RETURNS DESCRIPTION
int

Zero for a scalar, one more than the first entry's rank for a tuple; an empty tuple has rank one.

Source code in src/quivers/qiec/distributions.py
def tensor_rank(value: object) -> int:
    """The nesting depth of a host tensor.

    Parameters
    ----------
    value : object
        A host value: a scalar, or a tuple of values of one rank.

    Returns
    -------
    int
        Zero for a scalar, one more than the first entry's rank for a
        tuple; an empty tuple has rank one.
    """
    if isinstance(value, tuple):
        return 1 + (tensor_rank(value[0]) if value else 0)
    return 0

seed_reference_rng

seed_reference_rng(seed: int) -> None

Reseed the generator reference draws use by default.

PARAMETER DESCRIPTION
seed

The seed; two runs seeded alike draw alike.

TYPE: int

Source code in src/quivers/qiec/distributions.py
def seed_reference_rng(seed: int) -> None:
    """Reseed the generator reference draws use by default.

    Parameters
    ----------
    seed : int
        The seed; two runs seeded alike draw alike.
    """
    _RNG.seed(seed)

distribution_backend

distribution_backend() -> DistributionBackend

The backend distribution values currently sample and score through.

RETURNS DESCRIPTION
DistributionBackend

The installed backend; the reference backend until replaced.

Source code in src/quivers/qiec/distributions.py
def distribution_backend() -> DistributionBackend:
    """The backend distribution values currently sample and score through.

    Returns
    -------
    DistributionBackend
        The installed backend; the reference backend until replaced.
    """
    return _backend

install_distribution_backend

install_distribution_backend(backend: DistributionBackend) -> DistributionBackend

Replace the backend distribution values sample and score through.

PARAMETER DESCRIPTION
backend

The backend to install, such as one over a host library that covers every family.

TYPE: DistributionBackend

RETURNS DESCRIPTION
DistributionBackend

The backend that was installed before, so a caller can restore it.

Source code in src/quivers/qiec/distributions.py
def install_distribution_backend(backend: DistributionBackend) -> DistributionBackend:
    """Replace the backend distribution values sample and score through.

    Parameters
    ----------
    backend : DistributionBackend
        The backend to install, such as one over a host library that
        covers every family.

    Returns
    -------
    DistributionBackend
        The backend that was installed before, so a caller can restore it.
    """
    global _backend
    previous = _backend
    _backend = backend
    return previous