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
8 changes: 8 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ build:
tools:
python: "3.11"

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

mkdocs:
configuration: mkdocs.yml

Expand Down
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)
```
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mkdocs-material>=9.0.0
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
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ keywords = [
]

dependencies = [
"numpy>=1.26.0"
"numpy>=1.26.0",
"packaging>=23.0"
]

classifiers = [
Expand Down
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/backends/pd_engine/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def process(
result_array[mask] = raw_masked
else:
source_instances = source_cls(raw_masked, context=active_context)
target_instances = source_instances.to(field.target_unit)
target_instances = source_instances.to(field.target_unit, active_context)
result_array[mask] = target_instances.mag

except UnitNotFoundError:
Expand Down
4 changes: 2 additions & 2 deletions src/phaethon/core/backends/pl_engine/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def _vectorized_numpy_bridge(s: pl.Series, *args: Any, **kwargs: Any) -> pl.Seri
result_array[mask] = raw_masked
else:
source_instances = source_cls(raw_masked, context=active_context)
target_instances = source_instances.to(field.target_unit)
target_instances = source_instances.to(field.target_unit, active_context)
result_array[mask] = target_instances.mag

except Exception as e:
Expand Down Expand Up @@ -166,7 +166,7 @@ def _vectorized_numpy_bridge(s: pl.Series, *args: Any, **kwargs: Any) -> pl.Seri
if getattr(source_cls, '__name__', '') == getattr(field.target_unit, '__name__', ''):
result_array[pure_num_mask] = raw_masked
else:
result_array[pure_num_mask] = source_cls(raw_masked, context=active_context).to(field.target_unit).mag
result_array[pure_num_mask] = source_cls(raw_masked, context=active_context).to(field.target_unit, active_context).mag

except Exception as e:
if on_err == 'raise':
Expand Down
Loading
Loading