Skip to content

UNU.RAN PINV fails for shifted continuous distributions #117

@LeonidElkin

Description

@LeonidElkin

Summary

UNU.RAN PINV initialization fails for some shifted continuous distributions, for example Normal(100, 2).

The failure is caused by an incorrect default center used during PINV initialization. For strongly shifted distributions, the default center may be far outside the numerically meaningful region of the density.

There is also a cleanup/lifetime bug: after unur_init() consumes the parameter handle, the wrapper may try to free the same UNUR_PAR handle again, which can turn a normal initialization error into free(): invalid pointer.


Reproducer

from pysatl_core.families.configuration import configure_families_register
from pysatl_core.families.registry import ParametricFamilyRegister
from pysatl_core.sampling.unuran.core.unuran_sampling_strategy import DefaultUnuranSamplingStrategy


def sample_normal(mu: float, sigma: float, n: int) -> None:
    family = ParametricFamilyRegister.get("Normal")
    distribution = family.distribution(
        parametrization_name="meanStd",
        sampling_strategy=DefaultUnuranSamplingStrategy(),
        mu=mu,
        sigma=sigma,
    )

    print(f"sampling Normal(mu={mu}, sigma={sigma})")
    values = distribution.sample(n)
    print("ok", len(values))


def main() -> None:
    configure_families_register()

    sample_normal(20.0, 1.0, 80)
    sample_normal(100.0, 2.0, 80)


if __name__ == "__main__":
    main()

Actual behavior

Sampling may fail for a shifted normal distribution such as Normal(mu=100.0, sigma=2.0).

The underlying UNU.RAN error is caused by PINV checking the density at the default center. For a strongly shifted normal distribution, the density at x = 0 underflows to zero or is effectively zero. PINV then fails with an error equivalent to:

PDF(center) <= 0

Instead of reporting only a clean initialization error, the wrapper may additionally crash with:

free(): invalid pointer

Root cause

There are two related problems.

1. Incorrect PINV center

For continuous distributions, the UNU.RAN PINV configuration does not pass a suitable center.

As a result, UNU.RAN uses its default center, which is 0. This is invalid or numerically unstable for distributions whose mass is far away from zero.

For example, for Normal(100, 2), evaluating the PDF at 0 underflows or gives a value indistinguishable from zero, so PINV initialization fails.

The center should be set from the distribution mean when the mean is available.

2. Double-free of consumed parameter handle

After unur_init() succeeds or takes ownership of the UNUR_PAR parameter handle, the wrapper must not free that handle manually again.

Currently, the wrapper may free a consumed UNUR_PAR handle, which can produce:

free(): invalid pointer

This masks the original UNU.RAN initialization problem and makes debugging harder.


Expected behavior

The reproducer should successfully sample both distributions:

sample_normal(20.0, 1.0, 80)
sample_normal(100.0, 2.0, 80)

Expected output shape:

sampling Normal(mu=20.0, sigma=1.0)
ok 80
sampling Normal(mu=100.0, sigma=2.0)
ok 80

No invalid pointer crash should occur.


Proposed fix

  1. For continuous distributions using PINV, set the UNU.RAN center explicitly when a valid mean is available.
  2. The center should be configured after registering the required callbacks and before calling unur_init().
  3. Avoid freeing a UNUR_PAR handle after it has been consumed by unur_init().
  4. Make ownership transfer explicit in the wrapper code to prevent similar lifetime bugs.

Acceptance criteria

  1. The provided reproducer works for Normal(20, 1) and Normal(100, 2).
  2. PINV receives a valid center for continuous distributions when the mean is available.
  3. The center is set after callback registration and before unur_init().
  4. UNUR_PAR ownership is handled correctly.
  5. No double-free occurs after unur_init() consumes the parameter handle.
  6. A regression test is added for a strongly shifted normal distribution.
  7. Initialization errors remain readable and are not masked by memory-management crashes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions