Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: 2

build:
os: ubuntu-22.04
tools:
python: "3.11"

apt_packages:
- libcairo2-dev
- libfreetype6-dev
- libffi-dev
- libjpeg-dev
- libpng-dev
- libz-dev

mkdocs:
configuration: mkdocs.yml

python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
92 changes: 87 additions & 5 deletions docs/docs/physics/compute.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,28 @@ print(mag_v)

## Stochastic Physics (`phaethon.random`)

Generates highly-optimized stochastic tensors that are instantly bounded by physical dimensions.
Generates highly-optimized stochastic tensors that are instantly bounded by physical dimensions. Phaethon utilizes an isolated `RandomState` engine under the hood, ensuring that stochastic physics simulations remain completely reproducible without contaminating the global NumPy environment.

!!! warning "Axiom Bounds & Stochastic Generation"
Because `phaethon.random` functions automatically instantiate new `BaseUnit` tensors under the hood, generating stochastic values that violate the target unit's physical boundaries (e.g., generating negative `Kelvin` or `Mass`) will immediately trigger an `AxiomViolationError` under the `default` axiom strictness level.

To prevent unexpected crashes in your simulations, ensure your distribution parameters (`low`, `loc`, `scale`) remain within physically valid ranges, or temporarily adjust the axiom's strictness level using [`phaethon.using()`](config.md) or [`phaethon.config()`](config.md).

### phaethon.random.seed

Reseeds the isolated physics random number generator. Crucial for ensuring absolute reproducibility in stochastic physical models, thermodynamic simulations, or machine learning cross-validations.

**Arguments:**

<div class="param-box">
<div class="param-header">
<span class="p-name">seed</span>
<span class="p-sep">—</span>
<span class="p-type">int | None</span>
</div>
<div class="p-desc">An integer to initialize the internal BitGenerator. If None, fresh entropy is drawn from the OS.</div>
</div>

### phaethon.random.uniform / normal

Draws samples from continuous distributions (Uniform or Gaussian) and injects physical DNA.
Expand Down Expand Up @@ -231,6 +246,9 @@ Draws samples from continuous distributions (Uniform or Gaussian) and injects ph
```python
import phaethon as ptn

# Ensure reproducibility
ptn.random.seed(42)

# Uniform: Generate a 3x3 matrix of random pressures (10.5 to 20.5 Pascal)
pressures = ptn.random.uniform(low=10.5, high=20.5, size=(3, 3), unit='Pa')

Expand Down Expand Up @@ -300,15 +318,79 @@ Draws samples from an exponential distribution. Ideal for simulating the time be
<div class="p-desc">The physical dimension to attach (typically 's' or u.Second).</div>
</div>

### phaethon.random.randint / choice

Draws from discrete probability distributions. `randint` generates uniformly distributed integers, while `choice` generates a random sample from a predefined 1-D array of allowed physical states.

**Arguments:**

<div class="param-box">
<div class="param-header">
<span class="p-name">low, high (randint) / a (choice)</span>
<span class="p-sep">—</span>
<span class="p-type">int | ArrayLike</span>
</div>
<div class="p-desc">Integer bounds, or the 1-D array of allowed magnitudes to sample from.</div>
</div>

<div class="param-box">
<div class="param-header">
<span class="p-name">size</span>
<span class="p-sep">—</span>
<span class="p-type">Any</span>
</div>
<div class="p-desc">Output array shape.</div>
</div>

<div class="param-box">
<div class="param-header">
<span class="p-name">unit</span>
<span class="p-sep">—</span>
<span class="p-type">str | type[BaseUnit]</span>
</div>
<div class="p-desc">The physical dimension to attach.</div>
</div>

**Example Usage:**

```python
import phaethon as ptn
import phaethon.units as u

# Generate quantized discrete energy levels (-1, 0, or 1 eV)
energy_levels = ptn.random.randint(-1, 2, size=5, unit=u.Electronvolt)

# Monte Carlo: Select a random speed from allowed discrete states
allowed_speeds = [300.0, 400.0, 500.0]
particles = ptn.random.choice(allowed_speeds, size=10, unit=u.MeterPerSecond)
```

### phaethon.random.shuffle / permutation

Modifies sequence order. `shuffle` modifies a physical tensor sequence in-place along the first axis. `permutation` randomly permutes a tensor and returns a completely new copy (out-of-place).

**Arguments:**

<div class="param-box">
<div class="param-header">
<span class="p-name">x</span>
<span class="p-sep">—</span>
<span class="p-type">BaseUnit | int</span>
</div>
<div class="p-desc">The physical tensor to shuffle/permute. If an integer is passed to permutation, it returns a dimensionless permuted range.</div>
</div>

**Example Usage:**

```python
import phaethon as ptn
import phaethon.units as u

# Simulating time between particle arrivals (average 1.5 seconds)
arrival_times = ptn.random.exponential(scale=1.5, size=(3,), unit=u.Second)
velocity_array = ptn.array([10.0, 20.0, 30.0], unit=u.MeterPerSecond)

# Mutates the array directly in memory
ptn.random.shuffle(velocity_array)

print(arrival_times.dimension)
# Output: 'time'
# Creates a new array with randomized elements
new_tensor = ptn.random.permutation(velocity_array)
```
4 changes: 4 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mkdocs-material[imaging]>=9.0.0
mkdocstrings[python]>=0.24.0
mkdocs-minify-plugin>=0.7.0
mkdocs-git-revision-date-localized-plugin>=1.2.0
102 changes: 102 additions & 0 deletions src/phaethon/_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from __future__ import annotations

from typing import Any, TYPE_CHECKING, TypeAlias, Iterable, TypeVar, Callable, Literal, Protocol, Mapping, TypedDict
from phaethon.core.compat import HAS_POLARS, HAS_PANDAS, HAS_NUMPY, HAS_TORCH

if TYPE_CHECKING:
if HAS_POLARS: import polars as pl
if HAS_PANDAS: import pandas as pd
if HAS_NUMPY: import numpy as np
if HAS_TORCH: import torch
from .core.base import BaseUnit

DataFrameLike: TypeAlias = pd.DataFrame | pl.DataFrame
NumericLike: TypeAlias = int | float | str | np.ndarray | Iterable[Any]
UnitLike: TypeAlias = str | type[BaseUnit]

ConvertibleInput: TypeAlias = NumericLike | 'BaseUnit' | Iterable['BaseUnit']
ColumnTarget: TypeAlias = str | Any
Extractable: TypeAlias = BaseUnit | torch.Tensor | np.ndarray | float | int | str | list[Any] | tuple[Any, ...] | None
UnwrappedArray: TypeAlias = np.ndarray | float | int | None

ContextDict: TypeAlias = dict[str, NumericLike | BaseUnit]
AliasRegistry: TypeAlias = dict[str, str | list[str]]

ImputeMethod: TypeAlias = Literal['mean', 'median', 'mode', 'ffill', 'bfill'] | str | float
InterpolationMethod = Literal[
"linear", "nearest", "time", "index", "values", "pad", "zero", "slinear","quadratic",
"cubic", "spline", "barycentric", "polynomial", "krogh","piecewise_polynomial",
"pchip", "akima", "cubicspline"
]
ErrorAction: TypeAlias = Literal['raise', 'coerce', 'clip']
StrictnessLevel = Literal["default", "strict", "strict_warn", "loose_warn", "ignore"]
NumDtype: TypeAlias = Literal["float64", "float32", "float16", "int64", "int32"]

if HAS_TORCH:
from .core.pinns.tensor import PTensor
TensorLikeDict: TypeAlias = dict[str, PTensor | torch.Tensor]
TensorLikeTuple: TypeAlias = tuple[PTensor | torch.Tensor, ...]
GradTarget: TypeAlias = bool | list[str]

class _ParquetConfig(TypedDict, total=False):
engine: Literal["pyarrow", "fastparquet", "auto"]
compression: Literal["snappy", "gzip", "brotli", "lz4", "zstd"]
index: bool
partition_cols: list[str]

class _HDF5Config(TypedDict, total=False):
compression: Literal["gzip", "lzf", "szip"]
compression_opts: int
chunks: bool | tuple[int, ...]

DatasetInput: TypeAlias = Mapping[str, Any] | Iterable[Any] | Any
DatasetStateDict: TypeAlias = dict[str, dict[str, Any]]

class _ResponsiveTableConfig(TypedDict, total=False):
max_col_width: int
float_format: str
justify: Literal["left", "right", "center"]

else:
DataFrameLike: TypeAlias = Any
NumericLike: TypeAlias = Any
UnitLike: TypeAlias = Any
ConvertibleInput: TypeAlias = Any
ColumnTarget: TypeAlias = Any
Extractable: TypeAlias = Any
UnwrappedArray: TypeAlias = Any
ContextDict: TypeAlias = Any
AliasRegistry: TypeAlias = Any
ImputeMethod: TypeAlias = Any
InterpolationMethod: TypeAlias = Any
ErrorAction: TypeAlias = Any
StrictnessLevel: TypeAlias = Any
NumDtype: TypeAlias = Any
TensorLikeDict: TypeAlias = Any
TensorLikeTuple: TypeAlias = Any
GradTarget: TypeAlias = Any

_Signature: TypeAlias = frozenset[tuple[str, int]]
_DataFrameT = TypeVar("_DataFrameT", bound=DataFrameLike)
_NumericT = TypeVar("_NumericT", bound=NumericLike)
_UnitT = TypeVar("_UnitT", bound='BaseUnit')
_UnitT_co = TypeVar("_UnitT_co", bound='BaseUnit', covariant=True)
_UnitClassT = TypeVar("_UnitClassT", bound=type)
_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
_ReturnT = TypeVar("_ReturnT")
_KeyT = TypeVar('_KeyT')

class SupportsPredict(Protocol):
def fit(self, X: Any, y: Any = None, **kwargs: Any) -> Any: ...
def predict(self, X: Any) -> Any: ...

class SupportsTransform(Protocol):
def fit(self, X: Any, y: Any = None, **kwargs: Any) -> Any: ...
def transform(self, X: Any) -> Any: ...

class SupportsInverseTransform(SupportsTransform, Protocol):
def inverse_transform(self, X: Any) -> Any: ...

_EstimatorT = TypeVar("_EstimatorT", bound=SupportsPredict)
_TransformerT = TypeVar("_TransformerT", bound=SupportsTransform)
_InvTransformerT = TypeVar("_InvTransformerT", bound=SupportsInverseTransform)
2 changes: 1 addition & 1 deletion src/phaethon/core/axioms.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
class SupportsContextEvaluation(Protocol):
def __call__(self, context: ContextDict) -> NumericLike: ...

from .compat import _UnitClassT, _CallableT, NumericLike, ContextDict
from .._typing import _UnitClassT, _CallableT, NumericLike, ContextDict
from ..exceptions import AxiomViolationError, DimensionMismatchError

try:
Expand Down
2 changes: 1 addition & 1 deletion src/phaethon/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .registry import ureg
from .config import get_config, using
from ..exceptions import DimensionMismatchError, PhysicalAlgebraError
from .compat import _UnitT, ContextDict, NumericLike, UnitLike
from .._typing import _UnitT, ContextDict, NumericLike, UnitLike

if TYPE_CHECKING:
import numpy as np
Expand Down
118 changes: 118 additions & 0 deletions src/phaethon/core/base.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import numpy as np
import numpy.typing as npt
from typing import Any, overload
from .._typing import ContextDict, NumericLike, UnitLike, _UnitT

class _DecomposeDispatcher:
def __call__(self) -> str: ...
def __get__(self, instance: Any, owner: type) -> '_DecomposeDispatcher': ...

class _PhaethonUnitMeta(type):
dimension: str | None
symbol: str | None
base_multiplier: float
base_offset: float
is_anonymous: bool
__semantic__: str | None
_signature: frozenset[Any]

def __mul__(cls, other: Any) -> type['BaseUnit']: ...
def __rmul__(cls, other: Any) -> type['BaseUnit']: ...
def __truediv__(cls, other: Any) -> type['BaseUnit']: ...
def __rtruediv__(cls, other: Any) -> type['BaseUnit']: ...
def __pow__(cls, power: int | float) -> type['BaseUnit']: ...
def __matmul__(cls, other: type['BaseUnit']) -> type['BaseUnit']: ...
def __rmatmul__(cls, other: type['BaseUnit']) -> type['BaseUnit']: ...

def __lt__(cls, other: Any) -> bool: ...
def __le__(cls, other: Any) -> bool: ...
def __gt__(cls, other: Any) -> bool: ...
def __ge__(cls, other: Any) -> bool: ...

class BaseUnit(metaclass=_PhaethonUnitMeta):
"""
The foundational core class for all units in Phaethon.
"""
dimension: str | None
symbol: str | None
aliases: list[str] | None
base_multiplier: float
base_offset: float
is_anonymous: bool
__semantic__: str | None
context: ContextDict

def __init__(self, value: NumericLike, context: ContextDict | None = ...) -> None: ...

@property
def mag(self) -> float | npt.NDArray[np.float64]: ...

@property
def si(self) -> 'BaseUnit': ...

@property
def shape(self) -> tuple[int, ...]: ...

@property
def ndim(self) -> int: ...

@property
def T(self) -> 'BaseUnit': ...

def __getitem__(self, key: Any) -> 'BaseUnit': ...

def __add__(self, other: Any) -> 'BaseUnit': ...
def __sub__(self, other: Any) -> 'BaseUnit': ...
def __mul__(self, other: Any) -> 'BaseUnit': ...
def __truediv__(self, other: Any) -> 'BaseUnit': ...
def __floordiv__(self, other: Any) -> 'BaseUnit': ...
def __mod__(self, other: Any) -> 'BaseUnit': ...

def __radd__(self, other: Any) -> 'BaseUnit': ...
def __rsub__(self, other: Any) -> 'BaseUnit': ...
def __rmul__(self, other: Any) -> 'BaseUnit': ...
def __rtruediv__(self, other: Any) -> 'BaseUnit': ...

def __pow__(self, power: int | float) -> 'BaseUnit': ...
def __invert__(self) -> 'BaseUnit': ...
def __neg__(self) -> 'BaseUnit': ...
def __pos__(self) -> 'BaseUnit': ...
def __abs__(self) -> 'BaseUnit': ...
def __round__(self, ndigits: int | None = ...) -> 'BaseUnit': ...

def __matmul__(self, other: Any) -> 'BaseUnit': ...
def __rmatmul__(self, other: Any) -> 'BaseUnit': ...

def sum(self, axis: int | tuple[int, ...] | None = ..., keepdims: bool = ..., **kwargs: Any) -> 'BaseUnit': ...
def mean(self, axis: int | tuple[int, ...] | None = ..., keepdims: bool = ..., **kwargs: Any) -> 'BaseUnit': ...
def max(self, axis: int | tuple[int, ...] | None = ..., keepdims: bool = ..., **kwargs: Any) -> 'BaseUnit': ...
def min(self, axis: int | tuple[int, ...] | None = ..., keepdims: bool = ..., **kwargs: Any) -> 'BaseUnit': ...

def reshape(self, shape: tuple[int, ...] | int, *args: Any, order: str = ...) -> 'BaseUnit': ...
def flatten(self, order: str = ...) -> 'BaseUnit': ...

@overload
def to(self, unit: type[_UnitT], context: ContextDict | None = ...) -> _UnitT: ...
@overload
def to(self, unit: str, context: ContextDict | None = ...) -> 'BaseUnit': ...
def to(self, unit: UnitLike, context: ContextDict | None = ...) -> 'BaseUnit' | _UnitT: ...

def format(self, prec: int = ..., sigfigs: int | None = ..., scinote: bool = ..., delim: bool | str = ..., tag: bool = ...) -> str: ...

decompose: _DecomposeDispatcher

def __eq__(self, other: object) -> bool | npt.NDArray[np.bool_]: ...
def __ne__(self, other: object) -> bool | npt.NDArray[np.bool_]: ...
def __lt__(self, other: Any) -> bool | npt.NDArray[np.bool_]: ...
def __le__(self, other: Any) -> bool | npt.NDArray[np.bool_]: ...
def __gt__(self, other: Any) -> bool | npt.NDArray[np.bool_]: ...
def __ge__(self, other: Any) -> bool | npt.NDArray[np.bool_]: ...

def __float__(self) -> float: ...
def __int__(self) -> int: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...

def __array__(self, dtype: Any = ...) -> npt.NDArray[Any]: ...
def __array_ufunc__(self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any) -> Any: ...
def __array_function__(self, func: Any, types: Any, args: Any, kwargs: Any) -> Any: ...
Loading
Loading