Skip to content

Perturbations

dicex.GaussianPerturbation(mu, sigma=None, *, cov=None, d=None, env_mean=None, env_cov=None, seed=None)

Bases: BasePerturbation

Gaussian perturbation for the pair (T, ε).

T is Gaussian directional execution noise. ε ~ N(env_mean, env_cov) is the additive environmental noise.

Initialize with Gaussian directional noise and additive noise.

Parameters:

Name Type Description Default
mu float | ndarray

Mean parameter of T. Scalar for shared mean, vector otherwise.

required
sigma float | ndarray | None

Standard deviation parameter of T. Scalar for iid noise, vector for diagonal covariance. Mutually exclusive with cov.

None
cov ndarray | None

Covariance parameter of T. Can be diagonal (d,) or full covariance (d, d). Mutually exclusive with sigma.

None
d int | None

Optional dimensionality of T when it cannot be inferred later.

None
env_mean ndarray | None

Mean vector m_ε of the additive noise (d,). If None, defaults to zero vector.

None
env_cov ndarray | None

Covariance matrix Σ_ε of the additive noise (d, d). If None, defaults to zero matrix.

None
seed int | None

Optional seed for the random number generator.

None
Source code in src/dicex/distributions/gaussian.py
def __init__(  # noqa: PLR0913
    self,
    mu: float | np.ndarray,
    sigma: float | np.ndarray | None = None,
    *,
    cov: np.ndarray | None = None,
    d: int | None = None,
    env_mean: np.ndarray | None = None,
    env_cov: np.ndarray | None = None,
    seed: int | None = None,
) -> None:
    """Initialize with Gaussian directional noise and additive noise.

    Args:
        mu (float | np.ndarray): Mean parameter of T. Scalar for shared mean,
            vector otherwise.
        sigma (float | np.ndarray | None): Standard deviation parameter of T.
            Scalar for iid noise, vector for diagonal covariance.
            Mutually exclusive with ``cov``.
        cov (np.ndarray | None): Covariance parameter of T. Can be diagonal
            ``(d,)`` or full covariance ``(d, d)``. Mutually exclusive with
            ``sigma``.
        d (int | None): Optional dimensionality of T when it cannot be
            inferred later.
        env_mean (np.ndarray | None): Mean vector m_ε of the additive noise
            (d,). If None, defaults to zero vector.
        env_cov (np.ndarray | None): Covariance matrix Σ_ε of the additive
            noise (d, d). If None, defaults to zero matrix.
        seed (int | None): Optional seed for the random number generator.
    """
    if (sigma is None) == (cov is None):
        msg = "Exactly one of 'sigma' or 'cov' must be provided."
        raise ValueError(msg)

    mu_arr = np.asarray(mu, dtype=float)
    sigma_arr = np.asarray(sigma, dtype=float) if sigma is not None else None
    cov_arr = np.asarray(cov, dtype=float) if cov is not None else None

    if sigma_arr is not None and np.any(sigma_arr < 0):
        msg = f"Standard deviation sigma cannot be negative. Got {sigma}"
        raise ValueError(msg)
    if cov_arr is not None:
        if cov_arr.ndim not in (1, 2):
            msg = f"cov must be a vector or matrix. Got shape {cov_arr.shape}"
            raise ValueError(msg)
        if cov_arr.ndim == 1 and np.any(cov_arr < 0):
            msg = "Diagonal covariance entries cannot be negative."
            raise ValueError(msg)

    self.mu = float(mu_arr) if mu_arr.ndim == 0 else mu_arr
    self.sigma = None if sigma_arr is None else (float(sigma_arr) if sigma_arr.ndim == 0 else sigma_arr)
    self.cov = cov_arr
    self.d = d
    self.env_mean = np.asarray(env_mean, dtype=float) if env_mean is not None else None
    self.env_cov = np.asarray(env_cov, dtype=float) if env_cov is not None else None
    self.seed = seed
    self._rng = np.random.default_rng(seed)

params property

Return the parameters of the distribution.

Returns:

Type Description
dict[str, Any]

dict[str, Any]: Dictionary containing the perturbation configuration.

sample(n, d=None, rng=None)

Sample n directional perturbation vectors from the configured Gaussian law.

Parameters:

Name Type Description Default
n int

Number of samples.

required
d int | None

Optional feature-space dimension.

None
rng Generator | None

Optional random number generator.

None

Returns:

Type Description
ndarray

np.ndarray: 2D array of shape (n, dim).

Source code in src/dicex/distributions/gaussian.py
def sample(self, n: int, d: int | None = None, rng: np.random.Generator | None = None) -> np.ndarray:
    """Sample n directional perturbation vectors from the configured Gaussian law.

    Args:
        n (int): Number of samples.
        d (int | None): Optional feature-space dimension.
        rng (np.random.Generator | None): Optional random number generator.

    Returns:
        np.ndarray: 2D array of shape (n, dim).
    """
    if rng is None:
        rng = self._rng
    dim = self._resolve_dim(d)
    mu_vec = self._mean_vector(dim)
    if self.cov is not None:
        if self.cov.ndim == 1:
            if len(self.cov) != dim:
                msg = f"cov has dimension {len(self.cov)} but expected {dim}."
                raise ValueError(msg)
            return rng.normal(loc=mu_vec, scale=np.sqrt(self.cov), size=(n, dim))
        return rng.multivariate_normal(mean=mu_vec, cov=self.cov, size=n)

    sigma_vec = self._sigma_vector(dim)
    return rng.normal(loc=mu_vec, scale=sigma_vec, size=(n, dim))

sample_env(n, d, rng=None)

Sample n additive perturbation vectors ε_i from N(m_ε, Σ_ε).

Parameters:

Name Type Description Default
n int

Number of samples.

required
d int

Dimensionality of the feature space.

required
rng Generator | None

Optional random number generator.

None

Returns:

Type Description
ndarray

np.ndarray: 2D array of shape (n, d).

Source code in src/dicex/distributions/gaussian.py
def sample_env(self, n: int, d: int, rng: np.random.Generator | None = None) -> np.ndarray:
    """Sample n additive perturbation vectors ε_i from N(m_ε, Σ_ε).

    Args:
        n (int): Number of samples.
        d (int): Dimensionality of the feature space.
        rng (np.random.Generator | None): Optional random number generator.

    Returns:
        np.ndarray: 2D array of shape (n, d).
    """
    if rng is None:
        rng = self._rng
    return super().sample_env(n, d, rng)

dicex.UniformPerturbation(mu, delta, *, d=None, env_mean=None, env_cov=None, seed=None)

Bases: BasePerturbation

Uniform perturbation for the pair (T, ε).

T has iid coordinates distributed as Unif[mu - delta, mu + delta]. ε ~ N(env_mean, env_cov) is the additive environmental noise.

Initialize with scalar noise parameters and additive noise parameters.

Parameters:

Name Type Description Default
mu float | ndarray

Mean of the distribution.

required
delta float | ndarray

Half-width of the distribution (radius).

required
d int | None

Optional dimensionality of T when it cannot be inferred later.

None
env_mean ndarray | None

Mean vector m_ε of the additive noise (d,). If None, defaults to zero vector.

None
env_cov ndarray | None

Covariance matrix Σ_ε of the additive noise (d, d). If None, defaults to zero matrix.

None
seed int | None

Optional seed for the random number generator.

None
Source code in src/dicex/distributions/uniform.py
def __init__(
    self,
    mu: float | np.ndarray,
    delta: float | np.ndarray,
    *,
    d: int | None = None,
    env_mean: np.ndarray | None = None,
    env_cov: np.ndarray | None = None,
    seed: int | None = None,
) -> None:
    """Initialize with scalar noise parameters and additive noise parameters.

    Args:
        mu (float | np.ndarray): Mean of the distribution.
        delta (float | np.ndarray): Half-width of the distribution (radius).
        d (int | None): Optional dimensionality of T when it cannot be
            inferred later.
        env_mean (np.ndarray | None): Mean vector m_ε of the additive noise
            (d,). If None, defaults to zero vector.
        env_cov (np.ndarray | None): Covariance matrix Σ_ε of the additive
            noise (d, d). If None, defaults to zero matrix.
        seed (int | None): Optional seed for the random number generator.
    """
    mu_scalar = isinstance(mu, (int, float))
    delta_scalar = isinstance(delta, (int, float))
    delta_arr = np.asarray(delta, dtype=float)
    if np.any(delta_arr < 0):
        msg = f"Half-width delta cannot be negative. Got {delta}"
        raise ValueError(msg)
    scalar_mu = float(cast("float", mu)) if mu_scalar else None
    scalar_delta = float(cast("float", delta)) if delta_scalar else None
    self.mu = scalar_mu if scalar_mu is not None else np.asarray(mu, dtype=float)
    self.delta = scalar_delta if scalar_delta is not None else delta_arr
    self.d = d
    self.env_mean = np.asarray(env_mean, dtype=float) if env_mean is not None else None
    self.env_cov = np.asarray(env_cov, dtype=float) if env_cov is not None else None
    self.seed = seed
    self._rng = np.random.default_rng(seed)

params property

Return the parameters of the distribution.

Returns:

Type Description
dict[str, Any]

dict[str, Any]: Dictionary containing the perturbation configuration.

sample(n, d=None, rng=None)

Sample n directional perturbation vectors with iid uniform coordinates.

Parameters:

Name Type Description Default
n int

Number of samples.

required
d int | None

Optional feature-space dimension.

None
rng Generator | None

Optional random number generator.

None

Returns:

Type Description
ndarray

np.ndarray: 2D array of shape (n, dim).

Source code in src/dicex/distributions/uniform.py
def sample(self, n: int, d: int | None = None, rng: np.random.Generator | None = None) -> np.ndarray:
    """Sample n directional perturbation vectors with iid uniform coordinates.

    Args:
        n (int): Number of samples.
        d (int | None): Optional feature-space dimension.
        rng (np.random.Generator | None): Optional random number generator.

    Returns:
        np.ndarray: 2D array of shape (n, dim).
    """
    if rng is None:
        rng = self._rng
    dim = self._resolve_dim(d)
    mu_vec = np.full(dim, cast("float", self.mu)) if isinstance(self.mu, (int, float)) else self.mu
    delta_vec = np.full(dim, cast("float", self.delta)) if isinstance(self.delta, (int, float)) else self.delta
    return rng.uniform(low=mu_vec - delta_vec, high=mu_vec + delta_vec, size=(n, dim))

sample_env(n, d, rng=None)

Sample n additive perturbation vectors ε_i from N(m_ε, Σ_ε).

Parameters:

Name Type Description Default
n int

Number of samples.

required
d int

Dimensionality of the feature space.

required
rng Generator | None

Optional random number generator.

None

Returns:

Type Description
ndarray

np.ndarray: 2D array of shape (n, d).

Source code in src/dicex/distributions/uniform.py
def sample_env(self, n: int, d: int, rng: np.random.Generator | None = None) -> np.ndarray:
    """Sample n additive perturbation vectors ε_i from N(m_ε, Σ_ε).

    Args:
        n (int): Number of samples.
        d (int): Dimensionality of the feature space.
        rng (np.random.Generator | None): Optional random number generator.

    Returns:
        np.ndarray: 2D array of shape (n, d).
    """
    if rng is None:
        rng = self._rng
    return super().sample_env(n, d, rng)

dicex.CustomPerturbation(sample_fn, sample_env_fn=None, env_mean=None, env_cov=None, params=None)

Bases: BasePerturbation

Perturbation distribution defined by user-provided sampling callables.

Initialize with user-provided callables.

Parameters:

Name Type Description Default
sample_fn Callable

Callable that returns an array of directional samples of shape (n, d).

required
sample_env_fn Callable | None

Optional callable (n, d) -> (n, d) array for ε samples.

None
env_mean ndarray | None

Mean vector m_ε of the additive noise (d,).

None
env_cov ndarray | None

Covariance matrix Σ_ε of the additive noise (d, d).

None
params dict[str, Any] | None

Optional dict of parameters describing the distribution.

None
Source code in src/dicex/distributions/custom.py
def __init__(
    self,
    sample_fn: Callable[[int], np.ndarray] | Callable[[int, int], np.ndarray],
    sample_env_fn: Callable[[int, int], np.ndarray] | None = None,
    env_mean: np.ndarray | None = None,
    env_cov: np.ndarray | None = None,
    params: dict[str, Any] | None = None,
) -> None:
    """Initialize with user-provided callables.

    Args:
        sample_fn (Callable): Callable that returns an array of directional
            samples of shape (n, d).
        sample_env_fn (Callable | None): Optional callable
            (n, d) -> (n, d) array for ε samples.
        env_mean (np.ndarray | None): Mean vector m_ε of the additive noise (d,).
        env_cov (np.ndarray | None): Covariance matrix Σ_ε of the
            additive noise (d, d).
        params (dict[str, Any] | None): Optional dict of parameters
            describing the distribution.
    """
    self._sample_fn = sample_fn
    self._sample_env_fn = sample_env_fn
    self.env_mean = np.asarray(env_mean, dtype=float) if env_mean is not None else None
    self.env_cov = np.asarray(env_cov, dtype=float) if env_cov is not None else None
    self._params = params or {}

params property

Return the parameters of the distribution.

sample(n, d=None, rng=None)

Sample n directional perturbation vectors T_i.

Parameters:

Name Type Description Default
n int

Number of samples.

required
d int | None

Optional feature-space dimension.

None
rng Generator | None

Optional random number generator.

None

Returns:

Type Description
ndarray

np.ndarray: Array of shape (n,) when d is None or the callable ignores d, or (n, d) otherwise.

Source code in src/dicex/distributions/custom.py
def sample(self, n: int, d: int | None = None, rng: np.random.Generator | None = None) -> np.ndarray:
    """Sample n directional perturbation vectors T_i.

    Args:
        n (int): Number of samples.
        d (int | None): Optional feature-space dimension.
        rng (np.random.Generator | None): Optional random number generator.

    Returns:
        np.ndarray: Array of shape (n,) when d is None or the callable
            ignores d, or (n, d) otherwise.
    """
    del rng
    if d is None:
        return np.asarray(cast("Any", self._sample_fn)(n), dtype=float)
    try:
        return np.asarray(cast("Any", self._sample_fn)(n, d), dtype=float)
    except TypeError:
        return np.asarray(cast("Any", self._sample_fn)(n), dtype=float)

sample_env(n, d, rng=None)

Sample n additive perturbation vectors ε_i ∈ R^d.

Parameters:

Name Type Description Default
n int

Number of samples.

required
d int

Dimensionality of the feature space.

required
rng Generator | None

Optional random number generator.

None

Returns:

Type Description
ndarray

np.ndarray: Array of shape (n, d).

Source code in src/dicex/distributions/custom.py
def sample_env(self, n: int, d: int, rng: np.random.Generator | None = None) -> np.ndarray:
    """Sample n additive perturbation vectors ε_i ∈ R^d.

    Args:
        n (int): Number of samples.
        d (int): Dimensionality of the feature space.
        rng (np.random.Generator | None): Optional random number generator.

    Returns:
        np.ndarray: Array of shape (n, d).
    """
    if self._sample_env_fn is not None:
        return self._sample_env_fn(n, d)

    if rng is None:
        rng = np.random.default_rng()

    if self.env_mean is not None and len(self.env_mean) != d:
        msg = f"env_mean has length {len(self.env_mean)}, but d={d}. They must match."
        raise InvalidParameterError(msg)

    mean = self.env_mean if self.env_mean is not None else np.zeros(d)
    cov = self.env_cov if self.env_cov is not None else np.zeros((d, d))

    if np.allclose(cov, 0.0):
        return np.tile(mean, (n, 1))

    if cov.ndim == 1:
        return rng.normal(loc=mean, scale=np.sqrt(cov), size=(n, d))

    return rng.multivariate_normal(mean=mean, cov=cov, size=n)