pal.types module

Type definitions and protocols for the PAL library.

Defines common type aliases, protocols, and configuration classes used throughout the library for type safety and consistency.

class pal.types.ArithmeticProtocol(*args, **kwargs)[source]

Bases: Protocol

Base protocol for objects that support arithmetic operations.

Defines common arithmetic operations shared by both scalar and vector types.

__init__(*args, **kwargs)
class pal.types.Config(n_sims=10000, seed=123456789, rng=Generator(PCG64) at 0x71E451510BA0)[source]

Bases: object

Configuration class for PAL.

n_sims: int = 10000
seed: int = 123456789
rng: Generator = Generator(PCG64) at 0x71E451510BA0
__init__(n_sims=10000, seed=123456789, rng=Generator(PCG64) at 0x71E451510BA0)
class pal.types.DistributionLike(*args, **kwargs)[source]

Bases: Protocol[T_distribution]

Generic protocol for distribution-like objects.

DistributionLike is generic over the type of values it operates on, ensuring type consistency between inputs and outputs for mathematical operations.

Type Parameter:
T_distribution: The type of values the distribution operates on, bounded to

ScipyNumeric (float | int | np.floating | np.integer) or npt.NDArray[np.floating] for vectorized operations.

Key Properties:

  1. Input-Output Consistency: cdf() and invcdf() preserve input types - DistributionLike[float].cdf(x: float) -> float - DistributionLike[NDArray].cdf(x: NDArray) -> NDArray

  2. Scalar and Vector Support: Same distribution can work with both: - Scalar inputs: Individual probability calculations - Array inputs: Vectorized calculations across multiple values

  3. Type Safety: Generic parameter prevents mixing incompatible types and ensures mathematical operations maintain type consistency.

Usage Examples:

```python # Scalar distribution operations def eval_at_point(dist: DistributionLike[float], value: float) -> float:

return dist.cdf(value) # Returns float

# Vectorized distribution operations def eval_array(dist: DistributionLike[NDArray], values: NDArray) -> NDArray:

return dist.cdf(values) # Returns NDArray

# Generate always returns VectorLike (StochasticScalar-like object) def sample_distribution(dist: DistributionLike[Any]) -> VectorLike:

return dist.generate(n_sims=1000)

```

cdf(x)[source]

Compute cumulative distribution function.

Return type:

TypeVar(T_distribution, bound= ScipyNumeric | npt.NDArray[np.floating])

invcdf(u)[source]

Compute inverse cumulative distribution function.

Return type:

TypeVar(T_distribution, bound= ScipyNumeric | npt.NDArray[np.floating])

generate(n_sims=None, rng=None)[source]

Generate random samples from the distribution.

Parameters:
  • n_sims (int | None) – Number of simulations. Uses config.n_sims if None.

  • rng (Generator | None) – Random number generator.

Return type:

ProteusLike[TypeVar(T_distribution, bound= ScipyNumeric | npt.NDArray[np.floating])]

Returns:

Generated samples.

__init__(*args, **kwargs)
class pal.types.NumericProtocol(*args, **kwargs)[source]

Bases: ArithmeticProtocol, Protocol

Protocol for scalar-like objects that support numeric operations.

Comparison operations return bool (scalar semantics).

__init__(*args, **kwargs)
class pal.types.VectorLike(*args, **kwargs)[source]

Bases: VectorOperations, SupportsArray, Protocol[T_co]

Protocol for vector-like objects that support array conversion.

This protocol combines VectorOperations (vector-style arithmetic and comparisons) with SupportsArray (numpy array conversion). Use this for objects that are true vector-like types that can be converted to numpy arrays.

Comparison operations return Self (vectorized semantics).

Why VectorLikeProtocol vs numpy.ArrayLike?

These solve fundamentally different problems:

numpy.ArrayLike: “What can become an array?” - Purpose: Defines what inputs numpy functions will accept and convert to arrays - It’s about data conversion compatibility - Just a Union of types that numpy knows how to convert (lists, objects with

__array__, etc.)

  • Example: np.sum([1, 2, 3]) works because list is ArrayLike

VectorLikeProtocol: “How do math operations behave?” - Purpose: Defines the behavioral contract for mathematical operations - It’s about operation semantics and type preservation - Ensures that operations return the same type (Self), not just any array - Example: StochasticScalar([1, 2, 3]) + 5 returns StochasticScalar, not ndarray

Key Differences:

  1. Type Preservation: VectorLikeProtocol ensures operations maintain the original type (e.g., StochasticScalar + StochasticScalar = StochasticScalar), while ArrayLike would lose this information (becoming ndarray).

  2. Comparison Semantics: VectorLikeProtocol defines that comparisons return vectorized results (Self) for element-wise operations, not scalar bool values. Example: StochasticScalar([1, 2, 3]) > 2 returns StochasticScalar([False, False, True])

  3. Operation Contracts: VectorLikeProtocol defines how mathematical operations should behave for custom types, while ArrayLike only cares about convertibility.

Why Both Are Needed:

Classes implementing VectorLikeProtocol should also be ArrayLike-compatible by implementing __array__() for numpy interoperability. This gives: - Type preservation through operations (VectorLikeProtocol benefit) - Numpy function compatibility (ArrayLike benefit) - Clear semantic distinction between scalar and vector operations

Example Implementation:

class StochasticScalar:

# VectorLikeProtocol: defines operation behavior def __add__(self, other) -> Self: … # Returns StochasticScalar def __gt__(self, other) -> Self: … # Returns StochasticScalar

# ArrayLike compatibility: allows numpy function usage def __array__(self) -> np.ndarray: … # Enables np.sum(stochastic_scalar)

Numpy Compatibility:

VectorLikeProtocol includes numpy compatibility methods: - __array__(): Enables conversion to numpy array for use with numpy functions - __len__(): Required for many numpy operations - __array_ufunc__(): (inherited from ArrayUfuncCapable) Enables proper handling

of numpy universal functions while preserving type

These methods ensure VectorLike objects can seamlessly integrate with numpy’s ecosystem while maintaining their custom type semantics.

__init__(*args, **kwargs)